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

原生JS双滑块组件转React的实现建议及失真问题求助

双滑块组件的React改造与失真问题修复

一、原生JS逻辑转为React实现的核心技巧

1. 用React状态管理替代全局变量

把原生代码里的active、滑块位置、图层宽度等可变值,替换为React的useState状态,让React自动处理视图更新,避免直接操作DOM。

2. 使用useRef获取DOM元素

摒弃document.querySelector,用useRef绑定需要操作的DOM节点(如容器、图层、滑块),既符合React组件模型,也能提升性能。

3. 合理使用useEffect绑定/清理全局事件

全局的mouseup、mousemove、touchend这类事件,需在组件挂载时绑定,卸载时清理,防止内存泄漏。

4. 状态驱动样式更新

不要直接用element.style修改样式,而是将宽度、位置计算后存入状态,通过JSX的style属性动态绑定,让React统一管理视图渲染。

二、结尾失真问题的解决思路

失真通常源于滑块拖动到边界时图层宽度超出容器范围,或图片显示方式不合理。解决方案:

  • 严格限制图层宽度取值范围,确保不小于0且不超过容器宽度
  • 用object-fit: cover替代单纯设置height:100%,保证图片拉伸时不变形
  • 修复初始值设置逻辑,避免初始状态下图层宽度冲突

完整优化后的React代码

import { useState, useRef, useEffect } from 'react';
import iootz2 from '../images/ioootz-2.jpg';
import vriootz from '../images/vr-iootz.jpg';
import ioootz3 from '../images/ioootz-3.jpg';

const HorizoSlider = () => {
  // 状态管理
  const [active, setActive] = useState(false);
  const [middleWidth, setMiddleWidth] = useState(460);
  const [topWidth, setTopWidth] = useState(230);
  
  // DOM Refs
  const wrapperRef = useRef(null);
  const middleRef = useRef(null);
  const topRef = useRef(null);
  const scrollerMiddleRef = useRef(null);
  const scrollerTopRef = useRef(null);

  // 计算滑块位置的辅助函数
  const getScrollerLeft = (width) => width - 25;

  // 核心滚动逻辑
  const scrollIt = (x) => {
    if (!wrapperRef.current) return;
    const wrapperWidth = wrapperRef.current.offsetWidth;
    // 严格限制x的范围,防止超出边界
    const transform = Math.max(0, Math.min(x, wrapperWidth));

    if (active === "middle") {
      setMiddleWidth(transform);
      // 处理顶部滑块不超过中间滑块的逻辑
      if (getScrollerLeft(topWidth) > getScrollerLeft(transform) - 5) {
        const newTopWidth = Math.max(0, transform - 5);
        setTopWidth(newTopWidth);
      }
    }

    if (active === "top") {
      setTopWidth(transform);
      // 处理中间滑块不被顶部滑块超过的逻辑
      if (getScrollerLeft(transform) > getScrollerLeft(middleWidth) - 5) {
        const newMiddleWidth = Math.min(wrapperWidth, transform + 5);
        setMiddleWidth(newMiddleWidth);
      }
    }
  };

  // 鼠标事件绑定
  useEffect(() => {
    const handleMouseUp = () => {
      setActive(false);
    };

    const handleMouseMove = (e) => {
      if (!active || !wrapperRef.current) return;
      const wrapperLeft = wrapperRef.current.getBoundingClientRect().left;
      const x = e.pageX - wrapperLeft;
      scrollIt(x);
    };

    document.body.addEventListener('mouseup', handleMouseUp);
    document.body.addEventListener('mousemove', handleMouseMove);
    document.body.addEventListener('mouseleave', handleMouseUp);

    return () => {
      document.body.removeEventListener('mouseup', handleMouseUp);
      document.body.removeEventListener('mousemove', handleMouseMove);
      document.body.removeEventListener('mouseleave', handleMouseUp);
    };
  }, [active]);

  // 触摸事件绑定
  useEffect(() => {
    const handleTouchEnd = () => {
      setActive(false);
    };

    const handleTouchMove = (e) => {
      if (!active || !wrapperRef.current) return;
      e.preventDefault();
      const wrapperLeft = wrapperRef.current.getBoundingClientRect().left;
      const x = e.touches[0].pageX - wrapperLeft;
      scrollIt(x);
    };

    if (wrapperRef.current) {
      wrapperRef.current.addEventListener('touchmove', handleTouchMove);
    }
    document.body.addEventListener('touchend', handleTouchEnd);
    document.body.addEventListener('touchcancel', handleTouchEnd);

    return () => {
      if (wrapperRef.current) {
        wrapperRef.current.removeEventListener('touchmove', handleTouchMove);
      }
      document.body.removeEventListener('touchend', handleTouchEnd);
      document.body.removeEventListener('touchcancel', handleTouchEnd);
    };
  }, [active]);

  return (
    <section className="2-section">
      <div id="page">
        <div className="wrapper" ref={wrapperRef}>
          <div className="bottom">
            <img 
              className="ioootz-2" 
              src={iootz2} 
              draggable="false" 
              alt="main-images"
              style={{ objectFit: 'cover' }}
            />
          </div>
          <div 
            className="middle" 
            ref={middleRef}
            style={{ width: `${middleWidth}px` }}
          >
            <img 
              className="vr-iootz" 
              src={vriootz} 
              draggable="false" 
              alt="vr-iootz"
              style={{ objectFit: 'cover' }}
            />
          </div>
          <div 
            className={`scroller scroller-middle ${active === 'middle' ? 'scrolling' : ''}`}
            ref={scrollerMiddleRef}
            style={{ left: `${getScrollerLeft(middleWidth)}px` }}
            onMouseDown={() => setActive('middle')}
            onTouchStart={() => setActive('middle')}
          >
            <svg
              className="scroller__thumb"
              xmlns="http://www.w3.org/2000/svg"
              width="100"
              height="100"
              viewBox="0 0 100 100"
            >
              <polygon points="0 50 37 68 37 32 0 50" fill="rgb(24,24,62)" />
              <polygon points="100 50 64 32 64 68 100 50" fill="rgb(24,24,62)" />
            </svg>
          </div>
          <div 
            className="top" 
            ref={topRef}
            style={{ width: `${topWidth}px` }}
          >
            <img 
              className="ioootz" 
              src={iootz3} 
              draggable="false" 
              alt="ioootz"
              style={{ objectFit: 'cover' }}
            />
          </div>
          <div 
            className={`scroller scroller-top ${active === 'top' ? 'scrolling' : ''}`}
            ref={scrollerTopRef}
            style={{ left: `${getScrollerLeft(topWidth)}px` }}
            onMouseDown={() => setActive('top')}
            onTouchStart={() => setActive('top')}
          >
            <svg
              className="scroller__thumb"
              xmlns="http://www.w3.org/2000/svg"
              width="100"
              height="100"
              viewBox="0 0 100 100"
            >
              <polygon points="0 50 37 68 37 32 0 50" fill="rgb(24,24,62)" />
              <polygon points="100 50 64 32 64 68 100 50" fill="rgb(24,24,62)" />
            </svg>
          </div>
        </div>
      </div>
    </section>
  );
};

