實現要點
- 頁面布局
- 監聽鼠標滾動事件
- 計算滾動位置進行對齊
實現步驟
頁面布局
- 父元素采用
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)