Skip to content

Commit 5ab6511

Browse files
committed
fix: align overlay precisely and mask QR modules correctly
1 parent 324e522 commit 5ab6511

2 files changed

Lines changed: 40 additions & 35 deletions

File tree

example/src/assets/react.svg

Lines changed: 4 additions & 1 deletion
Loading

src/ReactQR.tsx

Lines changed: 36 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -59,52 +59,53 @@ export const ReactQR = forwardRef<SVGSVGElement, ReactQRProps>(
5959
const qrSize = qr?.size ?? 0;
6060
const cellSize = qrSize > 0 ? (size - margin * 2) / qrSize : 0;
6161

62-
const [{ modulesW, modulesH }, setOverlayModules] = useState({
63-
modulesW: 0,
64-
modulesH: 0,
65-
});
62+
const [{ w, h }, setOverlay] = useState({ w: 0, h: 0 });
6663

6764
useLayoutEffect(() => {
68-
if (!children || !childrenSvgRef.current || !cellSize) return;
69-
70-
const bBox = childrenSvgRef.current.getBBox();
71-
setOverlayModules({
72-
modulesW: Math.ceil((bBox.width + 1) / cellSize),
73-
modulesH: Math.ceil((bBox.height + 1) / cellSize),
74-
});
75-
}, [cellSize, children]);
65+
if (children && childrenSvgRef.current) {
66+
const { width, height } = childrenSvgRef.current.getBBox();
67+
setOverlay({ w: width, h: height });
68+
}
69+
}, [children]);
7670

77-
const { startX, startY, endX, endY } = useMemo(() => {
78-
if (children) {
79-
const sx = Math.floor((qrSize - modulesW) / 2);
80-
const sy = Math.floor((qrSize - modulesH) / 2);
81-
return {
82-
startX: sx,
83-
startY: sy,
84-
endX: sx + modulesW,
85-
endY: sy + modulesH,
86-
};
71+
const mask = useMemo(() => {
72+
if (!children || !cellSize) {
73+
return { sx: -1, sy: -1, ex: -1, ey: -1, fx: 0, fy: 0 };
8774
}
88-
return { startX: -1, startY: -1, endX: -1, endY: -1 };
89-
}, [children, qrSize, modulesW, modulesH]);
75+
76+
const startXf = (qrSize - w / cellSize) / 2;
77+
const startYf = (qrSize - h / cellSize) / 2;
78+
const endXf = startXf + w / cellSize;
79+
const endYf = startYf + h / cellSize;
80+
81+
return {
82+
sx: Math.floor(startXf),
83+
sy: Math.floor(startYf),
84+
ex: Math.ceil(endXf),
85+
ey: Math.ceil(endYf),
86+
fx: margin + startXf * cellSize,
87+
fy: margin + startYf * cellSize,
88+
};
89+
}, [children, w, h, cellSize, qrSize, margin]);
9090

9191
const qrPath = useMemo(() => {
9292
if (!qr) return "";
93-
93+
const { sx, sy, ex, ey } = mask;
9494
const modules = qr.getModules();
9595
let path = "";
96-
for (let y = 0; y < qrSize; y += 1) {
97-
for (let x = 0; x < qrSize; x += 1) {
98-
const skip = x >= startX && x < endX && y >= startY && y < endY;
96+
97+
for (let y = 0; y < qrSize; y++) {
98+
for (let x = 0; x < qrSize; x++) {
99+
const skip = x >= sx && x < ex && y >= sy && y < ey;
99100
if (!skip && modules[y][x]) {
100-
const rectX = margin + x * cellSize;
101-
const rectY = margin + y * cellSize;
102-
path += `M${rectX},${rectY}h${cellSize}v${cellSize}h-${cellSize}z`;
101+
const px = margin + x * cellSize;
102+
const py = margin + y * cellSize;
103+
path += `M${px},${py}h${cellSize}v${cellSize}h-${cellSize}z`;
103104
}
104105
}
105106
}
106107
return path;
107-
}, [qr, qrSize, startX, endX, startY, endY, margin, cellSize]);
108+
}, [qr, qrSize, mask, margin, cellSize]);
108109

109110
if (!qr) return null;
110111

@@ -123,8 +124,9 @@ export const ReactQR = forwardRef<SVGSVGElement, ReactQRProps>(
123124
{children ? (
124125
<svg
125126
ref={childrenSvgRef}
126-
x={margin + startX * cellSize}
127-
y={margin + startY * cellSize}
127+
x={mask.fx}
128+
y={mask.fy}
129+
pointerEvents="none"
128130
>
129131
{children}
130132
</svg>

0 commit comments

Comments
 (0)