使用场景
内容滚动,例如大屏中的列表,卡片等
滚动组件演示效果图
需求
- 轮播无缝滚动
- 方向控制
- 播放/暂停
原理
定义帧动画以及控制方向的css类,通过js增加/移除控制方向的css类的方式控制动画。
注意:需要复制出一份子组件放在子组件后(下/右)边,目的是消除视口中动画结束后导致的大片空白
核心
- css3 animation动画
- css3 transform根据自身宽高位移
- classList 操作 class 集合
组件代码
./components/index.less
// 暂停播放
.pause {
animation-play-state: paused !important;
}
// 播放
.play {
animation-play-state: running !important;
}
// 向上滚动
.top {
animation: top 3s linear infinite;
&:hover {
.pause();
}
}
@keyframes top {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-50%)
}
}
// 向左滚动
.left {
animation: left 3s linear infinite;
&:hover {
.pause();
}
}
@keyframes left {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-50%)
}
}
./components/index.tsx
import {
ForwardRefRenderFunction,
forwardRef,
useImperativeHandle,
useRef,
CSSProperties,
ReactNode,
} from 'react';
import './index.less';
export type Direction = 'left' | 'top';
export interface RollMethodTypes {
play(): void;
pause(): void;
}
interface ComponentPropTypes {
children: ReactNode;
direction?: Direction;
animation?: CSSProperties['animation'];
}
// 父盒子需规定视口宽高,并且overflow: hidden !!!
const Roll: ForwardRefRenderFunction<RollMethodTypes, ComponentPropTypes> = (
{ children, direction = 'top', animation },
ref,
) => {
const roll = useRef<HTMLDivElement>(null);
// 向父组件暴露 播放、暂停 方法
useImperativeHandle(ref, () => ({
play: () => {
roll.current?.classList.replace('pause', 'play');
},
pause: () => {
roll.current?.classList.replace('play', 'pause');
},
}));
// 向左滚动
if (direction === 'left' || animation?.toString().includes('left')) {
return (
<div
ref={roll}
className={`${direction} play`}
// 水平排列,并且复制一份无缝衔接,水平宽度变成视口的两倍,以消除视口中动画结束后导致的大片空白
style={{ width: '200%', display: 'flex', animation }}
>
<div style={{ flex: 1 }}>{children}</div>
<div style={{ flex: 1 }}>{children}</div>
</div>
);
}
// 向上滚动
return (
<div ref={roll} className={`${direction} play`} style={{ animation }}>
{/* 垂直排列,并且复制一份无缝衔接,让垂直高度变成视口的两倍,以消除视口中动画结束后导致的大片空白 */}
{children}
{children}
</div>
);
};
export default forwardRef(Roll);
如何使用(Demo)
index.less
.title {
background: rgb(121, 242, 157);
}
.content {
.item {
height: 80px;
}
.item:nth-child(1) {
background-color: red;
}
.item:nth-child(2) {
background-color: yellow;
}
.item:nth-child(3) {
background-color: blue;
}
.item:nth-child(4) {
background-color:rgb(121, 242, 157);
}
.item:nth-child(5) {
background-color: rgba(0, 0, 0, 0.3);
}
.item:nth-child(6) {
background-color: #daa520
}
}
index.tsx
import { useRef, useState } from "react";
import Roll from "./components/Roll";
import styles from './index.less';
import { Direction, RollMethodTypes } from './components/Roll'
const Index = () => {
const roller = useRef<RollMethodTypes>(null);
const [num, setNum] = useState(0);
const [direction, setDirection] = useState<Direction>('left');
return (
<div>
<div style={{ width: 500, height: 200, overflow: 'hidden' }}>
<Roll
ref={roller}
direction={direction}
// animation="left 1s linear infinite"
>
<div className={styles.content}>
<div className={styles.item} style={{ color: '#fff', fontSize: 40 }}>{num}</div>
<div className={styles.item}>2222222222</div>
<div className={styles.item}>333333333333333333333333333333</div>
<div className={styles.item}>444444444444444444</div>
<div className={styles.item}>55555</div>
<div className={styles.item}>6</div>
</div>
</Roll>
</div>
<button onClick={() => setDirection(direction === 'left' ? 'top' : 'left' )}>{direction == 'left' ? '向上滚动' : '向左滚动' }</button>
<button onClick={() => setNum(num + 1)}>+1</button>
<button onClick={() => roller.current?.play()}>开始滚动</button>
<button onClick={() => roller.current?.pause()}>暂停滚动</button>
</div>
)
};
export default Index;