diff --git a/src/__test__/index.test.tsx b/src/__test__/index.test.tsx
index 24687c1..27dd497 100644
--- a/src/__test__/index.test.tsx
+++ b/src/__test__/index.test.tsx
@@ -170,3 +170,42 @@ describe('`style` is passed to rendered nodes and merged correctly', () => {
expect(container.firstChild).toMatchSnapshot();
});
});
+
+describe('Device Pixel Ratio Detection', () => {
+ test('QRCodeCanvas reacts to devicePixelRatio changes', async () => {
+ // Import act from testing library
+ const { act } = await import('@testing-library/react');
+
+ // Mock window.devicePixelRatio
+ const originalDevicePixelRatio = window.devicePixelRatio;
+ Object.defineProperty(window, 'devicePixelRatio', {
+ writable: true,
+ value: 1,
+ });
+
+ const {rerender} = render();
+
+ // Simulate zoom change by changing devicePixelRatio and triggering resize
+ Object.defineProperty(window, 'devicePixelRatio', {
+ writable: true,
+ value: 2,
+ });
+
+ // Trigger resize event to simulate zoom wrapped in act
+ await act(async () => {
+ window.dispatchEvent(new Event('resize'));
+ });
+
+ // Re-render to trigger effect
+ rerender();
+
+ // The component should handle the change gracefully
+ expect(window.devicePixelRatio).toBe(2);
+
+ // Restore original value
+ Object.defineProperty(window, 'devicePixelRatio', {
+ writable: true,
+ value: originalDevicePixelRatio,
+ });
+ });
+});
diff --git a/src/index.tsx b/src/index.tsx
index b6dd37a..8c62956 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -384,6 +384,11 @@ const QRCodeCanvas = React.forwardRef(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [isImgLoaded, setIsImageLoaded] = React.useState(false);
+ // Track devicePixelRatio to re-render when zoom changes
+ const [devicePixelRatio, setDevicePixelRatio] = React.useState(
+ () => (typeof window !== 'undefined' ? window.devicePixelRatio : 1) || 1
+ );
+
const {margin, cells, numCells, calculatedImageSettings} = useQRCode({
value,
level,
@@ -395,6 +400,23 @@ const QRCodeCanvas = React.forwardRef(
size,
});
+ // Listen for resize events to detect zoom changes
+ React.useEffect(() => {
+ if (typeof window === 'undefined') return;
+
+ const handleResize = () => {
+ const newPixelRatio = window.devicePixelRatio || 1;
+ if (newPixelRatio !== devicePixelRatio) {
+ setDevicePixelRatio(newPixelRatio);
+ }
+ };
+
+ window.addEventListener('resize', handleResize);
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+ }, [devicePixelRatio]);
+
React.useEffect(() => {
// Always update the canvas. It's cheap enough and we want to be correct
// with the current state.
@@ -428,7 +450,7 @@ const QRCodeCanvas = React.forwardRef(
// matches the number of cells. This avoids rounding issues, but does
// result in some potentially unwanted single pixel issues between
// blocks, only in environments that don't support Path2D.
- const pixelRatio = window.devicePixelRatio || 1;
+ const pixelRatio = devicePixelRatio;
canvas.height = canvas.width = size * pixelRatio;
const scale = (size / numCells) * pixelRatio;
ctx.scale(scale, scale);
@@ -465,7 +487,17 @@ const QRCodeCanvas = React.forwardRef(
);
}
}
- });
+ }, [
+ cells,
+ numCells,
+ size,
+ bgColor,
+ fgColor,
+ margin,
+ calculatedImageSettings,
+ isImgLoaded,
+ devicePixelRatio,
+ ]);
// Ensure we mark image loaded as false here so we trigger updating the
// canvas in our other effect.