效果展示
实现思路
- 使用 svg pattern 结合 path 元素绘制网格;
- 监听画布相关事件,计算偏移量;
svg pattern 介绍
svg pattern 元素可以用于创建可以重复平铺的图案,通常用于填充形状或其他 svg 元素的背景。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<style>
html,body{
width: 100%;
height: 100%;
}
svg {
height: 500px;
width: 500px;
}
</style>
</head>
<body>
<svg>
<defs>
<pattern id="gridPattern" patternUnits="userSpaceOnUse" width="10" height="10">
<path d="M 0 0 L 0 10" stroke="black" stroke-width="0.5" />
<path d="M 0 0 L 10 0" stroke="black" stroke-width="0.5" />
</pattern>
</defs>
<rect x="0" y="0" width="200" height="200" fill="url(#gridPattern)" />
</svg>
</body>
</html>
以上代码可以利用 pattern 创建一个网格,效果如下图所示
实现步骤
- 状态定义
const gridSize = 50; // 网格大小
const maxScale = 3; // 最大缩放比例
const minScale = 0.3; // 最小缩放比例
const [isMoving, setIsMoving] = useState(false); // 画布是否处于移动状态
const [translate, setTranslate] = useState({ x: 0, y: 0 }) // 画布平移的偏移量
const [grid, setGrid] = useState({
x: 0,
y: 0,
width: gridSize,
height: gridSize
}); // grid 网格属性
const [startPos, setStartPos] = useState({x: 0, y: 0}); // 记录鼠标按下的位置,用于计算移动的偏移量
const [scale, setScale] = useState(1); // 画布缩放比例
- 添加 svg dom;
return (
<div
id="container"
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
onMouseOut={handleMouseUp}
onWheel={handleWheel}
>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
<defs>
<pattern
id="pattern_0"
patternUnits="userSpaceOnUse"
x={grid.x}
y={grid.y}
width={grid.width}
height={grid.height}
>
<path
d={`M ${grid.width} 0 H0 M0 0 V0 ${grid.height}`}
stroke="rgba(224,224,224,1)"
strokeWidth="1"
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill="url(#pattern_0)" />
</svg>
</div>
);
- 编写 handleMouseDown 事件
// 1. 记录 moving 状态
// 2. 记录初识位置
const handleMouseDown = (e: any) => {
setIsMoving(true);
setStartPos({ x: e.clientX, y: e.clientY });
};
- 编写 handleMouseMove 事件
const handleMouseMove = (e: any) => {
if (!isMoving) {
return;
}
// 计算平移的偏移量
const offsetX = e.clientX - startPos.x;
const offsetY = e.clientY - startPos.y;
// 计算缩放后的网格大小
const newGridSize = scale * gridSize;
let x = (grid.x + offsetX) % newGridSize;
if (x < 0) {
x += newGridSize;
}
let y = (grid.y + offsetY) % newGridSize;
if (y < 0) {
y += newGridSize;
}
// 更新网格的 x、y
// x, y 表示能够露出的内容长度和高度
setGrid({ ...grid, ...{ x, y } });
// 更新整个画布的偏移量
setTranslate({ x: translate.x + offsetX, y: translate.y + offsetY });
// 更新当前位置
setStartPos({ x: e.clientX, y: e.clientY });
};
- 编写 handleMouseUp 事件
// 退出 moving 状态
const handleMouseUp = () => {
setIsMoving(false);
};
至此,画布的移动功能就搞定了~,最后再来编写画布缩放相关的函数,也是一个难点。
- 编写 handleWheel 事件
const toFixed = (value: number) => Number(value.toFixed(3));
const handleWheel = (e: any) => {
// 计算新的缩放比例
let newScale = toFixed(e.deltaY > 0 ? scale / 1.1 : scale * 1.1);
// 临界值判断
if (newScale > maxScale || newScale < minScale) {
return;
}
const center = { x: e.clientX, y: e.clientY };
// 调用 zoom 函数对 grid 进行缩放
const grid = zoom(newScale, center);
setGrid(grid);
setScale(newScale);
};
const zoom = (factor: number, center: any) => {
let cx = center.x;
let cy = center.y;
const ts = translate;
const tx = cx - (cx - ts.x) * (factor / scale);
const ty = cy - (cy - ts.y) * (factor / scale);
setTranslate({ x: tx, y: ty });
const width = factor * gridSize;
const height = factor * gridSize;
let x = toFixed(tx % width);
if (x < 0) {
x += width;
}
let y = toFixed(ty % height);
if (y < 0) {
y += height;
}
return { x, y, width, height };
};