简单的元素滚动组件

1,383 阅读2分钟

前言

过长的元素内容一般采用换行、截断隐藏+省略号,但是类似公告、大屏上的内容必须完整的展示出来。这种情况下一般会让元素滚动起来,以便用户看到完整的信息。接下来带大家实现一种比较简单的方法。

思路

实现元素滚动的难点在于,无论使用何种方式都绕不开判定元素的实际宽度。 我们可以使用 transform: translateX可以绕过确定元素宽度的步骤,因为translateX可以接受百分比数值,如果设置 translateX(-100%) 就是意味着把整个元素向左移动整个元素本身宽度的距离;结合animation,可以使用循环滚动。

scroll.gif

布局

结构

一个父级容器 container,一个元素内容 content

import React, { useEffect, useRef, useState } from 'react'
import styles from './index.less'

export default function ScrollContainer(props: {
  children: React.ReactNode
}) {
  const containerRef = useRef<HTMLDivElement>(null)
  const contentRef = useRef<HTMLDivElement>(null)
  const [transform, setTransform] = useState(`translateX(0px)`)
  const [animationDuration, setAnimationDuration] = useState('10s')
  
  // 省略
  ......

  return (
    <div ref={containerRef} className={styles.container}>
      <div ref={contentRef} className={styles.content} style={{ transform, animationDuration }}>
        {children}
      </div>
    </div>
  )
}

样式

  • .container 需要确定展示区域的宽度 width,设置 overflow: hidden 隐藏超出的文本;
  • 同时因为.contentdiv,会自动继承父级元素的宽度,在这种情况下设定 translateX(-100%)自会向左移动父级元素宽度的距离,而不是元素本身长度,我们需要将 .content的宽度设置为内容的最大宽度 max-content,或者让.content通过浮动 float 或绝对定位 position: absolute来脱离文本流,不继承父级宽度, 让内容撑开宽度。

代码如下:

.container {
  width: 100%;
  color: #fff;
  overflow: hidden;
}

.content {
  white-space: nowrap;
  width: max-content;
  animation: scroll infinite linear;
}

动画

动画部分也是比较简单,使用 transform: translateX来设定.content的移动

@keyframes scroll {
  100% {
    transform: translateX(-100%);
  }
}

需要注意的点:

  • 动画初始位置,要让元素从右到左滚动,初始的位置向右偏移父元素的宽度
  • 动画执行时间,限定每秒滚动的距离为 speed,动画时间则为元素宽度 / speed
  // 每秒移动的距离
  const speed = 20

  useEffect(() => {
    // 设置滚动父级元素宽度、动画时间
    setTransform(`translateX(${containerRef?.current?.clientWidth ?? 0}px)`)
    setAnimationDuration(Math.floor((contentRef?.current?.clientWidth ?? 0) / speed) + 's')
  }, [children])

最后

以上实现是以横向滚动为例,同理,纵向滚动只要控制 translateY 即可。 完整代码如下:

import React, { useEffect, useRef, useState } from 'react'
import styles from './index.less'

export default function ScrollContainer(props: {
  children: React.ReactNode
}) {
  const { children } = props
  const containerRef = useRef<HTMLDivElement>(null)
  const contentRef = useRef<HTMLDivElement>(null)
  const [transform, setTransform] = useState(`translateX(0px)`)
  const [animationDuration, setAnimationDuration] = useState('10s')
  // 每秒移动的距离
  const speed = 20

  useEffect(() => {
    // 设置滚动父级元素宽度、动画时间
    setTransform(`translateX(${containerRef?.current?.clientWidth ?? 0}px)`)
    setAnimationDuration(Math.floor((contentRef?.current?.clientWidth ?? 0) / speed) + 's')
  }, [children])

  return (
    <div ref={containerRef} className={styles.container}>
      <div ref={contentRef} className={styles.content} style={{ transform, animationDuration }}>
        {children}
      </div>
    </div>
  )
}
.container {
  width: 100%;
  color: #fff;
  overflow: hidden;
}

.content {
  width: max-content;
  animation: scroll infinite linear;
}

@keyframes scroll {
  100% {
    transform: translateX(-100%);
  }
}