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>