当前位置:   article > 正文

VUE3实现table表格数据列表无缝滚动,移入暂停,移出滚动_vue3 表格内容自动滚动

vue3 表格内容自动滚动
前引
        此数据表格滚动功能是基于VUE3 + ant-design-vue实现,支持标题列数组、数据列表、各项数据列宽度、字体大小、数据为空等多参数动态配置,最大限度满足不同功能需求。

一、建立scroll.vue文件

  1. <script setup lang="ts">
  2. import {
  3. CSSProperties,
  4. getCurrentInstance,
  5. onMounted,
  6. ref,
  7. Ref,
  8. } from "vue";
  9. import {
  10. Props
  11. } from "ant-design-vue/es/form/useForm";
  12. import {
  13. number
  14. } from "vue-types";
  15. /*保存帧动画id*/
  16. const requestId = ref(0);
  17. /*渲染数据的大容器 -- 限制高度*/
  18. const listBoxRef = ref();
  19. /*渲染数据的容器*/
  20. const listRef = ref();
  21. /*主容器*/
  22. const refMain = ref();
  23. /*移动的位置*/
  24. const count = ref(0);
  25. /*组件高度*/
  26. const hHeight = ref(0);
  27. /*引用上下文*/
  28. const {
  29. ctx: that,
  30. proxy: any
  31. } = getCurrentInstance();
  32. /*定义属性*/
  33. const props: Props = defineProps({
  34. titles: Array < string > ,
  35. sizes: Array < number > ,
  36. datas: Array < Array < string >> ,
  37. title: String ,
  38. fontSize: Number,
  39. })
  40. /*标题数据*/
  41. const titles: Ref < Array < string >> = ref([]);
  42. /*尺寸数据*/
  43. const sizes: Ref < Array < number >> = ref([]);
  44. /*内容数据*/
  45. const datas: Ref < Array < Array < string >>> = ref([]);
  46. /*拷贝数据列表*/
  47. const copyList: Ref < Array < Array < string >>> = ref([]);
  48. /*样式集合*/
  49. const titleStyles: Ref < Array < CSSProperties >> = ref([]);
  50. const itemStyles: Ref < Array < CSSProperties >> = ref([]);
  51. const subItemStyles: Ref < Array < CSSProperties >> = ref([]);
  52. /* 判断数据是否为空 */
  53. const empty = ref(true)
  54. /* 传入空数据描述*/
  55. const destitle = ref('')
  56. import {
  57. Empty
  58. } from 'ant-design-vue';
  59. const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;
  60. /**
  61. * 开始滚动
  62. */
  63. const start = () => {
  64. if (requestId.value) {
  65. /*有动画 先移除动画*/
  66. window.cancelAnimationFrame(requestId.value);
  67. }
  68. /*计算当前容器高度 能够展示多少条数据*/
  69. let listNum = listBoxRef.value.offsetHeight / parseInt(30)
  70. /*判断是否需要动画*/
  71. if (datas.value.length < listNum) {
  72. listRef.value.style.transform = 'translateY(0)';
  73. // that.$forceUpdate();
  74. } else {
  75. /*复制一份首次展示出来的那些数据 并 添加到数组末尾 --- 为实现滚动位置重置做铺垫*/
  76. const newCopyList: Ref < Array < Array < string >>> = ref([])
  77. copyList.value = datas.value.slice(0, listNum)
  78. if (copyList.value.length < listNum) {
  79. /*当渲染的数据较少的时候 添加空字符串补充*/
  80. let length = listNum - copyList.value.length
  81. for (let i = 0; i < length; i++) {
  82. newCopyList.value.unshift();
  83. }
  84. }
  85. datas.value.push(...[...newCopyList.value, ...copyList.value])
  86. // that.$forceUpdate();
  87. requestId.value = requestAnimationFrame(onScroll)
  88. }
  89. }
  90. /**
  91. * 滚动方法
  92. */
  93. const onScroll = () => {
  94. count.value++
  95. if (count.value >= parseInt(30) * (datas.value.length - copyList.value.length)) {
  96. /*当移动的位置大于等于当前列表的高度的时候 重置移动位置*/
  97. count.value = 0
  98. }
  99. if (listRef.value) {
  100. listRef.value.style.transform = `translateY(-${count.value}px)`;
  101. requestId.value = requestAnimationFrame(onScroll)
  102. }
  103. }
  104. /**
  105. * 停止滚动
  106. */
  107. const stop = () => {
  108. if (requestId.value) {
  109. window.cancelAnimationFrame(requestId.value);
  110. }
  111. }
  112. /**
  113. * 鼠标移入
  114. */
  115. const onMouseOver = () => {
  116. stop()
  117. }
  118. /**
  119. * 鼠标移出
  120. */
  121. const onMouseOut = () => {
  122. if (datas.value.length > 0) {
  123. start()
  124. }
  125. }
  126. /**
  127. * 初始化
  128. */
  129. const init = () => {
  130. hHeight.value = refMain.value?.clientHeight - 40;
  131. }
  132. /**
  133. * 更新数据展示
  134. * @param inputTitles 标题集合[]
  135. * @param inputSizes 列宽度集合[]
  136. * @param inputDatas 数据集合[][]
  137. */
  138. const updateDom = (inputTitles: Array < string > , inputSizes: Array < number > , inputDatas: Array < Array < string >>
  139. , title) => {
  140. if (title) {
  141. empty.value = false
  142. destitle.value = title
  143. } else {
  144. empty.value = true
  145. }
  146. if (inputTitles.length != inputSizes.length || inputTitles.length != inputSizes.length) return;
  147. titles.value = [];
  148. sizes.value = [];
  149. datas.value = [];
  150. titleStyles.value = [];
  151. itemStyles.value = [];
  152. for (let idx = 0; idx < inputTitles.length; idx++) {
  153. titles.value.push(inputTitles[idx]);
  154. sizes.value.push(inputSizes[idx]);
  155. let titleStyle: CSSProperties = {
  156. width: '0px',
  157. fontSize: '12px',
  158. transformOrigin: '0 0',
  159. // whiteSpace: 'nowrap',
  160. transform: `scale(${props.fontSize/12})`
  161. }
  162. let itemStyle: CSSProperties = {
  163. width: '0px',
  164. // transform: 'scale(2)'
  165. }
  166. subItemStyles.value = {
  167. fontSize: '12px',
  168. transform: `scale(${props.fontSize/12})`,
  169. transformOrigin: '0 0',
  170. whiteSpace: 'nowrap',
  171. width: '100%',
  172. paddingLeft:'5px',
  173. textAlign:'center'
  174. }
  175. if (inputSizes[idx] != 0) {
  176. titleStyle.width = itemStyle.width = sizes.value[idx] + 'px';
  177. } else {
  178. titleStyle.width = itemStyle.width = '0px';
  179. titleStyle.flex = itemStyle.flex = 1;
  180. }
  181. titleStyles.value.push(titleStyle);
  182. if (idx % 2 == 1) {
  183. itemStyle.background = 'rgba(20, 121, 59, 0.2)';
  184. }
  185. itemStyles.value.push(itemStyle);
  186. }
  187. for (let idx = 0; idx < inputDatas.length; idx++) {
  188. datas.value.push(inputDatas[idx]);
  189. }
  190. start();
  191. }
  192. /**
  193. * DOM元素创建完成后执行的函数
  194. */
  195. onMounted(() => {
  196. init();
  197. // updateDom(props.titles, props.sizes, props.datas);
  198. })
  199. /**
  200. * 更新数据展示
  201. * @param inputTitles 标题集合[]
  202. * @param inputSizes 列宽度集合[]
  203. * @param inputDatas 数据集合[][]
  204. */
  205. const update = (inputTitles: Array < string > , inputSizes: Array < number > , inputDatas: Array < Array < string >> ,
  206. title) => {
  207. updateDom(inputTitles, inputSizes, inputDatas, title);
  208. }
  209. /**
  210. * 对外公开方法
  211. */
  212. defineExpose({
  213. update,
  214. })
  215. </script>
  216. <template>
  217. <div class="cr-scroll-auto-panel">
  218. <div class="cr-content" ref="refMain">
  219. <div class="cr-head">
  220. <div class="cr-column cr-common-no-color" :style="titleStyles[index]" v-for="(item,index) in titles">
  221. {{ item }}
  222. </div>
  223. </div>
  224. <div ref="listBoxRef" class="cr-list-box" @mouseenter="onMouseOver" @mouseleave="onMouseOut"
  225. :style="{height:hHeight+'px'}">
  226. <ul ref="listRef" class="list">
  227. <li v-for="(item,index) in datas" :key="index" v-if="empty">
  228. <div class="cr-column" v-for="(subItem,subIndex) in item" :style="itemStyles[subIndex]">
  229. <span :style="subItemStyles">
  230. {{ subItem }}
  231. </span>
  232. </div>
  233. </li>
  234. <div v-else>
  235. <a-empty :image="simpleImage" :description="destitle" />
  236. </div>
  237. </ul>
  238. </div>
  239. </div>
  240. </div>
  241. </template>
  242. <style scoped lang="less">
  243. /*引入字体样式*/
  244. @import url('/@/assets/fonts/index.css');
  245. :deep(.ant-empty-description) {
  246. color: #fff !important;
  247. }
  248. /*主体容器*/
  249. .cr-scroll-auto-panel {
  250. width: 100%;
  251. height: 100%;
  252. display: flex;
  253. flex-direction: column;
  254. /*内容样式*/
  255. .cr-content {
  256. height: 100px;
  257. flex: 1;
  258. display: flex;
  259. flex-direction: column;
  260. /*标题栏样式*/
  261. .cr-head {
  262. width: 100%;
  263. background: rgba(25, 32, 102, 1.0);
  264. display: flex;
  265. flex-direction: row;
  266. color: yellow;
  267. font-size: 10px;
  268. height: 40px;
  269. justify-items: center;
  270. }
  271. /*列样式*/
  272. .cr-column {
  273. font-size: 9px;
  274. display: flex;
  275. flex-direction: row;
  276. justify-content: center;
  277. align-items: center;
  278. font-family: "AlimamaShuHeiTiBold", serif;
  279. }
  280. /*滚动表无背景色列样式*/
  281. .cr-common-no-color {
  282. background: rgba(255, 255, 255, 0);
  283. }
  284. /*list容器样式*/
  285. .cr-list-box {
  286. height: 0;
  287. overflow: hidden;
  288. /*list样式*/
  289. .list {
  290. li {
  291. height: 30px;
  292. line-height: 30px;
  293. color: white;
  294. display: flex;
  295. flex-direction: row;
  296. justify-items: center;
  297. align-items: center;
  298. }
  299. }
  300. }
  301. }
  302. }
  303. </style>

        组件共接受五个参数,分别是titles、sizes、datas、title、fontSizetitles代表每列数据的标题,sizes是每列数据的宽度,datas是所有滚动展示数据数组,fontSize是展示字体大小,对于小于12号的字体我们做了缩放处理,不需要担心浏览器不支持,title是指数据为空时传入的标注,为空时表示有数据,不为空则会显示无数据图标。注意sizes的数组长度与titles和datas的长度要相同,否则会报错

        组件抛出了update方法,当数据更新时父组件调用update会重新渲染滚动列表数据,在vue2中更新滚动数据需要借助$forceUpdate强制更新视图,在vue3中响应式系统已经进行了重构,响应式数据会自动更新视图。

        数据滚动动画是基于window API requestAnimationFrame实现,可以更改speed速度等参数。

二、父组件调用

        在父组件中

<ScrollAuto ref="refScroll" :datas="datas" :sizes="sizes" :titles="titles" :fontSize="9" />

        数据更新时

refScroll.value.update(titles.value, sizes.value, datas.value);

示例图

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/193922?site
推荐阅读
相关标签
  

闽ICP备14008679号