vue 基于原生动画的自动滚动表格

前言

公司展示大屏需要写滚动表格,通过滚动播放数据,自己随便摸了一个基于动画的自动滚动表格

原理

根据每行的大小和设置的每行滚动时间设置滚动位置,动态添加动画,并把数组第一项移动到最后一项,并订阅该动画结束的事件,在结束时循环执行该操作。

其他功能

  • 可自定义单元格或行
  • 可设置中文映射和取消显示
  • 单元格默认基于网格的响应式大小
  • 鼠标进入时可设置暂停

代码

<template>
      <div class="title-container" v-if="!props.noTitle">
        <div
          v-for="item in props.displayTitles ?? Object.keys(props. List[0])"
          :key="item"
        >
          {{ props.titleMapping?.get(item) ?? item }}
        </div>
      </div>
    <div class="scroll-table">
      <div
        ref="container"
        class="container"
        v-on:mouseenter="() => {if(props.pauseWhenMouseEnter) animation?.pause()}"
        v-on:mouseleave="() => {if(props.pauseWhenMouseEnter) animation?.play()}"
      >
        <!-- 行插槽,作用于每个单元格,设置每个单元格的格式,设置 item-container 类型可继承组件定义的样式 -->
        <slot
          name="row"
          v-for="(item, index) in innerList"
          :key="item.id"
          :item="item"
          :index="index"
        >
          <div class="item-container">
            <!-- 默认插槽,作用于每个单元格,设置每个单元格的格式,设置 item 类型可继承组件定义的样式 -->
            <slot
              v-for="key in props.displayTitles ?? Object.keys(props.list[0])"
              :key="key"
              :item="Object.keys(item.data).includes(key) ? item.data[key] ? item.data[key] : props.undefinedPlaceholder : props.undefinedPlaceholder"
            >
              <div class="item">
                {{ Object.keys(item.data).includes(key) ? item.data[key] ? item.data[key] : props.undefinedPlaceholder : props.undefinedPlaceholder }}
              </div>
            </slot>
          </div>
        </slot>
      </div>
    </div>
</template>

<script setup lang="ts">
import BaseBox from "./BaseBox.vue";
import {
  defineProps,
  withDefaults,
  onMounted,
  computed,
  ref,
  watch,
} from "vue";
const props = withDefaults(
  defineProps<{
    // 属性名翻译为标题,默认值 属性名列表
    titleMapping?: Map<string, string>;
    // 列宽,与 grid-template-columns 格式,默认值 repeat(${props.displayTitles?.length ?? Object.keys(props.list[0]).length}, 1fr)
    columnSizes?: string;
    // 列表
    list: Array<any>;
    // 展示哪些标题,默认值 全部展示
    displayTitles?: Array<string>;
    // 走完每一行的时间,默认值 2300 ms
    interval?: number;
    // 是否显示标题行,默认值 true
    noTitle?: Boolean;
    // 属性无参数时替换为某字符串,默认值 --
    undefinedPlaceholder?: string;
    // 鼠标进入时暂停,默认值 true
    pauseWhenMouseEnter?: Boolean;
  }>(),
  {
    interval: 2300,
    noTitle: false,
    undefinedPlaceholder: "--",
    pauseWhenMouseEnter: false,
  }
);
const innerList = ref<Array<{ id: number; data: any }>>(
  props.list.map((item, index) => ({ id: index, data: item }))
);
const container = ref<HTMLDivElement>();
onMounted(() => {
  animate(true);
});
// 监控数据列表更新
watch(
  () => props.list,
  () => {
    innerList.value = props.list.map((item, index) => ({
      id: index,
      data: item,
    }));
  }
);
// 计算列大小
const columnSize = computed(() => {
  return (
    props.columnSizes ??
    `repeat(${
      props.displayTitles?.length ?? Object.keys(props.list[0]).length
    }, 1fr)`
  );
});
// 进行动画
const animation = ref<Animation>();
const animate = (isStart = false) => {
  // 计算动画高度
  let height = 0;
  if (!isStart) {
    height = -container.value!.children[1].getBoundingClientRect().height;
    // 移动数组第一个到最后一个
    let temp = innerList.value.shift();
    innerList.value.push(temp!);
  } else {
    height = -container.value!.children[0].getBoundingClientRect().height;
  }
  // 进行动画
  animation.value = container.value!.animate(
    [
      {
        top: `${height}px`,
      },
    ],
    {
      duration: props.interval,
      iterations: 1,
    }
  );
  // 监听动画完成后,重新开始动画
  animation.value.addEventListener("finish", () => animate(false));
};
</script>

<style scoped lang="scss">
.title-container {
  display: grid;
  padding: 1rem 0;
  font-size: 1.25rem;
  background-color: rgb(24, 34, 103);
  grid-template-columns: v-bind(columnSize);
  text-align: center;
}
:slotted(.item-container),
.item-container {
  overflow: hidden;
  position: relative;
  left: 0;
  right: 0;
  top: 0;
  display: grid;
  padding: 1rem 0;
  grid-template-columns: v-bind(columnSize);
}
:slotted(.item),
.item {
  text-align: center;
  font-size: 1.25rem;
}
.scroll-table {
  width: 100%;
  height: 100%;
  overflow: hidden;

  .container {
    overflow: hidden;
    position: relative;
    left: 0;
    right: 0;
    top: 0;
  }
}
</style>

参考

posted @ 2023-05-15 16:03  Aoba_xu  阅读(491)  评论(0编辑  收藏  举报