React Konva中实现矩形拖动自动横竖滚动(仿Trello效果)
嘿,这个需求我之前做过类似的,刚好可以给你分享一个贴近Trello交互的实现方案!核心思路是靠外层滚动容器+拖动时的边缘检测来实现,具体步骤如下:
实现方案拆解
1. 先搭好滚动基础结构
Konva的Stage本身不具备滚动能力,所以我们需要给它套一个外层的滚动容器。这个容器设置固定的可视范围,Stage的宽高要远大于容器,这样才能产生滚动空间:
import { useRef } from 'react'; import { Stage, Layer, Rect } from 'react-konva'; const ScrollableBoard = () => { // 用来操作滚动的容器ref const scrollContainerRef = useRef(null); // Stage的ref(可选,用来做一些坐标转换) const stageRef = useRef(null); return ( {/* 外层滚动容器:固定视口,超出部分滚动 */} <div ref={scrollContainerRef} style={{ width: '100vw', height: '90vh', overflow: 'auto', // 隐藏滚动条,完全模仿Trello的无滚动条体验 scrollbarWidth: 'none', msOverflowStyle: 'none' }} > {/* Stage的宽高要足够大,比如模拟横向长列表+纵向高度 */} <Stage ref={stageRef} width={3000} height={2000}> <Layer> {/* 可拖动的矩形元素 */} <Rect x={100} y={100} width={200} height={150} fill="#2563eb" draggable shadowBlur={5} /> {/* 在右侧/底部添加更多元素,测试滚动 */} <Rect x={2700} y={1700} width={200} height={150} fill="#ef4444" draggable shadowBlur={5} /> </Layer> </Stage> </div> ); }; export default ScrollableBoard;
注:上面的scrollbarWidth: 'none'是用来隐藏滚动条的,完全贴合Trello的无滚动条交互。
2. 实现拖动时的边缘触发滚动逻辑
接下来是核心:在拖动元素的过程中,实时检测鼠标位置,当靠近容器边缘时触发自动滚动。我们需要监听元素的onDragMove事件,结合requestAnimationFrame实现平滑滚动:
import { useRef, useState, useEffect } from 'react'; import { Stage, Layer, Rect } from 'react-konva'; const ScrollableBoard = () => { const scrollContainerRef = useRef(null); const stageRef = useRef(null); // 标记是否正在滚动,避免重复触发 const [isScrolling, setIsScrolling] = useState(false); // 触发滚动的边缘阈值(比如距离边缘20px时开始滚动) const edgeThreshold = 20; // 基础滚动速度,可根据需求调整 const baseScrollSpeed = 6; const handleDragMove = (e) => { const container = scrollContainerRef.current; if (!container) return; // 获取鼠标相对于浏览器视口的位置 const mouseX = e.evt.clientX; const mouseY = e.evt.clientY; // 获取滚动容器的位置信息 const containerRect = container.getBoundingClientRect(); let scrollX = 0; let scrollY = 0; // 检测水平边缘:右侧/左侧 if (mouseX >= containerRect.right - edgeThreshold) { scrollX = baseScrollSpeed; } else if (mouseX <= containerRect.left + edgeThreshold) { scrollX = -baseScrollSpeed; } // 检测垂直边缘:底部/顶部 if (mouseY >= containerRect.bottom - edgeThreshold) { scrollY = baseScrollSpeed; } else if (mouseY <= containerRect.top + edgeThreshold) { scrollY = -baseScrollSpeed; } // 有滚动需求时开启滚动循环 if (scrollX !== 0 || scrollY !== 0) { if (!isScrolling) setIsScrolling(true); // 执行一次滚动 container.scrollLeft += scrollX; container.scrollTop += scrollY; } else { if (isScrolling) setIsScrolling(false); } }; // 用requestAnimationFrame实现持续平滑滚动 useEffect(() => { let animationId; const container = scrollContainerRef.current; const scrollLoop = () => { if (!isScrolling || !container) return; // 这里可以重复之前的边缘检测逻辑,保持持续滚动 // (或者直接复用handleDragMove的逻辑,简化写法) animationId = requestAnimationFrame(scrollLoop); }; if (isScrolling) { animationId = requestAnimationFrame(scrollLoop); } // 组件卸载时取消动画,防止内存泄漏 return () => cancelAnimationFrame(animationId); }, [isScrolling]); return ( <div ref={scrollContainerRef} style={{ width: '100vw', height: '90vh', overflow: 'auto', scrollbarWidth: 'none', msOverflowStyle: 'none' }} > <Stage ref={stageRef} width={3000} height={2000}> <Layer> <Rect x={100} y={100} width={200} height={150} fill="#2563eb" draggable onDragMove={handleDragMove} shadowBlur={5} /> <Rect x={2700} y={1700} width={200} height={150} fill="#ef4444" draggable onDragMove={handleDragMove} shadowBlur={5} /> </Layer> </Stage> </div> ); }; export default ScrollableBoard;
3. 优化体验的小技巧
- 动态调整滚动速度:可以根据鼠标距离边缘的远近调整速度,越靠近边缘速度越快,比如:
const distanceToEdge = containerRect.right - mouseX; scrollX = baseScrollSpeed + (edgeThreshold - distanceToEdge) * 2; - 节流处理:如果拖动时事件触发太频繁,可以给
handleDragMove加节流,避免性能问题。 - 坐标转换:如果需要更精准的元素位置判断,可以用Konva的
stage.getPointerPosition()来获取相对于Stage的鼠标坐标。
内容的提问来源于stack exchange,提问作者Nidhi Dadiya
相关产品推荐
相关产品推荐

