前言 公司展示大屏需要寫滾動表格,通過滾動播放數據,自己隨便摸了一個基於動畫的自動滾動表格 原理 根據每行的大小和設置的每行滾動時間設置滾動位置,動態添加動畫,並把數組第一項移動到最後一項,並訂閱該動畫結束的事件,在結束時迴圈執行該操作。 其他功能 可自定義單元格或行 可設置中文映射和取消顯示 單元 ...
前言
公司展示大屏需要寫滾動表格,通過滾動播放數據,自己隨便摸了一個基於動畫的自動滾動表格
原理
根據每行的大小和設置的每行滾動時間設置滾動位置,動態添加動畫,並把數組第一項移動到最後一項,並訂閱該動畫結束的事件,在結束時迴圈執行該操作。
其他功能
- 可自定義單元格或行
- 可設置中文映射和取消顯示
- 單元格預設基於網格的響應式大小
- 滑鼠進入時可設置暫停
代碼
<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>