React 实现鼠标水平滚动组件
实现要点
- 页面布局
- 监听鼠标滚动事件
- 计算滚动位置进行对齐
实现步骤
页面布局
- 父元素采用
flex
布局且设置flex-wrap: nowrap
使其子元素可以完全展开 - 子元素设置
flex-shrink: 0
使其能够不进行自适应缩小
事件监听
- 通过调用
event.preventDefault()
阻止浏览器默认行为 - 使用
useRef()
获取父元素的DOM元素,使用.current
获取dom对象进行操作 - 设置父元素的
wheel
鼠标滚动监听事件,并进行对应的计算
注意事项
- 使用react
onWheel
事件进行阻止默认行为无效,且会提示报错,所以使用ref获取dom元素代替 - react 事件是合成事件且不持久,不可异步传入
元素滚动
-
元素可以通过scrollTo()方法进行滚动
-
Tips:
- offsetWidth/offsetHeight 获取元素宽高
- scrollLeft/Top 获取偏移位置
- scrollWidth 获取滚动宽度
参考代码
import { createStyles, withStyles } from '@material-ui/core/styles'
import { SitePropsType } from 'components/base/Site'
import { useEffect, useRef } from 'react'
const styles = createStyles({
root: {
overflowX: 'auto',
},
container: {
display: 'flex',
flexWrap: 'nowrap',
overflowX: 'auto',
},
item: {
height: '300px',
width: '100%',
backgroundColor: '#f0f0f0',
border: '1px solid #333333',
flexShrink: 0,
// '&:hover': {
// cursor: 'pointer',
// },
},
indicator: {},
})
interface SiteSwiperProps {
classes?: {
root: string
container: string
item: string
indicator: string
}
sites: SitePropsType[]
row?: number
}
/**
* 计算滚动位置
* @param currentScrollLeft
* @param scrollElWith
*/
const computeScroll = (
currentScrollLeft: number,
scrollElWith: number
): number => {
// 判断滚动偏移是否满足滚动要求
console.log('current scroll left:', currentScrollLeft)
const index = Math.round(currentScrollLeft / scrollElWith)
return scrollElWith * index
}
function SiteSwiper({ classes, sites, row = 3 }: SiteSwiperProps): JSX.Element {
const containerRef = useRef(null)
const timer = useRef(null)
useEffect(() => {
console.log('current ref:', containerRef)
containerRef.current.addEventListener('wheel', (e) => {
console.log('mouse wheel event:', e)
// 阻止原生滚动事件
e.preventDefault()
// 获取滚动位置
let scrollLeft = containerRef.current.scrollLeft
const scrollTotalWidth = containerRef.current.scrollWidth
const scrollItemWidth = containerRef.current.offsetWidth
// 获取容器的宽度
console.log(
'current container:',
containerRef.current.offsetWidth,
e.deltaY
)
// 即时水平滚动偏移值
const bufferOffset = 70
const scrollBehavior = 'smooth'
let offset = scrollLeft + e.deltaY * 4 // 放大偏移倍数
if (offset >= scrollTotalWidth - scrollItemWidth + bufferOffset) {
// 到达最后元素
offset = offset - scrollTotalWidth - bufferOffset
// scrollBehavior = 'auto'
} else if (offset + bufferOffset < 0) {
// 达到第一元素
offset = scrollTotalWidth + offset - bufferOffset
// scrollBehavior = 'auto'
} else {
// 其它情况
}
console.log('offset y at time:', scrollLeft, offset)
containerRef.current.scrollTo({
top: 0,
left: offset,
behavior: scrollBehavior,
})
// 防抖
if (timer.current) {
clearTimeout(timer.current)
}
timer.current = setTimeout(() => {
// 计算滚动最后的位置进行位置矫正
console.log('TIME OUT: starting position correct...')
// 计算是否滚动
scrollLeft = computeScroll(offset, scrollItemWidth)
containerRef.current.scrollTo({
top: 0,
left: scrollLeft,
behavior: 'smooth',
})
}, 700)
})
})
return (
<div className={classes.root} id="swiper-container">
{/* Content */}
<div
className={classes.container}
// onScroll={handleMouseScroll}
// onMouseOver={handleMouseOver}
// onWheel={handleWheel}
ref={containerRef}
>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((item) => (
<div className={`${classes.item} swiper-item`} key={item}>
{item}
</div>
))}
</div>
{/* Indicator */}
<div className={classes.indicator}></div>
</div>
)
}
export default withStyles(styles)(SiteSwiper)