Vue3中实现自然滚动表格列表,可调整滚动快慢

avatar
作者
筋斗云
阅读量:0

实现效果如图所示,可以调整滚动的快慢,可以实现内容高度不够的时候停止滚动的效果。

1.实现原理:

创建一个dom为ul,赋值为当前列表数据,然后拷贝这个dom赋值给第二个ul,然后判断屏幕高度跟滚动高度对比,利用requestAnimationFrame动画实现滚动

2.注意事项:

这是基于react的滚动列表改造过来的,所以本身留了render口子,可以自定义表内的内容,不只是文字,但是对于vue3跟jsx的结合,我还是不是很熟,所以目前还没有用到render写法

3.对比:

最开始实现我是想用vue3-seamless-scroll这个插件的,但是使用起来发现,首先它不支持数据少的情况下自动停止滚动,需要传入step为0,感觉不是很方便(也可能是我没有很会用这个插件),所以感觉直接写了一个。

组件代码:

<template>     <div         class="scrollContainer"         :key="currentTime"         :style="{ height: `${height}px` }"     >         <div             class="scrollHead"             :style="{                 height: headerHeight + 'px',             }"         >             <div                 v-for="l in columns"                 :key="l.key"                 :style="{ width: `${l.width}px` }"             >                 {{ l.title }}             </div>         </div>         <ul             class="scrollBody"             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 lang="ts"> 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 lang="scss" 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 lang="ts"> 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 lang="scss" scoped></style> 

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!