1.實現原理:
建立一個dom為ul,賦值為當前列表資料,然後複製這個dom賦值給第二個ul,然後判斷螢幕高度跟滾動高度對比,利用requestAnimationFrame動畫實現滾動
2.注意事項:
這是基於react的滾動列表改造過來的,所以本身留了render口子,可以自定義表內的內容,不只是文字,但是對於vue3跟jsx的結合,我還是不是很熟,所以目前還沒有用到render寫法
3.對比:
最開始實現我是想用vue3-seamless-scroll這個外掛的,但是使用起來發現,首先它不支援資料少的情況下自動停止滾動,需要傳入step為0,感覺不是很方便(也可能是我沒有很會用這個外掛),所以感覺直接寫了一個。
實現效果如圖所示,可以調整滾動的快慢,可以實現內容高度不夠的時候停止滾動的效果:
元件程式碼:
<template> <div :key="currentTime" :style="{ height: `${height}px` }" > <div :style="{ height: headerHeight + 'px', }" > <div v-for="l in columns" :key="l.key" :style="{ width: `${l.width}px` }" > {{ l.title }} </div> </div> <ul ref="wrapperDom" :style="{ height: `${height - headerHeight}px` }" > <ul ref="childDom1" @mouseenter="handleEnter" @mouseleave="handleLeave" > <li v-for="(l, i) in dataSource" :data-key="rowKey ? l[rowKey] : `list${i}`" :key="rowKey ? l[rowKey] : `list${i}`" :style="{ height: `${rowHeight}px` }" > <div v-for="(p, c) in columns" :key="`p${c}`" :style="getStyle(p, l)" @click=" (e) => { e.stopPropagation() onCellClick(l, p) onRowClick?.(l) } " > {{ p?.render?.(i, l, l[p.key]) || l[p.key] }} </div> </li> </ul> <ul ref="childDom2"></ul> </ul> </div> </template> <script setup> import { onMounted, watch, ref, onBeforeUnmount, computed, nextTick } from 'vue' interface ViewProps { height: number dataSource: Record<string, any>[] columns: TableColumn[] headerHeight?: number rowHeight?: number onRowClick?: (l: Record<string, any>) => void rowKey?: string scroll?: boolean } export interface TableColumn { key: string title: string width: number render?: (index: number, data: Record<string, any>, text: any) => any onClick?: (data: Record<string, any>) => void } const props = defineProps<ViewProps>() const { height, columns, rowHeight = 27.5, headerHeight = 36, rowKey } = props const wrapperDom = ref<any>() const childDom1 = ref<any>() const childDom2 = ref<any>() const currentTime = ref(new Date().getTime()) let count = 0 let reqAnimation: number onMounted(() => { nextTick(() => { reqAnimation = window.requestAnimationFrame(taskStart) }) }) onBeforeUnmount(() => { handleEnter() }) const dataSource = computed(() => { console.log('dataSource', dataSource) return props.dataSource }) watch( () => props.dataSource, () => { currentTime.value = new Date().getTime() } ) const getStyle = (p, l) => { let pStyle = { width: `${p.width}px` } if (l.lineColor) { pStyle['color'] = l.lineColor } return pStyle } var startTime = null, stepInMs = 100, drawCount = 0 const taskStart = (timestamp: any) => { var progress if (startTime === null) { startTime = timestamp } progress = timestamp - startTime! if (progress > stepInMs) { startTime = timestamp if ( childDom1.value?.clientHeight >= wrapperDom.value?.clientHeight && childDom2.value?.clientHeight < 10 ) { childDom2.value.innerHTML = childDom1.value.innerHTML } if (wrapperDom.value?.scrollTop >= childDom1.value?.scrollHeight) { wrapperDom.value.scrollTop = 0 count = 0 } else { count += 1 wrapperDom.value.scrollTop = count } } if (props.scroll) { reqAnimation = window.requestAnimationFrame(taskStart) } } const handleEnter = () => { window.cancelAnimationFrame(reqAnimation) } const handleLeave = () => { reqAnimation = window.requestAnimationFrame(taskStart) } const onCellClick = (l: Record<string, any>, p: TableColumn) => { p?.onClick?.(l) } </script> <style scoped> .scrollContainer { width: 100%; div { text-align: center; display: inline-block; margin: 0; font-size: 14px; font-weight: normal; font-stretch: normal; letter-spacing: 0; opacity: 0.9; } .scrollHead { display: flex; align-items: center; background-color: rgba(33, 60, 93, 0.55); div { font-size: 14px; font-stretch: normal; letter-spacing: 0; font-family: MicrosoftYaHei, sans-serif; font-weight: bold; color: #ffffff; opacity: 0.47; } } .scrollBody { overflow-y: scroll; width: 100%; padding: 0; scrollbar-width: none; -ms-overflow-style: none; ul { height: auto; padding: 0; margin: 0; } li { list-style: none; position: relative; cursor: pointer; display: flex; height: 36px; color: #fff; align-items: center; } li div { line-height: 36px; color: #24acef; white-space: nowrap; /* 文字不換行 */ overflow: hidden; /* 溢位部分隱藏 */ text-overflow: ellipsis; /* 溢位部分用"..."代替 */ } li:hover { background: rgba(43, 143, 171, 0.52); > div { color: #fff; } } &::-webkit-scrollbar { display: none; } li:nth-child(even) { background-color: rgba(43, 143, 171, 0.13); } li:nth-child(even):hover { background: rgba(43, 143, 171, 0.52); color: #fff; } } } </style>
父元件呼叫:
<template> <div> <ScrollTable :height="300" :dataSource="dataSource" :columns="columns" :scroll="true" ></ScrollTable> </div> </template> <script setup> import { ref, reactive } from 'vue' import ScrollTable from '../../components/ScrollTable.vue' const dataSource = [ { name: '張三', age: 18, gender: '男', address: '北京市', phone: '12345678901', }, { name: '李四', age: 20, gender: '女', address: '上海市', phone: '12345678902', }, { name: '王五', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '趙六', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '王思聰', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '王健林', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '馬雲', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '馬化騰', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '馬1', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '馬2', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '馬3', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '馬4', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '馬5', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '馬6', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '馬7', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '馬8', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, { name: '馬9', age: 22, gender: '男', address: '廣州市', phone: '12345678903', }, ] const columns = [ { title: '姓名', dataIndex: 'name', width: 50, key: 'name', }, { title: '年齡', dataIndex: 'age', width: 50, key: 'age', }, { title: '性別', dataIndex: 'gender', width: 50, key: 'gender', }, { title: '地址', dataIndex: 'address', width: 50, key: 'address', }, { title: '電話', dataIndex: 'phone', width: 50, key: 'phone', }, ] </script> <style scoped></style>