Konva React SDK如何实现所有绘制线条固定延迟后淡出效果
Konva React 自由绘制线条固定延迟淡出实现方案
核心逻辑不需要改动官方自由绘制示例的基础框架,只要在「线条绘制完成」的节点挂载独立的延迟动画逻辑即可,比全局轮询修改透明度性能好很多,也不会出现多线条计时错乱的问题。
实现步骤
- 首先给每根线条的数据结构补充基础属性:初始
opacity设为1,可加isFading标记位避免重复触发动画 - 捕捉绘制完成时机:在Stage绑定的
mouseup/touchend事件触发时,说明当前拖拽绘制的线条已经定稿,此时给这根线单独启动延迟定时器 - 延迟时长到了之后,直接调用Konva节点原生的补间方法做透明度渐变,不要自己手动逐帧setState改透明度,避免不必要的重渲染
- 淡出动画结束后,直接把对应线条从渲染列表里移除,释放内存
核心可运行代码(基于官方示例改造)
import React, { useState, useRef, useEffect } from 'react'; import { Stage, Layer, Line } from 'react-konva'; const DrawBoard = () => { const [lines, setLines] = useState([]); const isDrawing = useRef(false); const timerRef = useRef({}); // 存储每个线条对应的定时器,方便清理 const stageRef = useRef(null); // 可按需调整的配置项 const STAY_DURATION = 3000; // 绘制完成后停留3秒再开始淡出 const FADE_DURATION = 500; // 淡出动画总耗时0.5秒 // 组件卸载时清除所有定时器,避免内存泄漏 useEffect(() => { return () => Object.values(timerRef.current).forEach(id => clearTimeout(id)); }, []); const handlePointerDown = (e) => { isDrawing.current = true; const pos = e.target.getStage().getPointerPosition(); setLines(prev => [...prev, { points: [pos.x, pos.y], opacity: 1 }]); }; const handlePointerMove = (e) => { if (!isDrawing.current) return; const stage = e.target.getStage(); const pos = stage.getPointerPosition(); setLines(prev => { const newLines = [...prev]; const lastLine = newLines.at(-1); lastLine.points.push(pos.x, pos.y); return newLines; }); }; const handlePointerUp = () => { if (!isDrawing.current) return; isDrawing.current = false; const currentLineIndex = lines.length; // 刚加入的线条索引等于当前数组长度 // 启动延迟淡出计时器 timerRef.current[currentLineIndex] = setTimeout(() => { const lineNode = stageRef.current?.findOne(`#draw-line-${currentLineIndex}`); if (!lineNode) return; // 调用Konva原生补间,性能远高于手动setState刷帧 lineNode.to({ opacity: 0, duration: FADE_DURATION / 1000, onFinish: () => { // 动画结束移除线条 setLines(prev => prev.filter((_, idx) => idx !== currentLineIndex)); delete timerRef.current[currentLineIndex]; } }); }, STAY_DURATION); }; return ( <Stage ref={stageRef} width={window.innerWidth} height={window.innerHeight} onMouseDown={handlePointerDown} onMouseMove={handlePointerMove} onMouseUp={handlePointerUp} onTouchStart={handlePointerDown} onTouchMove={handlePointerMove} onTouchEnd={handlePointerUp} > <Layer> {lines.map((line, idx) => ( <Line key={idx} id={`draw-line-${idx}`} points={line.points} stroke="#2563eb" strokeWidth={4} tension={0.6} lineCap="round" lineJoin="round" opacity={line.opacity} /> ))} </Layer> </Stage> ); }; export default DrawBoard;
注意事项
- 如果需要实现「最后一根线画完后,所有线一起淡出」的效果,不要给单根线绑定定时器,只要全局维护一个计时器,每次触发
pointerdown/pointermove的时候重置计时器,延迟到点后遍历画布上所有Line节点统一触发淡出动画即可 - 如果有橡皮擦、撤销功能,手动删除线条的时候一定要记得清除对应线条绑定的setTimeout,避免已经删掉的线还触发状态更新报错
- 不要用setInterval全局轮询修改所有线条的透明度,线条数量多的时候会造成严重的性能卡顿,Konva原生的Tween走的是画布独立渲染队列,不会触发React重渲染,性能差距非常明显
内容的提问来源于stack exchange,提问作者dpatra
相关产品推荐
相关产品推荐

