ps:滑动条款式间接搬运的下方
https://www.chaos.com/blog/ho...
组件实现局部重点:
- visibility: "hidden"一份左半的图片兜底占位,左右两半图片都相对定位。
- 配置滑动条的鼠标下按、抬起事件,并在鼠标下按时监听mousemove事件。
- 为了实现“即便鼠标挪动到滚动条、浏览器外、浏览器内的f12控制台处松开”也能勾销鼠标拖拽————事件监听间接配在了document上,而不是document.body。
- 图片缩放的时候,按比例变动鼠标程度挪动量moveX,实现绝对静止滑动条地位。
- 为了能拿到正确的鼠标挪动值,“上一次鼠标x地位”cursorX没有存成state,仅仅是一个一般的全局变量。
源码
.tsx
import React, { useState, useEffect } from "react";import "./styles.less";export default function App() { return ( <div className="container"> 测试拖拽组件 <SwiperWindow leftSrc="//z3.ax1x.com/2021/09/24/407zfs.jpg" rightSrc="//s4.ax1x.com/2021/12/29/T62gcF.jpg" /> </div> );}const SwiperWindow = ({ leftSrc = "", rightSrc = "" }) => { let cursorX = 0; const [boxState, setBoxState] = useState({ boxW: 0, boxH: 0 }); const [sliderW, setSliderW] = useState(40); // 滑动条的宽度 const INITLEFT = -(sliderW / 2); const [sliderLeft, setSliderLeft] = useState(INITLEFT); const [sliderMax, setSliderMax] = useState(0); const [moveX, setMoveX] = useState(0); const [scale, setScale] = useState(1); useEffect(() => { setSliderW(document.querySelector(".slider")?.clientWidth || 40); setSize(true); return () => { document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); }; }, []); useEffect(() => { if (boxState.boxW) { setSize(false); } }, [document.querySelector(".scanBox")?.clientWidth, boxState.boxW]); const setSize = (init: boolean = false) => { let new_boxW = document.querySelector(".scanBox")?.clientWidth; let new_boxH = document.querySelector(".scanBox")?.clientHeight; const Half = sliderW / 2; if (new_boxW) { const new_sliderMax = new_boxW - Half; setSliderMax(new_sliderMax); if (init) { setSliderLeft(INITLEFT + new_boxW / 2); setMoveX(new_boxW / 2); setScale(new_boxW / 2 / new_boxW); } else if (moveX) { let new_sliderLeft = INITLEFT + new_boxW * scale; const Half = sliderW / 2; const MIN = -Half; const MAX = new_sliderMax; if (new_sliderLeft <= MIN) { new_sliderLeft = MIN; } if (new_sliderLeft >= MAX) { new_sliderLeft = MAX; } setSliderLeft(new_sliderLeft); } setBoxState( Object.assign({ boxW: new_boxW, boxH: new_boxH }) ); } }; const onMouseDown = (e: any) => { let new_cursorX = e.clientX; cursorX = new_cursorX; document.addEventListener("mousemove", onMouseMove); document.addEventListener("mouseup", onMouseUp); }; const onMouseMove = (e: any) => { // 鼠标挪动间隔 let moveX = e.clientX - cursorX; setMoveX(moveX); let new_sliderLeft = sliderLeft + moveX; setScale((new_sliderLeft - INITLEFT) / boxState.boxW); const Half = sliderW / 2; const MIN = -Half; const MAX = sliderMax; if (new_sliderLeft <= MIN) { new_sliderLeft = MIN; } if (new_sliderLeft >= MAX) { new_sliderLeft = MAX; } setSliderLeft(new_sliderLeft); }; // 当鼠标弹起触发事件 const onMouseUp = (e: any) => { cursorX = e.clientX; document.removeEventListener("mousemove", onMouseMove); document.removeEventListener("mouseup", onMouseUp); }; const { boxW, boxH } = boxState; return ( <div className="scanBox noSelect"> <div className="left noSelect" style={{ width: sliderLeft + sliderW / 2 }} > <img alt="after" src={leftSrc} className="leftPic" style={{ width: boxW, height: "auto" }} /> </div> {/* 滑动条 */} <div onMouseDown={(e) => onMouseDown(e)} onMouseUp={(e) => onMouseUp(e)} className="slider" style={{ left: sliderLeft }} > <div className="line-top"></div> <div className="round"> <div className="arrow-left"></div> <div className="arrow-right"></div> </div> <div className="line-bottom"></div> </div> <div className="right noSelect"> <img alt="before" src={rightSrc} className="rightPic" style={{ height: "auto" }} /> </div> <img alt="after" src={leftSrc} className="leftPic" style={{ width: boxW, height: "auto", visibility: "hidden" }} /> </div> );};
.less
.container { max-width: 1200px; margin: auto; width: 100%; height: auto;}.noSelect { user-select: none;}.scanBox { position: relative; width: inherit; overflow: hidden;}.scanBox { .slider { align-items: center; cursor: ew-resize; display: flex; flex-direction: column; height: 100%; justify-content: center; left: 268.768px; position: absolute; top: 0px; width: 40px; z-index: 5; .line-top { background: #ffffff; box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); flex: 0 1 auto; height: 100%; width: 2px; } .line-bottom { background: #ffffff; box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); flex: 0 1 auto; height: 100%; width: 2px; } .round { align-items: center; border: 2px solid #ffffff; border-radius: 100%; box-shadow: 0px 3px 1px -2px rgb(0 0 0 / 20%), 0px 2px 2px 0px rgb(0 0 0 / 14%), 0px 1px 5px 0px rgb(0 0 0 / 12%); box-sizing: border-box; display: flex; flex: 1 0 auto; height: 40px; justify-content: center; width: 40px; transform: none; .arrow-left { border: inset 6px rgba(0, 0, 0, 0); border-right: 6px solid #ffffff; height: 0px; margin-left: -10px; margin-right: 10px; width: 0px; } .arrow-right { border: inset 6px rgba(0, 0, 0, 0); border-left: 6px solid #ffffff; height: 0px; margin-right: -10px; width: 0px; } } } .left { position: absolute; top: 0; left: 0; z-index: 2; width: 100%; height: 100%; overflow: hidden; .leftPic { width: 100%; height: auto; -webkit-user-drag: none; //不可拖动 } } .right { position: absolute; top: 0; left: 0px; z-index: 1; width: 100%; height: 100%; overflow: hidden; .rightPic { width: 100%; height: auto; -webkit-user-drag: none; //不可拖动 } }}