React实现带可拖动边框的三类产权占比调整区块方案咨询
实现React水平拖动调整产权占比的优化方案
针对你遇到的react-draggable卡顿问题,推荐直接基于原生鼠标/触摸事件实现拖动逻辑——这种方案更轻量,能精准控制状态更新时机,避免第三方库的额外封装开销。以下是具体实现思路和代码示例:
核心思路
- 布局结构:用一个父容器包裹三个产权区块,在区块之间放置蓝色拖动分隔线(绝对定位),通过百分比控制各区块宽度。
- 状态管理:维护完全产权、裸产权的占比状态,用益物权占比通过
100 - 完全产权占比 - 裸产权占比自动计算,确保总和始终为100%。 - 拖动逻辑:
- 给拖动条绑定
mousedown/touchstart事件,记录初始鼠标位置和当前占比状态。 - 在全局
mousemove/touchmove事件中计算拖动距离,转换为百分比变化,更新状态并做边界限制(比如占比不能为负、不能超过剩余可用比例)。 - 绑定
mouseup/touchend事件,结束拖动并移除全局事件监听,避免内存泄漏。
- 给拖动条绑定
- 性能优化:用
useRef存储拖动状态和初始值(避免状态更新触发不必要重渲染),用useCallback缓存事件处理函数,减少组件渲染开销。
代码示例
import { useState, useRef, useCallback } from 'react'; const PropertyRatio = () => { // 初始占比:完全产权50%,裸产权30%,用益物权自动计算为20% const [fullPercent, setFullPercent] = useState(50); const [barePercent, setBarePercent] = useState(30); const usufructPercent = 100 - fullPercent - barePercent; // 存储容器DOM、拖动状态、初始位置和占比 const containerRef = useRef(null); const isDraggingFirst = useRef(false); const isDraggingSecond = useRef(false); const startX = useRef(0); const startFullPercent = useRef(0); const startBarePercent = useRef(0); // 处理第一个拖动条(完全产权与裸产权之间)的拖动开始 const handleFirstDragStart = useCallback((e) => { isDraggingFirst.current = true; startX.current = e.clientX; startFullPercent.current = fullPercent; document.addEventListener('mousemove', handleFirstDragMove); document.addEventListener('mouseup', handleDragEnd); e.preventDefault(); }, [fullPercent]); // 第一个拖动条的拖动过程 const handleFirstDragMove = useCallback((e) => { if (!isDraggingFirst.current) return; const containerWidth = containerRef.current.offsetWidth; const deltaX = e.clientX - startX.current; const deltaPercent = (deltaX / containerWidth) * 100; // 计算新的完全产权占比,限制边界:不能小于0,不能占用裸产权+用益物权的全部比例 const newFullPercent = Math.max(0, Math.min(100 - barePercent - usufructPercent, startFullPercent.current + deltaPercent)); // 裸产权占比随完全产权的变化反向调整 const newBarePercent = barePercent + (startFullPercent.current - newFullPercent); setFullPercent(newFullPercent); setBarePercent(newBarePercent); }, [barePercent, usufructPercent]); // 处理第二个拖动条(裸产权与用益物权之间)的拖动开始 const handleSecondDragStart = useCallback((e) => { isDraggingSecond.current = true; startX.current = e.clientX; startBarePercent.current = barePercent; document.addEventListener('mousemove', handleSecondDragMove); document.addEventListener('mouseup', handleDragEnd); e.preventDefault(); }, [barePercent]); // 第二个拖动条的拖动过程 const handleSecondDragMove = useCallback((e) => { if (!isDraggingSecond.current) return; const containerWidth = containerRef.current.offsetWidth; const deltaX = e.clientX - startX.current; const deltaPercent = (deltaX / containerWidth) * 100; // 计算新的裸产权占比,限制边界:不能小于0,不能超过完全产权之外的剩余比例 const newBarePercent = Math.max(0, Math.min(100 - fullPercent, startBarePercent.current + deltaPercent)); setBarePercent(newBarePercent); }, [fullPercent]); // 拖动结束,清理全局事件监听 const handleDragEnd = useCallback(() => { isDraggingFirst.current = false; isDraggingSecond.current = false; document.removeEventListener('mousemove', handleFirstDragMove); document.removeEventListener('mousemove', handleSecondDragMove); document.removeEventListener('mouseup', handleDragEnd); }, [handleFirstDragMove, handleSecondDragMove]); return ( <div ref={containerRef} style={{ position: 'relative', width: '100%', height: '80px', display: 'flex', border: '1px solid #eee', margin: '20px 0' }} > {/* 完全产权区块 */} <div style={{ width: `${fullPercent}%`, height: '100%', backgroundColor: '#f0f8ff', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'width 0.1s ease' }}> 完全产权 {fullPercent.toFixed(1)}% </div> {/* 第一个拖动条 */} <div style={{ position: 'absolute', left: `${fullPercent}%`, top: 0, bottom: 0, width: '2px', backgroundColor: '#1890ff', cursor: 'ew-resize', zIndex: 10, opacity: isDraggingFirst.current ? 0.8 : 0.5 }} onMouseDown={handleFirstDragStart} /> {/* 裸产权区块 */} <div style={{ width: `${barePercent}%`, height: '100%', backgroundColor: '#e6f7ff', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'width 0.1s ease' }}> 裸产权 {barePercent.toFixed(1)}% </div> {/* 第二个拖动条 */} <div style={{ position: 'absolute', left: `${fullPercent + barePercent}%`, top: 0, bottom: 0, width: '2px', backgroundColor: '#1890ff', cursor: 'ew-resize', zIndex: 10, opacity: isDraggingSecond.current ? 0.8 : 0.5 }} onMouseDown={handleSecondDragStart} /> {/* 用益物权区块 */} <div style={{ width: `${usufructPercent}%`, height: '100%', backgroundColor: '#bae7ff', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'width 0.1s ease' }}> 用益物权 {usufructPercent.toFixed(1)}% </div> </div> ); }; export default PropertyRatio;
额外优化建议
- 触摸支持:添加
touchstart/touchmove/touchend事件,获取触摸位置时使用e.touches[0].clientX,适配移动端。 - 动画平滑:给区块宽度添加
transition过渡效果,提升拖动时的视觉流畅度。 - 精度控制:用
toFixed(1)限制百分比小数位数,避免显示过多小数影响体验。 - 窗口 resize 适配:如果需要响应窗口宽度变化,可监听
resize事件,但本方案使用百分比宽度,CSS会自动适配,一般无需额外处理。
内容的提问来源于stack exchange,提问作者tilly
相关产品推荐
相关产品推荐

