原生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
相关产品推荐
相关产品推荐

