li1234yun

实现要点

  • 页面布局
  • 监听鼠标滚动事件
  • 计算滚动位置进行对齐

实现步骤

页面布局

  • 父元素采用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)

分类:

技术点:

相关文章: