🔥🔥高效易用的 Vue3 公告滚动组件:打造丝滑的内容滚动体验(附源码)

在各类后台管理系统、营销页面或信息展示场景中,公告滚动是高频且基础的交互需求 ------ 既要实现内容自动向上滚动的展示效果,也要兼顾用户手动操作的灵活性。基于 Vue3 Setup 语法糖封装的这款公告滚动组件,完美平衡了「自动展示」与「手动交互」的需求,为前端开发提供了开箱即用、高度可定制的解决方案。

核心特性:兼顾体验与灵活性

这款组件围绕 "用户体验优先" 设计,核心功能覆盖场景全、交互细节拉满:

  1. 丝滑自动滚动 :支持像素级缓慢向上滚动,滚动条与内容同步移动,滚到底部后无缝重置至顶部,避免内容断层;可通过autoScrollSpeed参数自定义滚动速度(默认 1px / 帧),兼顾展示效率与视觉舒适度。
  2. 灵活的手动交互
    • 鼠标悬浮即时暂停滚动,移出后立即恢复,方便用户聚焦查看单条公告;
    • 滚轮操作大幅提速(默认单次滚动 40px),可通过wheelStep参数调整灵敏度,满足快速浏览需求;
    • 保留原生滚动条并支持自定义样式,手动拖拽滚动条后 1 秒自动恢复滚动,兼顾不同操作习惯。
  3. 响应式与兼容性:监听公告列表数据变化,数据更新后自动重新计算高度并重启滚动;兼容 Chrome、Firefox、Edge 等现代浏览器,适配不同内核的滚动条样式。
  4. 轻量且易扩展:无第三方依赖,基于 Vue3 原生 API 开发;组件样式、容器宽高可通过外部样式灵活覆盖,无需修改源码即可适配不同 UI 风格。

快速上手:极简集成,开箱即用

组件采用 Vue3 Setup 语法糖开发,集成流程极简:

  1. 引入组件 :将NoticeScroll.vue文件放入项目组件目录,在需要使用的页面直接导入;
  2. 传入数据 :仅需传递核心参数list(公告列表数组),即可启动基础滚动功能;
  3. 定制化配置 (可选):通过autoScrollSpeed(滚动速度)、wheelStep(滚轮步长)、pauseOnHover(悬浮暂停)等参数,快速适配业务场景。

示例代码简洁直观:

vue

ini 复制代码
<NoticeScroll
  :list="noticeList"
  :autoScrollSpeed="1"
  :wheelStep="40"
  style="width: 500px; height: 200px;"
/>

适用场景:覆盖多类信息展示需求

无论是后台系统的系统公告、电商页面的营销通知,还是资讯类产品的动态资讯,这款组件都能适配 ------ 既满足 "无人操作时自动轮播展示" 的基础需求,也解决了 "用户想手动快速浏览 / 聚焦查看" 的交互痛点。组件内置的内存泄漏防护(卸载时清理定时器、事件监听),也保证了在复杂页面中使用的稳定性。

代码如下:

xml 复制代码
<template>
  <div 
    class="notice-scroll-wrapper"
    ref="wrapperRef"
    @mouseenter="handleMouseEnter"
    @mouseleave="handleMouseLeave"
    @wheel="handleWheel"
  >
    <ul class="notice-scroll-list">
      <li 
        class="notice-scroll-item"
        v-for="(item, index) in list"
        :key="index"
      >
        {{ item }}
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';

// 定义组件属性
const props = defineProps({
  // 公告列表数据
  list: {
    type: Array,
    required: true,
    default: () => []
  },
  // 自动滚动速度(像素/帧)
  autoScrollSpeed: {
    type: Number,
    default: 1
  },
  // 滚轮单次滚动步长(像素)
  wheelStep: {
    type: Number,
    default: 40
  },
  // 帧率(建议60)
  frameRate: {
    type: Number,
    default: 60
  },
  // 鼠标悬浮是否暂停
  pauseOnHover: {
    type: Boolean,
    default: true
  }
});

// DOM 引用
const wrapperRef = ref(null);
// 状态变量
const timer = ref(null); // 自动滚动定时器
const autoScrollTimer = ref(null); // 滚轮/滚动条后恢复定时器
const isHover = ref(false); // 是否悬浮
const wrapperH = ref(0); // 容器高度
const contentH = ref(0); // 内容总高度
const maxScrollTop = ref(0); // 滚动条最大位置

// 初始化
const init = () => {
  if (!wrapperRef.value) return;
  // 计算容器/内容高度
  wrapperH.value = wrapperRef.value.offsetHeight;
  contentH.value = wrapperRef.value.querySelector('.notice-scroll-list').offsetHeight;
  maxScrollTop.value = contentH.value - wrapperH.value;
  // 启动自动滚动
  startAutoScroll();
};

// 启动自动滚动
const startAutoScroll = () => {
  // 内容高度 ≤ 容器高度时,不滚动
  if (contentH.value <= wrapperH.value) return;
  // 清除旧定时器
  clearInterval(timer.value);
  // 逐帧更新滚动条位置
  timer.value = setInterval(() => {
    let current = wrapperRef.value.scrollTop;
    current += props.autoScrollSpeed;
    // 无缝重置:滚到底部回到顶部
    if (current >= maxScrollTop.value) {
      current = 0;
    }
    wrapperRef.value.scrollTop = current;
  }, 1000 / props.frameRate);
};