export default HorizoSlider;

调整后的CSS代码(修复失真相关)

.wrapper{
  width: 600px;
  height: 338px;
  max-height:100vh;
  position: absolute;
  left:1200px;
  top:290px;
  transform:translate3d(-50%,-50%,0);
  overflow:hidden;
  box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
}

.bottom,
.middle,
.top {
  width:100%;
  height:100%;
  background-repeat:no-repeat;
  background-color: white;
  background-size: cover;
  background-position: center;
  position: absolute;
  top:0;
  left:0;
  pointer-events:none;
  overflow: hidden;
}
/* 修改图片样式,防止拉伸失真 */
img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.bottom{
  background: #2196f3;
}
.middle {
  background:  #bbdefb;
}
.top{
  background:  #ffffff;
}

.scroller{
  width: 50px;
  height:50px;
  position: absolute;
  top:50%;
  transform:translateY(-50%);
  border-radius:50%;
  background-color: #fff;
  opacity:0.9;
  transition: opacity 0.12s ease-in-out;
  pointer-events:auto;
  cursor: pointer;
  box-shadow: 3.5px 0px 7px rgba(100, 100, 100, 0.2);
}
.scroller-middle{
  margin-top:25px;
}
.scroller-top{
  margin-top:-25px;
}

.scroller:hover{
  opacity:1;
}

.scrolling{
  pointer-events:none;
  opacity:1;
  z-index: 1;
}

.scroller__thumb{
  width:100%;
  height:100%;
  border-radius:50%;
  padding: 7px;
}

.scroller:before,
.scroller:after{
  content:" ";
  display: block;
  width: 7px;
  height: 9999px;
  position: absolute;
  left: 50%;
  margin-left: -3.5px;
  z-index: 30;
  transition:0.1s;
  box-shadow: 3.5px 0px 7px rgba(100, 100, 100, 0.2);
}
.scroller:before{
  top:49px;
}
.scroller:after{
  bottom:49px;
}

.scroller-middle>.scroller__thumb{
  border: 5px solid rgb(24,24,62);
}
.scroller-middle:before,
.scroller-middle:after{
  background: rgb(24,24,62);
}

.scroller-top>.scroller__thumb{
  border: 5px solid rgb(24,24,62);
}
.scroller-top:before,
.scroller-top:after{
  background: rgb(24,24,62);
}

内容的提问来源于stack exchange,提问作者Kiki-ko

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 06:14:51