React无缝滚动组件

使用场景

内容滚动,例如大屏中的列表,卡片等

滚动组件演示效果图

需求

  • 轮播无缝滚动
  • 方向控制
  • 播放/暂停

原理

定义帧动画以及控制方向的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;
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值