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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM