跳到主要内容

如何使用 Konva 创建响应式 Canvas

Canvas 有显示尺寸和绘制尺寸。CSS 调整只会更改显示尺寸。此更改可能拉伸已绘制的像素。

对于响应式 Konva 场景,请定义固定的虚拟尺寸。然后根据容器的可用宽度缩放舞台。

缩放公式如下:

scale = containerWidth / virtualWidth

显示高度为 virtualHeight * scale。所有节点坐标都保留在虚拟坐标系中。

响应式 React 示例

此示例使用 ResizeObserver。容器尺寸变化时,观察器会作出响应。它不只响应浏览器窗口的尺寸变化。

import { useEffect, useRef, useState } from 'react';
import { Stage, Layer, Rect, Circle, Text } from 'react-konva';

const VIRTUAL_WIDTH = 900;
const VIRTUAL_HEIGHT = 420;

const App = () => {
  const containerRef = useRef(null);
  const [containerWidth, setContainerWidth] = useState(VIRTUAL_WIDTH);

  useEffect(() => {
    const container = containerRef.current;
    const observer = new ResizeObserver(([entry]) => {
      setContainerWidth(entry.contentRect.width);
    });

    observer.observe(container);
    return () => observer.disconnect();
  }, []);

  const scale = containerWidth / VIRTUAL_WIDTH;

  return (
    <div ref={containerRef} style={{ width: '100%' }}>
      <Stage
        width={containerWidth}
        height={VIRTUAL_HEIGHT * scale}
        scaleX={scale}
        scaleY={scale}
      >
        <Layer>
          <Rect
            width={VIRTUAL_WIDTH}
            height={VIRTUAL_HEIGHT}
            fill="#e7f5ff"
          />
          <Text
            x={40}
            y={35}
            text="900 × 420 virtual scene"
            fontSize={28}
            fill="#1864ab"
          />
          <Circle
            x={VIRTUAL_WIDTH / 2}
            y={VIRTUAL_HEIGHT / 2}
            radius={70}
            fill="#4dabf7"
            draggable
          />
          <Rect
            x={VIRTUAL_WIDTH - 190}
            y={VIRTUAL_HEIGHT - 130}
            width={150}
            height={90}
            fill="#74c0fc"
            cornerRadius={10}
            draggable
          />
        </Layer>
      </Stage>
    </div>
  );
};

export default App;

不要在应用的 state 中存储缩放后的坐标。请存储虚拟坐标。Konva 在绘制和转换指针坐标时应用舞台缩放比例。

对于高像素密度屏幕,Konva 默认使用设备像素比。除非应用自行管理后备存储,否则不要用 devicePixelRatio 乘以舞台尺寸。

有关调整舞台尺寸的更多信息,请参阅响应式 Canvas 沙盒示例