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>
相关推荐
java1234_小锋8 小时前
【技术专题】HTML5 网页技术 - HTML5 Canvas 基础以及动画入门
前端·html·html5
知了学历孙老师11 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
ShineWinsu13 小时前
对于JavaScript:操作元素的解析
开发语言·前端·javascript·面试·web·dom·元素
福兮说13 小时前
JSON 格式正确,数据就可靠吗?我用一份自编配置检查类型与空值
java·前端·json
程序员老赵15 小时前
Docker 部署青龙面板:轻松搭建定时任务管理平台
前端·docker·开源
Swot15 小时前
Asciidoctor 公式数字下标错位问题:原因分析与解决方案
前端
Swot15 小时前
leo-editor 中将 Markdown 内容转成 asciidoc 格式
前端
狒狒AI15 小时前
Remotion / GSAP+Three / HyperFrames / Skia 四栈实测:代码渲染知识视频的选栈方法(附开源技能)
前端·人工智能·aigc
修炼的dance15 小时前
浏览器里导出H.265,8.5秒和55.5秒差在硬件
前端
补码缩写15 小时前
WebCodecs选编码器,探测之后还要验解码和播放
前端