- A+
所属分类:Web前端
前言
公司展示大屏需要写滚动表格,通过滚动播放数据,自己随便摸了一个基于动画的自动滚动表格
原理
根据每行的大小和设置的每行滚动时间设置滚动位置,动态添加动画,并把数组第一项移动到最后一项,并订阅该动画结束的事件,在结束时循环执行该操作。
其他功能
- 可自定义单元格或行
- 可设置中文映射和取消显示
- 单元格默认基于网格的响应式大小
- 鼠标进入时可设置暂停
代码
<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>