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

React实现带可拖动边框的三类产权占比调整区块方案咨询

实现React水平拖动调整产权占比的优化方案

针对你遇到的react-draggable卡顿问题,推荐直接基于原生鼠标/触摸事件实现拖动逻辑——这种方案更轻量,能精准控制状态更新时机,避免第三方库的额外封装开销。以下是具体实现思路和代码示例:

核心思路

  1. 布局结构:用一个父容器包裹三个产权区块,在区块之间放置蓝色拖动分隔线(绝对定位),通过百分比控制各区块宽度。
  2. 状态管理:维护完全产权、裸产权的占比状态,用益物权占比通过100 - 完全产权占比 - 裸产权占比自动计算,确保总和始终为100%。
  3. 拖动逻辑:
    • 给拖动条绑定mousedown/touchstart事件,记录初始鼠标位置和当前占比状态。
    • 在全局mousemove/touchmove事件中计算拖动距离,转换为百分比变化,更新状态并做边界限制(比如占比不能为负、不能超过剩余可用比例)。
    • 绑定mouseup/touchend事件,结束拖动并移除全局事件监听,避免内存泄漏。
  4. 性能优化:用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;

额外优化建议

  1. 触摸支持:添加touchstart/touchmove/touchend事件,获取触摸位置时使用e.touches[0].clientX,适配移动端。
  2. 动画平滑:给区块宽度添加transition过渡效果,提升拖动时的视觉流畅度。
  3. 精度控制:用toFixed(1)限制百分比小数位数,避免显示过多小数影响体验。
  4. 窗口 resize 适配:如果需要响应窗口宽度变化,可监听resize事件,但本方案使用百分比宽度,CSS会自动适配,一般无需额外处理。

内容的提问来源于stack exchange,提问作者tilly

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 00:47:42