// 停止自动滚动
const stopAutoScroll = () => {
  clearInterval(timer.value);
  timer.value = null;
};

// 鼠标移入:暂停滚动
const handleMouseEnter = () => {
  if (!props.pauseOnHover) return;
  isHover.value = true;
  stopAutoScroll();
};

// 鼠标移出:恢复滚动
const handleMouseLeave = () => {
  if (!props.pauseOnHover) return;
  isHover.value = false;
  startAutoScroll();
};

// 滚轮控制:提速滚动
const handleWheel = (e) => {
  e.preventDefault();
  // 暂停自动滚动
  stopAutoScroll();
  // 计算新滚动位置
  const newScrollTop = wrapperRef.value.scrollTop + (e.deltaY > 0 ? props.wheelStep : -props.wheelStep);
  // 边界限制
  wrapperRef.value.scrollTop = Math.max(0, Math.min(newScrollTop, maxScrollTop.value));
  // 1秒后恢复自动滚动
  clearTimeout(autoScrollTimer.value);
  autoScrollTimer.value = setTimeout(() => {
    if (!isHover.value) startAutoScroll();
  }, 1000);
};

// 监听滚动条手动拖动:恢复自动滚动
const handleScroll = () => {
  // 排除自动滚动触发的scroll事件
  if (timer.value) return;
  stopAutoScroll();
  clearTimeout(autoScrollTimer.value);
  autoScrollTimer.value = setTimeout(() => {
    if (!isHover.value) startAutoScroll();
  }, 1000);
};

// 监听列表数据变化:重新初始化
watch(
  () => props.list,
  () => {
    // 清除旧定时器
    clearInterval(timer.value);
    clearTimeout(autoScrollTimer.value);
    // 重新计算高度并启动
    setTimeout(init, 0); // 异步等待DOM更新
  },
  { deep: true }
);

// 生命周期:挂载时初始化
onMounted(() => {
  init();
  // 绑定滚动条拖动事件
  if (wrapperRef.value) {
    wrapperRef.value.addEventListener('scroll', handleScroll);
  }
});

// 生命周期:卸载时清理
onUnmounted(() => {
  clearInterval(timer.value);
  clearTimeout(autoScrollTimer.value);
  if (wrapperRef.value) {
    wrapperRef.value.removeEventListener('scroll', handleScroll);
  }
});
</script>

<style scoped>
.notice-scroll-wrapper {
  width: 100%;
  height: 200px; /* 默认高度,可通过父组件覆盖 */
  border: 1px solid #e5e5e5;
  border-radius: 4px;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-width: thin; /* Firefox 滚动条样式 */
  scrollbar-color: #ccc #f5f5f5;
}

/* 自定义滚动条 - Chrome/Edge/Safari */
.notice-scroll-wrapper::-webkit-scrollbar {
  width: 6px;
}
.notice-scroll-wrapper::-webkit-scrollbar-track {
  background: #f5f5f5;
  border-radius: 3px;
}
.notice-scroll-wrapper::-webkit-scrollbar-thumb {
  background: #ccc;
  border-radius: 3px;
}
.notice-scroll-wrapper::-webkit-scrollbar-thumb:hover {
  background: #999;
}

.notice-scroll-list {
  list-style: none;
  padding: 0 20px;
  margin: 0;
}

.notice-scroll-item {
  line-height: 1.6;
  padding: 8px 0;
  color: #333;
  word-break: break-all;
}

.notice-scroll-item:hover {
  color: #1890ff;
  transition: color 0.2s;
}
</style>

调用示例:

xml 复制代码
<template>
  <div class="demo-container">
    <h3>公告滚动示例</h3>
    <!-- 使用公告滚动组件 -->
    <NoticeScroll
      :list="noticeList"
      :autoScrollSpeed="1"
      :wheelStep="40"
      :pauseOnHover="true"
      style="width: 500px; height: 200px;"
    />
  </div>
</template>

<script setup>
import NoticeScroll from './NoticeScroll.vue';

// 公告列表数据
const noticeList = [
  '【公告1】系统将于2025-12-30 23:00进行维护升级,预计耗时2小时,维护期间将暂停所有线上服务,敬请谅解。',
  '【公告2】新用户注册即可领取88元新人礼包,包含5张满减券+1张免运费券,有效期7天,仅限首次注册用户使用。',
  '【公告3】企业版新增数据导出功能,支持Excel/PDF格式,可导出近3个月的客户跟进数据、成交数据、报表数据等。',
  '【公告4】本周累计成交满10000元,可享9折优惠,优惠可叠加会员权益,活动截止至2025-12-31。',
  '【公告5】移动端APP已更新至v2.8.0版本,新增扫码核销、离线缓存功能,建议所有用户及时升级。',
  '【公告6】客服工作时间调整为9:00-22:00,节假日正常值班,如有问题可随时联系在线客服。',
  '【公告7】会员等级体系升级,新增钻石会员等级,可享专属客服、优先发货等权益。'
];
</script>

<style scoped>
.demo-container {
  padding: 50px;
}
</style>
相关推荐
2分钟速写快排32 分钟前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60611 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角2 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!2 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰3 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi4 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen5 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马5 小时前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒6 小时前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端
乘风gg6 小时前
DeepSeek V4.1 Flash 来了,明天中午 Flash 降价 60%
前端·ai编程·claude