用react实现一个简单的scrollView组件

 更新时间:2023年07月10日 10:00:07   作者:前端小张同学  
这篇文章主要给大家介绍一下如何用 react 实现一个简单的 scrollView组件,文中有详细的代码示例,具有一定的参考价值,需要的朋友可以参考下

效果

我们先看一下效果,大概就是希望点击左边按钮 或者右边按钮将元素以 每一个 子选项卡长度单位进行精准偏移。

设计考虑的问题

在这之前,大家不妨思考一下这个需求给到你,你应该怎么去设计这个东西?

1 : 父盒子的长度多少 ? 如何控制多出的元素隐藏?

2 :我们应该如何进行偏移 ? 用定位还是位移 ? 他们有什么区别?如何保证偏移量一点不差?

3 :每次偏移的量是多少?如何处理边界情况?

好,带着以下几个问题,我们一起来思考一下这个组件,应该如何封装。

1: 结构

首先 , 结构如下图,我们有一个 父盒子(content-wapper-hidden) ,还有一个子盒子内嵌在 父盒子中,父盒子负责元素的溢出隐藏,固定宽度,子盒子负责渲染,和滚动。

结构搭建

scrollView.tsx

import { ReactNode, memo, useRef } from 'react'
import { ScrollViewWapper } from './style'
interface ScrollViewProps {
  children: ReactNode
}
const ScrollView = memo((
  {
    children
  }: ScrollViewProps
) => {
  const contentRef = useRef<HTMLDivElement>(null)
  return (
    <ScrollViewWapper>
      <div className='leftIcon'>左边</div>
      <div className='content-wapper-hidden'>
        <div className='render-content' ref={contentRef}>
          {children}
        </div>
      </div>
      <div className='rightIcon'>右边</div>
    </ScrollViewWapper>
  )
})
export default ScrollView

逻辑处理

1 : 接下来 我们分别给两个 按钮绑定事件 , 并且 在初始化的时候 去计算 最大可偏移的值,

import { ReactNode, memo, useEffect, useRef, useState } from 'react'
import { ScrollViewWapper } from './style'
interface ScrollViewProps {
  children: ReactNode
}
const ScrollView = memo((
  {
    children
  }: ScrollViewProps
) => {
  const contentRef = useRef<HTMLDivElement>(null)
  const [maxoffset, setMaxOffset] = useState(0) // 两者最大的偏移量 可滚动距离
  const [currentOffsetIndex, setCurrentOffsetIndex] = useState(0) // 当前滚动元素的索引
  const handelIconClick = (isRoght: boolean) => {
  // 事件处理函数 
  }
  function getScrollOffset() {
    const scrollWidth = contentRef.current!.scrollWidth
    const clientWidth = contentRef.current!.clientWidth
    setMaxOffset(scrollWidth - clientWidth) // 计算最大偏移量
  }
  useEffect(() => {
    getScrollOffset()
  }, [])
  return (
    <ScrollViewWapper>
      <div className='leftIcon' onClick={() => handelIconClick(false)}>左边</div>
      <div className='content-wapper-hidden'>
        <div className='render-content' ref={contentRef}>
          {children}
        </div>
      </div>
      <div className='rightIcon' onClick={() => handelIconClick(true)}>右边</div>
    </ScrollViewWapper>
  )
})
export default ScrollView

在这里我们通过 ref 绑定了 一个 内容元素 ,然后 初始化的时候 ,我们去计算了元素最大可滚动的距离, 然后当我们点击了 按钮时 我们获取了 contentRef 下所有 绑定 类名为 item的元素,点击时判断 是否为 右侧 如果是 右侧 点击 则 索引 + 1 否则 索引 -1 ,然后做边界处理,依次获得item的 offsetLeft ,注意 offsetleft 是相对于 父级元素的距离,然后 将元素 contentRef 进行 translate 位移

