封装Xscroll
水平滚动方法一:基础版onwheel
- 利用onwheel事件的deltaY来直接控制scrollLeft的改变
- 缺点:各个设备的onwheel事件的触发deltaY是不一致的,浏览器,包括鼠标也可以自己设置,会导致滚动效果一般
html
<head>
<style>
.wrap {
position: relative;
margin: auto;
width: 400px;
height: 200px;
cursor: pointer;
border: 1px solid #000;
box-sizing: border-box;
}
.scroll {
width: 400px;
height: 200px;
border: 1px solid #f00;
box-sizing: border-box;
overflow-x: scroll;
overflow-y: hidden;
background: linear-gradient(rgba(122, 122, 50, .3), rgba(20, 200, 150, .3));
display: flex;
}
.item3 {
width: 200px;
height: 200px;
margin-right: 20px;
border: 1px solid green;
flex-shrink: 0;
}
</style>
</head>
<body>
<div class="wrap">
<div class="scroll">
<div class="item3">1</div>
<div class="item3">2</div>
<div class="item3">3</div>
<div class="item3">4</div>
<div class="item3">5</div>
<div class="item3">6</div>
<div class="item3">7</div>
<div class="item3">8</div>
<div class="item3">9</div>
<div class="item3">10</div>
</div>
</div>
<script>
const box = document.querySelector('.scroll')
box.addEventListener('wheel', (e) => {
e.currentTarget.scrollLeft += e.deltaY;
})
</script>
</body>水平滚动方法二:利用transform来将垂直滚动转换为水平滚动
- js代码
js
import React, { useEffect, useRef, useState } from 'react';
import style from './xscroll.module.css';
import { useDebounce } from '../../../utils/hooks.js';
const XScroll = React.forwardRef(
({ customStyle, children, scrollBar, scrollBarType, contentStyle, containerHeights, selfid = 'defalut' }, ref) => {
const [wrapperWidth, setWrapperWidth] = useState(0);
const [wrapperHeight, setWrapperHeight] = useState(0);
const [scrollbarPosition, setScrollbarPosition] = useState(0)
const [mouseDownFlag, setMoseDownFlag] = useState(false)
const [showBar, setShowBar] = useState(scrollBar)
const wrapperRef = useRef(null);
const showBarClearId = useRef(null);
useEffect(() => {
window.addEventListener('resize', debounceWheel);
return () => {
window.removeEventListener('resize', debounceWheel);
};
}, []);
useEffect(() => {
setShowBar(scrollBar)
}, [scrollBar])
const handleResize = () => {
if (wrapperRef.current) {
const { width, height } = wrapperRef.current.getBoundingClientRect();
setWrapperWidth(width);
setWrapperHeight(height);
}
};
useEffect(() => {
debounceWheel();
}, [wrapperRef]);
const handlerScroll = (e) => {
if (mouseDownFlag) return
if (scrollBarType === 'scroll') {
setShowBar(true)
clearTimeout(showBarClearId.current)
showBarClearId.current = setTimeout(() => {
setShowBar(false)
}, 1500);
}
const MaxScrollLength = e.target.scrollHeight - e.target.clientHeight
const assignAbleLength = e.target.clientHeight - 100
setScrollbarPosition(e.target.scrollTop / MaxScrollLength * assignAbleLength)
}
const debounceWheel = useDebounce(handleResize, 300);
const handlerMoveStart = (event) => { //实现滑动条手动滑动
setMoseDownFlag(true)
if (event.button !== 0) return;
if (scrollBarType === 'scroll') {
clearTimeout(showBarClearId.current)
}
const startX = event.clientX;
const scrollBox = document.querySelector(`#scrollBox${selfid}`)
const MaxScrollLength = scrollBox.scrollHeight - scrollBox.clientHeight
const assignAbleLength = scrollBox.clientHeight - 100
const maxDiffX = wrapperWidth - 100
document.onmousemove = function (e) {
const endX = e.clientX;
let diffX = endX - startX + scrollbarPosition;
if (diffX < 0) {
diffX = 0
}
if (diffX > maxDiffX) {
diffX = maxDiffX
}
setScrollbarPosition(diffX)
scrollBox.scrollTo({ top: diffX / assignAbleLength * MaxScrollLength })
}
document.onmouseup = function () {
setMoseDownFlag(false)
if (scrollBarType === 'scroll') {
showBarClearId.current = setTimeout(() => {
setShowBar(false)
}, 2000);
}
document.onmousemove = null;
document.onmouseup = null;
}
if (event.preventDefault) {
event.preventDefault();
}
}
return (
<div
style={customStyle}
className={style.wrapper}
ref={wrapperRef}>
{/* 关键点:
1.动态获取父容器的宽高
2.设置子容器的宽度为父容器的高度,高度为父元素的宽度
3.旋转子容器,注意旋转中心如何改变
*/}
<div
onScroll={handlerScroll}
className={`${style.scrollBox} ${showBar ? style.showBar : style.hideBar
}`}
//天坑,generate和prod页面刚生成的时候是覆盖的,所以直接拿id拿的是generate中的Xscroll,导致滑动失效
id={"scrollBox" + selfid}
ref={ref}
style={
width: containerHeights ? containerHeights + 7 : wrapperHeight,
height: wrapperWidth,
transform: `translateY(${containerHeights ? containerHeights : wrapperHeight}px) rotate(-90deg)`,
}
>
<div
className={style.content}
style={
contentStyle === 'widthAuto'
? {
width: wrapperWidth,
height: 'auto',
left: containerHeights ? containerHeights : '100%'
}
: {
width: '100%',
height: 'fit-content',
left: containerHeights ? containerHeights : '100%'
}
}
>
{wrapperRef.current && children}
</div>
</div>
<div
onMouseDown={handlerMoveStart}
className={style.scrollBar}
style={showBar ? { left: scrollbarPosition } : { display: 'none' }}>
</div>
</div>
);
},
);
XScroll.displayName = "XScroll"
export { XScroll };- css代码
css
.wrapper {
position: relative;
width: 100%;
height: 100%;
cursor: pointer;
box-sizing: border-box;
}
.scrollBox {
position: relative;
box-sizing: border-box;
transform-origin: 0 0;
overflow: auto;
/* 隐藏水平滚动轴 显示竖直滚动轴 */
overflow-x: hidden;
direction: rtl;
}
.content {
position: absolute;
/* left: 70%; */
/* 宽度自动,高度和最顶层的宽度一致 */
box-sizing: border-box;
transform-origin: 0 0;
transform:rotate(90deg);
display: flex;
direction: ltr;
}
.scrollBar{
position: absolute;
bottom: -10px;
background-color:#c5c5c5 ;
width: 100px;
height: 7px;
border-radius: 5px;
}方法三:onwheel进阶版
- 利用onwheel + transition 模拟水平滚动
- js代码实现
js
const XsctollEasy = ({ children }) => {
const stylizedContent = useRef(null)
const xscollContent = useRef(null)
const [deltaY, setDeltaY] = useState(0);
const handlerWheel = (e) => {
const speed = 40 //自己控制滑动速度
const pWidth = stylizedContent.current?.clientWidth
const sWidth = xscollContent.current?.clientWidth
if (sWidth <= pWidth) return
if (e.deltaX > 0) { //deltaX兼容触摸板左右滑动实现水平滚动
if (-deltaY < sWidth - pWidth) {
setDeltaY(pre => pre - speed)
}
} else if (e.deltaX < 0) {
if (deltaY < 0) {
setDeltaY(pre => pre + speed)
}
} else if (e.deltaY > 0) {
if (-deltaY < sWidth - pWidth) {
setDeltaY(pre => pre - speed)
}
} else if (e.deltaY < 0) {
if (deltaY < 0) {
setDeltaY(pre => pre + speed)
}
}
}
return (
<div
className={style.stylizedContent}
ref={stylizedContent}
onWheel={handlerWheel}
>
<div
className={style.xscollContent}
ref={xscollContent}
style={
transform: `translateX(${deltaY}px)`
}>
{children}
</div>
</div>
)
}- css代码
css
.stylizedContent {
width: 100%;
clip-path: inset(0px 0px -100px 0px); /* 根据情况来控制裁剪*/
}
.xscollContent {
width: fit-content;
display: flex;
transition: transform 0.2s ease-out; /* 自己控制过渡效果 ease-out滑动后续会减速*/
gap: 8px;
}兼容平板等触摸设备的滑动
- 利用touch事件计算滑动量再派发onwhell事件
js
useEffect(() => {
const scrollBox = stylizedContent.current;
let startX;
let startScrollTop;
// 兼容触屏滑动
const touchStart = (event) => {
startX = event.touches[0].pageX;
startScrollTop = scrollBox.scrollTop;
};
const touchMove = (event) => {
const currentX = event.touches[0].pageX;
const diffX = startX - currentX;
scrollBox.scrollTop = startScrollTop + diffX;
// 创建并触发一个模拟的wheel事件
const wheelEvent = new WheelEvent('wheel', {
deltaX: diffX, // 模拟横向滑动的位移作为deltaX
deltaY: 0, // Y方向位移为0
clientX: currentX,
clientY: event.touches[0].clientY,
bubbles: true,
cancelable: true,
});
scrollBox.dispatchEvent(wheelEvent);
event.preventDefault();
};
const wheel = (event) => {
if (event.deltaX) {
event.preventDefault(); // 防止左右回退
scrollBox.scrollTop += event.deltaX;
}
};
scrollBox.addEventListener('touchstart', touchStart);
scrollBox.addEventListener('touchmove', touchMove);
scrollBox.addEventListener('wheel', wheel);
return () => {
scrollBox.removeEventListener('touchstart', touchStart);
scrollBox.removeEventListener('touchmove', touchMove);
scrollBox.removeEventListener('wheel', wheel);
};
}, []);