React-Konva有限尺寸工作区缩放:如何实现流畅且可调显示大小?
可以实现,以下是优化流畅性的解决方案
你的流畅性问题核心在于:用React状态zoom驱动Stage的宽高属性,导致每次缩放都会触发React重渲染Stage组件,打断了Konva自身的高效绘制流程。下面是分离工作区内在尺寸与屏幕显示尺寸的优化方案:
关键优化思路
- 分离两类尺寸:
- 工作区内在尺寸:固定为你设定的
500x500,这是画布的实际内容尺寸 - Stage屏幕显示尺寸:匹配父容器大小(比如全屏),这是画布在屏幕上的占位尺寸
- 工作区内在尺寸:固定为你设定的
- 避免React重渲染干扰:不再用
zoom状态控制Stage的宽高,仅用Konva原生的stage.scale()控制缩放,zoom状态仅用于记录当前比例(如需展示) - 保持缩放交互逻辑:维持以鼠标位置为中心的缩放体验,和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
相关产品推荐
相关产品推荐

