You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React-Konva有限尺寸工作区缩放:如何实现流畅且可调显示大小?

可以实现,以下是优化流畅性的解决方案

你的流畅性问题核心在于:用React状态zoom驱动Stage的宽高属性,导致每次缩放都会触发React重渲染Stage组件,打断了Konva自身的高效绘制流程。下面是分离工作区内在尺寸与屏幕显示尺寸的优化方案:

关键优化思路

  1. 分离两类尺寸:
    • 工作区内在尺寸:固定为你设定的500x500,这是画布的实际内容尺寸
    • Stage屏幕显示尺寸:匹配父容器大小(比如全屏),这是画布在屏幕上的占位尺寸
  2. 避免React重渲染干扰:不再用zoom状态控制Stage的宽高,仅用Konva原生的stage.scale()控制缩放,zoom状态仅用于记录当前比例(如需展示)
  3. 保持缩放交互逻辑:维持以鼠标位置为中心的缩放体验,和Photoshop一致

优化后的完整代码

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

const App = () => {
  const [zoom, setZoom] = useState(1);
  const stageRef = useRef(null);
  const containerRef = useRef(null);

  // 固定的工作区内在尺寸
  const workspaceWidth = 500;
  const workspaceHeight = 500;
  // Stage的屏幕显示尺寸(匹配父容器)
  const [stageSize, setStageSize] = useState({ width: 500, height: 500 });

  useEffect(() => {
    // 监听容器尺寸变化,自动适配Stage显示大小
    const updateStageSize = () => {
      if (containerRef.current) {
        setStageSize({
          width: containerRef.current.clientWidth,
          height: containerRef.current.clientHeight
        });
      }
    };
    updateStageSize();
    window.addEventListener('resize', updateStageSize);
    return () => window.removeEventListener('resize', updateStageSize);
  }, []);

  const handleScale = (event) => {
    const stage = stageRef.current;
    if (!stage) return;

    const evt = event.evt;
    evt.preventDefault();

    // 仅在按住Ctrl/Command键时触发缩放
    if (!evt.ctrlKey && !evt.metaKey) return;

    const oldScale = stage.scaleX() || 1;
    const pointer = stage.getPointerPosition();
    if (!pointer) return;

    const minScale = 0.1;
    const maxScale = 10;
    const scaleSpeed = 0.015;

    // 计算新缩放比例
    const newScale = Math.min(
      Math.max(oldScale - evt.deltaY * scaleSpeed, minScale),
      maxScale
    );

    // 计算锚点,保证鼠标指向的位置在缩放后不变
    const anchorPoint = {
      x: (pointer.x - stage.x()) / oldScale,
      y: (pointer.y - stage.y()) / oldScale
    };

    stage.scale({ x: newScale, y: newScale });
    stage.position({
      x: pointer.x - anchorPoint.x * newScale,
      y: pointer.y - anchorPoint.y * newScale
    });

    // 仅更新状态用于展示,不触发Stage重渲染
    setZoom(newScale);
    // 用Konva批量绘制优化性能
    stage.batchDraw();
  };

  return (
    <div 
      ref={containerRef} 
      style={{ 
        width: '100vw', 
        height: '100vh', 
        overflow: 'hidden',
        border: '1px solid #ccc'
      }}
    >
      <Stage
        width={stageSize.width}
        height={stageSize.height}
        ref={stageRef}
        onWheel={handleScale}
      >
        <Layer>
          {/* 工作区背景,直观标记出500x500的内容范围 */}
          <Rect 
            x={0} 
            y={0} 
            width={workspaceWidth} 
            height={workspaceHeight} 
            fill="#f5f5f5" 
            stroke="#ddd"
          />
          <Rect x={50} y={50} width={100} height={100} fill="red" draggable />
        </Layer>
      </Stage>
      {/* 可选:显示当前缩放比例 */}
      <div style={{ position: 'absolute', top: 10, left: 10, background: 'white', padding: '4px 8px', borderRadius: '4px' }}>
        缩放比例:{Math.round(zoom * 100)}%
      </div>
    </div>
  );
};

export default App;

代码说明

  • 父容器div控制Stage的屏幕占位大小(这里设为全屏),缩放时不会改变这个容器的尺寸
  • 工作区的500x500是Konva画布的内在内容尺寸,通过stage.scale()控制它在屏幕上的显示大小,完全由Konva原生处理绘制,避免React重渲染的开销
  • 缩放逻辑保持以鼠标位置为中心,确保交互体验符合Photoshop等设计工具的习惯

内容的提问来源于stack exchange,提问作者Shell

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 17:27:50