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; //不可拖动
}
}
}