# 【Canvas系列】可视区域内渲染提高 Canvas 的书写性能

1,783 阅读4分钟

## 实现无限画布

### 实现思路

x1 = x - scrollX

y1 = y - scrollY

``````ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
ctx.save();
ctx.translate(scrollX, scrollY);
// 绘制相关的逻辑
// ...
ctx.restore();
``````

### 具体实现

WheelEvent 事件能够监听鼠标滚动。其中的 WheelEvent.deltaX 记录了横向滚动量（也就是我们上面说到的 scrollX），WheelEvent.deltaY 记录了纵向滚动量 （上面说的 scrollY）

``````   const handleCanvasWheel = (e: React.WheelEvent<HTMLCanvasElement>) => {
if (!ctxRef.current) return;
const { deltaX, deltaY } = e;
appState.current.scrollX = appState.current.scrollX - deltaX;
appState.current.scrollY = appState.current.scrollY - deltaY;
render(ctxRef.current);
};

return (
<>
<canvas
ref={canvasRef}
id="draw"
className={styles["draw"]}
onWheel={handleCanvasWheel}
></canvas>
</>
);
``````

## 可视区域内渲染

### 实现思路

``````export interface Pointer {
x: number;
y: number;
}
export const getBoundsFromPoints = (points: Pointer[]) => {
let minX = Infinity;
let minY = Infinity;
let maxX = -Infinity;
let maxY = -Infinity;
for (const { x, y } of points) {
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}

return [minX, minY, maxX, maxY];
};

``````

``````/**
* 判断点是否在画布内
* @param element
* @param canvasWidth
* @param canvasHeight
* @returns
*/
export const isVisibleElement = (
element: Pointer[],
screenCoords = {
minX: 0,
minY: 0,
maxX: window.innerWidth,
maxY: window.innerHeight,
}
) => {
const [x1, y1, x2, y2] = getBoundsFromPoints(element);

return (
x1 <= screenCoords.maxX &&
y1 <= screenCoords.maxY &&
x2 >= screenCoords.minX &&
y2 >= screenCoords.minY
);
};
``````

### 具体实现

`````` const render = useCallback(
(ctx: CanvasRenderingContext2D, points?: Pointer[] | undefined) => {
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);

const pointList = [...elementsRes.current, points || []];
ctx.save();
ctx.translate(appState.current.scrollX, appState.current.scrollY);
pointList.forEach((points) => {
if (!points.length) return;
// 判断是否在可视区域内
console.log(
`是否在可视区域内: `,
isVisibleElement(points, {
minX: -appState.current.scrollX,
minY: -appState.current.scrollY,
maxX: window.innerWidth - appState.current.scrollX,
maxY: window.innerHeight - appState.current.scrollY,
})
);
/**
* 这里为什么是减去scrollX和scrollY呢？
* （上文 handleCanvasWheel 函数有具体代码 👆）
* 因为鼠标在向下滚动的时候, appState.current.scrollY 是减去了偏移量，也就是这里的 appState.current.scrollY = -偏移量
* 因此我们需要通过减法来获取滚动后的真实坐标
*/
if (
isVisibleElement(points, {
minX: -appState.current.scrollX,
minY: -appState.current.scrollY,
maxX: window.innerWidth - appState.current.scrollX,
maxY: window.innerHeight - appState.current.scrollY,
})
) {