React 实现无限滚动表格

前言

无限滚动效果

以文本为例,为了实现无限循环的视觉效果,我们需要准备两段相同的文本,并让第二段文本的头部衔接在第一段文本的尾部。同时,为两段文本设置相同的滚动动画。

当第一段文本滚动到尾部时,如果能让第一段文本的位置瞬间移动回头部,并将第一段文本的头部内容替换为第二段的头部内容,同时动画也回到开始位置,这样,用户从视觉效果上看是感受不到变化的。

以下代码是 Marquee 组件及其样式的简单实现。

tsx 复制代码
// Marquee.tsx
export default function Marquee(props) {
  const { children } = props;
  return (
    <div className={styles.marquee}>
      <div className={styles.ctx}>{children}</div>
      <div className={styles.ctx}>{children}</div>
    </div>
  );
}

// App.tsx
export default function App() {
  return (
    <Marquee>只期待 後來的你 能快樂 那就是 後來的我 最想的</Marquee>
  );
}
scss 复制代码
// Marquee.module.scss
.marquee {
  width: 40px;
  overflow: hidden;
  position: relative;

  .ctx {
    animation: scroll 4s infinite linear; // 指定滚动动画
  }
}

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

悬停效果

实现鼠标悬停效果,通常有两种方法:

  1. 声明 isHovered 变量,通过 onMouseEnteronMouseLeave 事件改变 isHovered 的值,从而实现悬停效果。
  2. 使用 :hover 伪类,通过设置 animation-play-state 属性控制动画的运行,从而实现悬停效果。

本文使用第二种方法,为整个外层容器设置 :hover 样式。当鼠标悬浮在整个容器上时,让内部元素的动画暂停。我们在 Marquee.module.scss 文件中添加如下样式:

scss 复制代码
// ...
.marquee:hover {
  .ctx {
    animation-play-state: paused;
  }
}

到此,最基本的功能已经实现,接下来开始实现表格组件的无限滚动。

表格的无限滚动

首先,实现根据配置动态生成表格的功能。我们从传入的对象数组中提取所有的键,作为表头内容。

本文实现的表头提取函数考虑了传入可选参数的情况,因此它会遍历所有列表项并提取所有存在的键。如果表头属性是固定数量的话,可以使用更简便的方法来提取键。

提取出表头后,我们就可以遍历对象数组中的每一项,并根据对应的键将属性值填入相应的单元格中,逐步构建起表格内容。

基于上述步骤分析,表格组件的初步实现如下(样式在文章末尾给出):

