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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 20:27:16