svg 画布无限拖动、缩放实现

923 阅读2分钟

效果展示

codesandbox.io/s/svg-empty…

实现思路

  1. 使用 svg pattern 结合 path 元素绘制网格;
  2. 监听画布相关事件,计算偏移量;

svg pattern 介绍

svg pattern 元素可以用于创建可以重复平铺的图案,通常用于填充形状或其他 svg 元素的背景。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <style>
    html,body{
      width100%;
      height100%;
    }
    svg {
      height500px;
      width500px;
    }
  </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 创建一个网格,效果如下图所示

image.png

实现步骤

  1. 状态定义
const gridSize = 50// 网格大小
const maxScale = 3// 最大缩放比例
const minScale = 0.3// 最小缩放比例
const [isMoving, setIsMoving] = useState(false); // 画布是否处于移动状态
const [translate, setTranslate] = useState({ x0y0 }) // 画布平移的偏移量
const [grid, setGrid] = useState({
    x0,
    y0,
    width: gridSize,
    height: gridSize
}); // grid 网格属性
const [startPos, setStartPos] = useState({x0y0}); // 记录鼠标按下的位置,用于计算移动的偏移量
const [scale, setScale] = useState(1); // 画布缩放比例
  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.width0 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>
);
  1. 编写 handleMouseDown 事件
// 1. 记录 moving 状态
// 2. 记录初识位置
const handleMouseDown = (e: any) => {
    setIsMoving(true);
    setStartPos({ x: e.clientXy: e.clientY });
};
  1. 编写 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.clientXy: e.clientY });
};
  1. 编写 handleMouseUp 事件
// 退出 moving 状态
const handleMouseUp = () => {
    setIsMoving(false);
};

至此,画布的移动功能就搞定了~,最后再来编写画布缩放相关的函数,也是一个难点。

  1. 编写 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.clientXy: 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 };
};

参考