前言
过长的元素内容一般采用换行、截断隐藏+省略号,但是类似公告、大屏上的内容必须完整的展示出来。这种情况下一般会让元素滚动起来,以便用户看到完整的信息。接下来带大家实现一种比较简单的方法。
思路
实现元素滚动的难点在于,无论使用何种方式都绕不开判定元素的实际宽度。
我们可以使用 transform: translateX
可以绕过确定元素宽度的步骤,因为translateX
可以接受百分比数值,如果设置 translateX(-100%)
就是意味着把整个元素向左移动整个元素本身宽度的距离;结合animation
,可以使用循环滚动。
布局
结构
一个父级容器 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
隐藏超出的文本;- 同时因为
.content
是div
,会自动继承父级元素的宽度,在这种情况下设定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%);
}
}