import { ReactNode, memo, useEffect, useRef, useState } from 'react'
import { ScrollViewWapper } from './style'
interface ScrollViewProps {
  children: ReactNode
}
const ScrollView = memo((
  {
    children
  }: ScrollViewProps
) => {
  const contentRef = useRef<HTMLDivElement>(null)
  const [maxoffset, setMaxOffset] = useState(0) // 两者最大的偏移量 可滚动距离
  const [currentOffsetIndex, setCurrentOffsetIndex] = useState(0)
  const [isContinueScroll, setisContinueScroll] = useState(true)
  const handelIconClick = (isRight: boolean) => {
    const newIndex = isRight ? currentOffsetIndex + 1 : currentOffsetIndex - 1
    if (newIndex < 0 || (!isContinueScroll && isRight)) return // 边界处理
    const TabAllList = getAllElements('item', 'class') // 获取conntentRef 下面的 所有 item 子节点
    const TabItem = TabAllList[newIndex] as HTMLDivElement // 获取下一个 准备滚动元素
    const TabItemOffsetLeft = TabItem.offsetLeft
    contentRef.current!.style.transform = `translateX(${-TabItemOffsetLeft}px)`
    setisContinueScroll(maxoffset > TabItemOffsetLeft) // 是否能继续滚动 如果 你的 offsetLeft 都
    // 比我可滚动距离大了 , 则肯定是不能滚动的
    setCurrentOffsetIndex(newIndex)
  }
  const getAllElements = (querySelectorName: string, type: 'id' | 'class' | 'el' = 'class') => {
    let seletorName = null
    if (type === 'id') { // 对选择器 做不同类型处理
      seletorName = `#${querySelectorName.replace(/\^#/, '')}`
    } else if (type == 'class') {
      seletorName = `.${querySelectorName.replace(/\^./, '')}`
    } else {
      seletorName = `${querySelectorName.replace(/\^(.|#)/, '')}`
    }
    return contentRef.current!.querySelectorAll(seletorName)
  }
  function getScrollOffset() {
    const scrollWidth = contentRef.current!.scrollWidth
    const clientWidth = contentRef.current!.clientWidth
    setMaxOffset(scrollWidth - clientWidth)
  }
  useEffect(() => {
    getScrollOffset()
  }, [])
  return (
    <ScrollViewWapper>
      <div className='leftIcon' onClick={() => handelIconClick(false)}>左边</div>
      <div className='content-wapper-hidden'>
        <div className='render-content' ref={contentRef}>
          {children}
        </div>
      </div>
      <div className='rightIcon' onClick={() => handelIconClick(true)}>右边</div>
    </ScrollViewWapper>
  )
})
export default ScrollView

外部使用ScrollView 组件

import classNames from 'classnames';
import { memo, useState } from 'react';
import ScrollView from './components';
const Login = memo(() => {
  const [list, setList] = useState(['YYDS', '易烊千玺', '李易峰', '鸡哥', '古巨基', '罗志祥', '肖站', '彭于晏'])
  const [currentIndex, setCurrentIndex] = useState(0)
  return (
    <div id="danmu-container">
      <ScrollView>
        {
          list.map((item, index) => {
            return (
              <div className='item' key={item} onClick={() => setCurrentIndex(index)}>
                <div className={classNames('tab-item', currentIndex === index ? 'active' : '')}>{item}</div>
              </div>
            )
          })
        }
      </ScrollView>
    </div>
  )
})
export default Login 

ok 到这里 scrollView 组件就简单的封装完成了 , 当然你还可以集成 点击某个选项时 再进行偏移也是可以的,额可以拓展一下。

问题

为什么 用 tranform 而不是定位 ?

答案很简单 : 定位移动的回引发视图重绘,而transform 不会触发重回,出于这一点可以在性能上做优化,当然 掘友在上一张 弹幕的文章中也讲到 这一点,谢谢大家

总结

总结下来,这个组件 我们主要做的就是它的transform ,当然还有更多的功能大家可以拓展一下,如果你觉得还有哪些可以补充的,欢迎评论区留言。

以上就是用react实现一个简单的scrollView组件的详细内容,更多关于react实现scrollView组件的资料请关注脚本之家其它相关文章!

相关文章

  • React中引入less、less-loader问题

    React中引入less、less-loader问题

    这篇文章主要介绍了React中引入less、less-loader问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-01-01
  • 示例详解react中useState的用法

    示例详解react中useState的用法

    useState 通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state,接下来通过一个示例来看看怎么使用 useState吧
    2021-06-06
  • create-react-app全家桶router mobx全局安装配置

    create-react-app全家桶router mobx全局安装配置

    这篇文章主要为大家介绍了create-react-app全家桶router mobx全局安装配置,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • ReactNative支付密码输入框实现详解

    ReactNative支付密码输入框实现详解

    这篇文章主要为大家介绍了ReactNative支付密码输入框实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • JavaScript中的useRef 和 useState介绍

    JavaScript中的useRef 和 useState介绍

    这篇文章主要给大家分享的是 JavaScript中的useRef 和 useState介绍,下列文章,我们将学习 useRef 和 useState hook是什么,它们的区别以及何时使用哪个。 这篇文章中的代码示例将仅涉及功能组件,但是大多数差异和用途涵盖了类和功能组件,需要的朋友可以参考一下
    2021-11-11
  • react中JSX的注意点详解

    react中JSX的注意点详解

    这篇文章主要为大家详细介绍了react中JSX的注意点,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • React配置多个代理实现数据请求返回问题

    React配置多个代理实现数据请求返回问题

    这篇文章主要介绍了React之配置多个代理实现数据请求返回问题,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • 使用Node搭建reactSSR服务端渲染架构

    使用Node搭建reactSSR服务端渲染架构

    这篇文章主要介绍了使用Node搭建reactSSR服务端渲染架构,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-08-08
  • react实现浏览器自动刷新的示例代码

    react实现浏览器自动刷新的示例代码

    这篇文章主要介绍了react实现浏览器自动刷新的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-04-04
  • 详解React 父组件和子组件的数据传输

    详解React 父组件和子组件的数据传输

    这篇文章主要介绍了React 父组件和子组件的数据传输的相关资料,帮助大家更好的理解和学习使用React,感兴趣的朋友可以了解下
    2021-04-04

最新评论