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>
相关推荐
卸任9 小时前
Vibe coding工具使用心得
前端·ai编程
brien99 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance10 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M10 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写10 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李10 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易10 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马10 小时前
前沿视界
前端
kyriewen10 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易11 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员