tsx 复制代码
// DataTable.tsx
export default function DataTable<T extends object>({ dataSource }: { dataSource: Array<T> }) {
  if (!Array.isArray(dataSource) || dataSource.length === 0) {
    return null; // 数据源不是数组或数组为空,停止操作
  }

  const labelList = dataSource.reduce((acc: any, cur: any) => {
    const keys = Object.keys(cur);
    for (const key of keys) {
      if (!acc.includes(key)) {
        acc.push(key);
      }
    }
    return acc;
  }, []); // 提取表头

  return (
    <table>
      <thead>
        <tr>
          {labelList.map((key, index) => (
            <th key={index}>{key}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {dataSource.map((data, rowIndex) => (
          <tr key={rowIndex}>
            {labelList.map((key, columnIndex) => (
              <!-- 如果是可选属性,赋予默认值 -->
              <td key={columnIndex}>{data[key] ?? '-'}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
}

此时,在 App.tsx 中直接引用 DataTable 组件并传入 data 数组,就可以看到表格动态生成如下。

tsx 复制代码
// App.tsx
import DataTable from '@/components/DataTable';

export default function App() {
  const data = [
    { name: 'Alice', city: 'New York' },
    { name: 'Bob', age: 30, city: 'San Francisco' },
    { name: 'Charlie', age: 35, city: 'Chicago' },
    { name: 'David', age: 40, city: 'Los Angeles', num: 0 },
  ];

  return (
    <DataTable dataSource={data} />
  );
}

接下来,就是加上无限滚动的效果。

通常来说,可滚动表格只会有一个表头,也就是只有一个 thead 部分,并且此处我们把它固定在顶部。再按照上述文字滚动的实现思路,我们只要在一个 table 中准备两份相同的 tbody 内容,然后给这两份 tbody 设置相同的动画即可。

同时,为了限制整体容器的高度,以防用户在表格位置滚动时感知到突变,造成不好的视觉体验,还需要给 table 加一层外部容器。

到此,整个分析过程就结束了,DataTable 组件的最终实现如下:

tsx 复制代码
// DataTable.tsx
export default function DataTable<T extends object>({ dataSource }: { dataSource: Array<T> }) {
  if (!Array.isArray(dataSource) || dataSource.length === 0) {
    return null; // 数据源不是数组或数组为空,停止操作
  }

  const labelList = dataSource.reduce((acc: any, cur: any) => {
    const keys = Object.keys(cur);
    for (const key of keys) {
      if (!acc.includes(key)) {
        acc.push(key);
      }
    }
    return acc;
  }, []); // 提取表头

  return (
    <div className={styles.container}>
      <table className={styles.table}>
        <thead>
          <tr>
            {labelList.map((key, index) => (
              <th key={index}>{key}</th>
            ))}
          </tr>
        </thead>
        <tbody className={styles.tbody}>
          {dataSource.map((data, rowIndex) => (
            <tr key={rowIndex}>
              {labelList.map((key, columnIndex) => (
                <td key={columnIndex}>{data[key] ?? '-'}</td>
              ))}
            </tr>
          ))}
        </tbody>
        <tbody className={styles.tbody}>
          {dataSource.map((data, rowIndex) => (
            <tr key={rowIndex}>
              {labelList.map((key, columnIndex) => (
                <td key={columnIndex}>{data[key] ?? '-'}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
scss 复制代码
// DataTable.module.scss
@keyframes scroll {
  0% {
    transform: translateY(0);
  }
  100% {
    transform: translateY(-100%);
  }
}

.container {
  height: 160px;
  overflow: hidden;

  .table {
    height: 100%;
    overflow: hidden;
    position: relative;
    background-color: #e6e6e6;
  
    .tbody {
      animation: scroll 4s infinite linear;
    }
  }
}

table {
  width: max-content;
  border-spacing: 0;

  thead {
    height: 30px;
    z-index: 99;
    background-color: #adbcaa;
    position: sticky;
    top: 0;

    tr {
      color: #fff;
      font-weight: bold;
    }
  }

  th,
  td {
    width: max-content;
    padding: 0 8px;
    line-height: 30px;
    text-align: center;
  }
}

然后,刷新页面,再看一下页面效果,这样就实现了。

最后

前端新手一枚,如果有什么不对的地方或者更好的建议,欢迎大家提出。

相关推荐
来吧~5 分钟前
vue3使用video-player实现视频播放(可拖动视频窗口、调整大小)
前端·vue.js·音视频
鎈卟誃筅甡18 分钟前
Vuex 的使用和原理详解
前端·javascript
呆呆小雅23 分钟前
二、创建第一个VUE项目
前端·javascript·vue.js
m0_7482393330 分钟前
前端(Ajax)
前端·javascript·ajax
Fighting_p33 分钟前
【记录】列表自动滚动轮播功能实现
前端·javascript·vue.js
前端Hardy35 分钟前
HTML&CSS:超炫丝滑的卡片水波纹效果
前端·javascript·css·3d·html
技术思考者38 分钟前
HTML速查
前端·css·html
缺少动力的火车39 分钟前
Java前端基础—HTML
java·前端·html
Domain-zhuo1 小时前
Git和SVN有什么区别?
前端·javascript·vue.js·git·svn·webpack·node.js
雪球不会消失了1 小时前
SpringMVC中的拦截器
java·开发语言·前端