vue3很丝滑的table表格向上滚动效果,多用于统计页面

效果如图, 上边无缝滚动

组件代码:

xml 复制代码
<template>
  <div class="table-scroll-wrapper" ref="wrapperRef" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave">
    <div class="table-scroll-content" ref="contentRef">
      <!-- 原始内容 -->
      <div ref="originalContentRef">
        <slot></slot>
      </div>
      <!-- 克隆内容用于无缝滚动 -->
      <div v-if="shouldScroll" ref="cloneContentRef">
        <slot></slot>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue';

const props = defineProps({
  // 滚动速度(像素/帧)
  speed: {
    type: Number,
    default: 0.5
  },
  // 鼠标悬停是否暂停
  hoverPause: {
    type: Boolean,
    default: true
  },
  // 是否启用滚动
  enabled: {
    type: Boolean,
    default: true
  }
});

// 引用DOM元素
const wrapperRef = ref<HTMLElement>();
const contentRef = ref<HTMLElement>();
const originalContentRef = ref<HTMLElement>();
const cloneContentRef = ref<HTMLElement>();

// 滚动状态
const animationId = ref<number>();
const currentTop = ref(0);
const isPaused = ref(false);
const contentHeight = ref(0);

// 计算属性:是否需要滚动
const shouldScroll = computed(() => {
  return props.enabled && contentHeight.value > (wrapperRef.value?.offsetHeight || 0);
});

// 滚动动画函数
const scroll = () => {
  if (!shouldScroll.value || isPaused.value || !wrapperRef.value || !contentRef.value) {
    return;
  }

  // 向上滚动
  currentTop.value -= props.speed;
  contentRef.value.style.transform = `translateY(${currentTop.value}px)`;

  // 当滚动过原始内容高度时,重置位置实现无缝滚动
  if (Math.abs(currentTop.value) >= contentHeight.value) {
    currentTop.value = 0;
  }

  // 继续下一帧动画
  animationId.value = requestAnimationFrame(scroll);
};

// 开始滚动
const startScroll = () => {
  if (animationId.value) {
    cancelAnimationFrame(animationId.value);
  }
  if (shouldScroll.value) {
    scroll();
  }
};

// 停止滚动
const stopScroll = () => {
  if (animationId.value) {
    cancelAnimationFrame(animationId.value);
    animationId.value = undefined;
  }
};

// 鼠标进入处理
const handleMouseEnter = () => {
  if (props.hoverPause) {
    isPaused.value = true;
    stopScroll();
  }
};

// 鼠标离开处理
const handleMouseLeave = () => {
  if (props.hoverPause) {
    isPaused.value = false;
    startScroll();
  }
};

// 更新内容高度
const updateContentHeight = () => {
  if (originalContentRef.value) {
    contentHeight.value = originalContentRef.value.offsetHeight;
  }
};

// 监听窗口大小变化
const handleResize = () => {
  updateContentHeight();
  // 如果条件变化,重新控制滚动
  if (shouldScroll.value && !isPaused.value && props.enabled) {
    startScroll();
  } else if (!shouldScroll.value) {
    stopScroll();
    currentTop.value = 0;
    if (contentRef.value) {
      contentRef.value.style.transform = 'translateY(0)';
    }
  }
};

// 生命周期钩子
onMounted(async () => {
  // 等待DOM完全渲染
  await nextTick();
  updateContentHeight();

  // 开始滚动
  if (shouldScroll.value && props.enabled) {
    startScroll();
  }

  // 监听窗口大小变化
  window.addEventListener('resize', handleResize);
});

onUnmounted(() => {
  stopScroll();
  window.removeEventListener('resize', handleResize);
});
</script>

<style scoped>
.table-scroll-wrapper {
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;
}

.table-scroll-content {
  width: 100%;
  position: absolute;
  top: 0;
  left: 0;
  will-change: transform;
}

/* 确保表格行正确显示 */
:deep(.el-row) {
  width: 100%;
  box-sizing: border-box;
}
</style>

页面中的使用

xml 复制代码
<template>
<div style="height: 20.3rem; overflow: hidden; margin-top: .4rem;">
    <simple-scroll :speed="0.5" :hoverPause="true" v-if="MaintainList && MaintainList.length > 0">
      <el-row class="screen_ltable_cont" :key="index" v-for="(item, index) in MaintainList" :gutter="10">
        <el-col :span="6">
          <el-tooltip class="box-item" effect="dark" :content="item.deviceAdress" placement="top">
            <div class="textellipsis">{{ index }}</div>
          </el-tooltip>
        </el-col>
        <el-col :span="5">
          <span class="screen_tag screen_warning" v-if="item.status == '1'">维修中</span>
          <span class="screen_tag screen_primary" v-if="item.status == '2'">维修完成</span>
        </el-col>
      </el-row>
    </simple-scroll>
    </div>
</template>
<script setup>
import SimpleScroll from '../mycomponents/simpleScroll/index.vue';
</script>
相关推荐
敲敲了个代码2 小时前
从硬编码到 Schema 推断:前端表单开发的工程化转型
前端·javascript·vue.js·学习·面试·职场和发展·前端框架
张雨zy3 小时前
Pinia 与 TypeScript 完美搭配:Vue 应用状态管理新选择
vue.js·ubuntu·typescript
dly_blog3 小时前
Vue 响应式陷阱与解决方案(第19节)
前端·javascript·vue.js
消失的旧时光-19433 小时前
401 自动刷新 Token 的完整架构设计(Dio 实战版)
开发语言·前端·javascript
console.log('npc')4 小时前
Table,vue3在父组件调用子组件columns列的方法展示弹窗文件预览效果
前端·javascript·vue.js
用户47949283569154 小时前
React Hooks 的“天条”:为啥绝对不能写在 if 语句里?
前端·react.js
我命由我123454 小时前
SVG - SVG 引入(SVG 概述、SVG 基本使用、SVG 使用 CSS、SVG 使用 JavaScript、SVG 实例实操)
开发语言·前端·javascript·css·学习·ecmascript·学习方法
用户47949283569154 小时前
给客户做私有化部署,我是如何优雅搞定 NPM 依赖管理的?
前端·后端·程序员
C_心欲无痕5 小时前
vue3 - markRaw标记为非响应式对象
前端·javascript·vue.js
qingyun9895 小时前
深度优先遍历:JavaScript递归查找树形数据结构中的节点标签
前端·javascript·数据结构