尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于jQuery实现带进度条的时间轴自动播放组件

基于jQuery实现带进度条的时间轴自动播放组件 简介面向Web前端开发者的jQuery日期时间轴组件源码以动态进度条方式展示日期与小时之间的时间流转适用于活动倒计时、项目里程碑或网站底部时间轴展示等场景。资源共4个文件包含2个JavaScript脚本、1个HTML页面和1个CSS样式表压缩包大小仅38KBHTML负责页面骨架CSS定义进度条与时间轴样式JS则基于Date对象与定时器实现自动播放及日期/小时模式切换。目前已有314人学习下载适合希望掌握jQuery动画与CSS3过渡配合的开发者参考尤其适合前端初学者熟悉两者结合的实际用法。通过该资源可直观理解setInterval驱动宽度变化、结合Date对象计算时间差从而构建动态时间轴代码结构简洁便于快速改造复用对前端交互组件开发具有实践参考价值。 做前端久了总会遇到一些“看似简单、做起来事多”的需求。去年公司要做一套产品迭代回顾页要求把过去一年的重要版本更新放在一条时间轴上自动轮播展示还得带一条能反映播放进度的进度条。我当时第一反应是直接找个现成的轮播图插件改但试了几个都不顺手——要么时间轴样式固定死要么进度条只是个装饰跟播放进度完全对不上。最后索性自己基于 jQuery 写了一个带进度条的日期时间轴自动播放组件顺手把“点击节点跳转、拖动进度条、滚动加载更多”都揉了进去。今天就把这套实现思路和踩坑过程完整分享一下。这个组件适合谁来参考如果你正在做产品公告页、营销活动回顾、旅游行程回放、个人时间线展示或者手上维护着老项目不能轻易上 Vue、React那这套基于 jQuery 的方案会非常实用。它不依赖后端纯前端渲染数据源只需要一个 JSON 数组改一改就能直接嵌进现有页面。1. 整体设计思路与方案选型1.1 为什么用 jQuery 而不是直接用 Vue 或原生 JS可能有朋友会说2025 年了还写 jQuery 是不是有点复古这个我承认但实际项目里确实存在大量老系统。我们这次要嵌入的页面本身就是一台老业务系统的后台管理页里面积累了巨量 jQuery 代码如果为了一个时间轴组件强行引入 Vue 或 React反而会带来构建链路、依赖冲突、团队成员学习成本等一系列问题。jQuery 在这里胜在轻量、兼容性好、API 直观尤其适合做“页面局部增强”的场景。另一层考虑是这个需求本质上是对“状态”的管理——当前播放到第几个节点、进度条宽度多少、是否处于播放中。jQuery 配合一点简单的对象封装完全能表达清楚。它不像数据表格那样需要细粒度的响应式更新用 jQuery 直接操作 DOM 反而更直接性能也没有瓶颈。如果你是全新的现代化项目我当然建议用 Vue 或者 React 起步但如果你是在维护老项目不要排斥 jQuery它能解决的问题比想象中多。1.2 核心功能拆解我把需求拆成了四个模块时间轴渲染模块根据日期数据生成左侧时间线、右侧内容卡片高亮当前活动节点。进度条控制模块进度条宽度与当前索引/总条数形成百分比映射支持点击跳转和拖拽。自动播放模块定时器驱动索引递增播完一轮自动回到开头支持暂停/继续、播放速度调节。数据加载扩展模块当时间点超过 50 个时采用滚动到底部加载更多的方式分页渲染避免一次渲染过多 DOM。这个拆分主要是为了“隔离变化”。比如以后数据源从本地数组改成接口返回只需要替换渲染前的数据获取逻辑进度条想换成圆环样式也只需要改动控制模块。下面我详细讲每个模块的实现要点。1.3 数据结构设计所有功能都围绕数据展开所以第一步是定数据结构。我用的非常简单var timelineData [ { date: 2024-01-15, title: v2.0 全新改版上线, desc: 重构首页布局新增暗色模式, status: done }, { date: 2024-03-22, title: v2.3 性能优化, desc: 首屏加载时间降低 45%, status: done }, { date: 2024-06-10, title: v2.6 移动端适配, desc: 补齐小程序端核心流程, status: active }, { date: 2024-09-05, title: v3.0 数据大屏, desc: 新增实时数据可视化面板, status: todo } ];字段说明date日期字符串格式统一为YYYY-MM-DD便于排序和展示。title节点主标题。desc节点描述内容。status节点状态done表示已过去active表示当前播放项todo表示未播到。这里有个小细节如果后端返回的时间格式不统一比如有的带时分秒有的不带建议在渲染前先做一次归一化我在normalizeData方法里统一处理避免排序时字符串比较出问题。2. 进度条与时间轴联动的原理2.1 进度条宽度计算进度条和时间轴之间的映射关系是核心逻辑。最直接的方式是“按索引算百分比”function updateProgress(index, total) { var percent total 1 ? 100 : Math.round((index / (total - 1)) * 100); $(#progressBar).css(width, percent %); $(#progressText).text(percent %); }这里我用了total - 1作为分母原因是第一个节点对应 0%最后一个节点对应 100%这样进度条才能跟“播放到哪了”直观对应。如果你用的是“已播放条目数 / 总条目数”那第一个节点只会显示 0%最后一个节点永远到不了 100%视觉上会差一口气。这个细节测试时特别容易忽略。2.2 让进度条“可点击跳转”进度条不只是展示我还希望用户点击进度条任意位置就能跳到对应节点。这里需要监听进度条的点击事件再根据点击位置的 X 坐标算出目标索引。$(#progressWrap).on(click, function(e) { var $this $(this); var offsetX e.pageX - $this.offset().left; var totalWidth $this.width(); var percent offsetX / totalWidth; var targetIndex Math.round(percent * (timelineData.length - 1)); gotoIndex(targetIndex); });要注意e.pageX和$(this).offset().left的组合。如果你直接用e.offsetX在部分浏览器里也没问题但一旦进度条外层有 padding 或 borderoffsetX就会偏。用 pageX 减去容器左边界是兼容性最好的写法。2.3 让进度条“可拖拽”点击跳转只是第一步有反馈说“能拖进度条更直观”。拖拽比点击麻烦一点因为你要区分“按下、移动、松开”三个状态并且拖动过程中要暂停自动播放否则会打架。var isDragging false; $(#progressHandle).on(mousedown, function(e) { isDragging true; pauseAutoPlay(); e.preventDefault(); }); $(document).on(mousemove, function(e) { if (!isDragging) return; var $wrap $(#progressWrap); var offsetX e.pageX - $wrap.offset().left; var percent Math.min(1, Math.max(0, offsetX / $wrap.width())); var targetIndex Math.round(percent * (timelineData.length - 1)); // 拖动时先只更新 UI不触发完整跳转 updateProgress(targetIndex, timelineData.length); }); $(document).on(mouseup, function() { if (isDragging) { isDragging false; var percent parseFloat($(#progressBar).css(width)) / 100; var targetIndex Math.round(percent * (timelineData.length - 1)); gotoIndex(targetIndex); startAutoPlay(); } });这里有几个细节拖动过程要e.preventDefault()避免触发文本选择或原生拖拽行为松手之后再真正执行跳转因为拖动过程中频繁切换节点会让页面很闪。3. 核心功能实现细节3.1 时间轴 DOM 渲染渲染部分我用的 jQuery 字符串拼接这种方式在数据量不大时性能足够而且比模板引擎更直观。以纵向时间轴为例每条记录的 HTML 结构大致是div classtimeline-item>function renderTimeline(items) { var html ; $.each(items, function(i, item) { html div classtimeline-item>function gotoIndex(index) { var len timelineData.length; if (index 0) index 0; if (index len) index len - 1; currentIndex index; $(.timeline-item).each(function(i) { if (i index) { $(this).addClass(passed); } else { $(this).removeClass(passed); } }); $(.timeline-item) .eq(currentIndex) .addClass(active) .siblings() .removeClass(active); updateProgress(currentIndex, len); $(.timeline-item).eq(currentIndex)[0].scrollIntoView({ behavior: smooth, block: center }); }scrollIntoView是原生方法jQuery 里可以直接拿 DOM 对象调用不用额外插件。会自动把当前活动节点滚动到可视区域中间对纵向时间轴来说观感特别好。3.3 自动播放与定时器管理自动播放我用的setInterval每 3 秒切换一次。这里最容易踩的坑是“定时器重复开启”。用户先点了一次播放又点了一次播放如果每次都setInterval而不先clearInterval到后面一次 tick 会触发多次跳转进度条跟抽风一样。var playTimer null; var isPlaying false; function startAutoPlay() { if (isPlaying) return; isPlaying true; clearInterval(playTimer); playTimer setInterval(function() { if (currentIndex timelineData.length - 1) { gotoIndex(0); // 播完一轮回到开头 } else { gotoIndex(currentIndex 1); } }, 3000); $(#playBtn).text(暂停); } function pauseAutoPlay() { isPlaying false; clearInterval(playTimer); playTimer null; $(#playBtn).text(播放); }另外要注意当用户拖拽进度条或点击某个节点时一定要先暂停自动播放等操作完成后再决定是否恢复。否则定时器回调里用的currentIndex是旧值会出现“刚点到第 5 个节点下一秒又跳回第 6 个”的诡异现象。3.4 日期的格式化与排序后端返回的数据经常不保证按日期排序。我习惯在渲染前统一做一次排序并格式化为指定格式function normalizeData(rawList) { var list rawList.slice(); list.sort(function(a, b) { return new Date(a.date) - new Date(b.date); }); $.each(list, function(i, item) { var d new Date(item.date); var year d.getFullYear(); var month (d.getMonth() 1).toString().padStart(2, 0); var day d.getDate().toString().padStart(2, 0); item.date year - month - day; }); return list; }日期统一用YYYY-MM-DD格式还有一个隐藏好处这种格式下字符串排序和日期排序结果是一致的后面如果做筛选功能直接拿字符串比较也行。4. 从零写一个最小可用版本4.1 引入 jQuery因为老项目通常不接构建工具我直接用 CDN 引入 jQuery。哪怕是新项目用轻量场景也完全够用script srchttps://cdn.jsdelivr.net/npm/jquery3.7.1/dist/jquery.min.js/script如果你所在环境无法访问外网 CDN建议下载 jQuery 文件到本地然后用相对路径引入。这也是热词里“jquery的js下载”对应的实际场景——下载时记得选 min 版本能省下不少体积页面加载更快。4.2 页面骨架与样式我写了一个纵向时间轴的最小页面结构div idapp div idprogressWrap div idprogressBar/div div idprogressHandle/div /div div classprogress-info span idplayBtn classbtn播放/span span idcurrentDate/span span idprogressText0%/span /div div idtimelineList/div /divCSS 的核心是用伪元素画时间轴中线圆点用绝对定位放在中线上。这里特别提示时间轴中线不要用左边距加 border 的方式硬画最好用::before伪元素居中定位这样在不同屏幕宽度下都能自动居中。#timelineList { position: relative; padding: 20px 0; } #timelineList::before { content: ; position: absolute; left: 50%; top: 0; bottom: 0; width: 2px; background: #e2e6ed; transform: translateX(-50%); } .timeline-item { position: relative; width: 50%; padding: 20px 40px 20px 0; } .timeline-item:nth-child(even) { margin-left: 50%; padding: 20px 0 20px 40px; } .timeline-dot { position: absolute; right: -6px; top: 30px; width: 12px; height: 12px; background: #4f8ff7; border-radius: 50%; border: 2px solid #fff; } .timeline-item:nth-child(even) .timeline-dot { right: auto; left: -6px; }这种左右交替布局是时间轴组件的常见形态但如果你的节点描述文字很长交替布局反而不好阅读。后来我在实际项目里增加了配置项layout: alternate | left默认用左侧单排布局只有重要节点才用交替样式。4.3 完整交互代码我这里给一版最小可用的核心交互代码把播放、暂停、上一项、下一项、点击节点跳转都包含进去$(function() { var rawData [ { date: 2024-01-15, title: v2.0 全新改版上线, desc: 重构首页布局 }, { date: 2024-03-22, title: v2.3 性能优化, desc: 首屏加载降低 45% }, { date: 2024-06-10, title: v2.6 移动端适配, desc: 补齐小程序端核心流程 }, { date: 2024-09-05, title: v3.0 数据大屏, desc: 新增实时数据可视化面板 } ]; var timelineData normalizeData(rawData); var currentIndex 0; var playTimer null; var isPlaying false; var total timelineData.length; renderTimeline(timelineData); gotoIndex(0); $(#playBtn).on(click, function() { if (isPlaying) { pauseAutoPlay(); } else { startAutoPlay(); } }); $(#prevBtn).on(click, function() { gotoIndex(currentIndex - 1); }); $(#nextBtn).on(click, function() { gotoIndex(currentIndex 1); }); $(#timelineList).on(click, .timeline-item, function() { var index parseInt($(this).attr(data-index), 10); gotoIndex(index); }); function gotoIndex(index) { /* 见上文 */ } function startAutoPlay() { /* 见上文 */ } function pauseAutoPlay() { /* 见上文 */ } function updateProgress(index, total) { /* 见上文 */ } function renderTimeline(items) { /* 见上文 */ } });代码逻辑很直白但有一点值得强调事件绑定用事件代理。点击.timeline-item时我绑定在#timelineList上而不是直接给每个.timeline-item绑click。这样做的好处是后续用滚动加载更多往列表里追加新节点时新节点不需要重新绑定事件天然就能响应点击。这个习惯建议所有 jQuery 项目都养成。4.4 扩展滚动到底部加载更多时间轴数据一多比如超过 50 条一次性渲染会导致页面很长DOM 节点也多。我在组件里加了“滚动到底部加载更多”的逻辑首次只渲染前 10 条滚动到容器底部时再追加下一批。实现思路不复杂$(window).on(scroll, function() { var scrollTop $(window).scrollTop(); var windowHeight $(window).height(); var docHeight $(document).height(); if (scrollTop windowHeight docHeight - 100) { loadMoreItems(); } }); function loadMoreItems() { if (isLoading || currentRenderCount timelineData.length) return; isLoading true; var nextCount currentRenderCount 10; var html ; for (var i currentRenderCount; i nextCount i timelineData.length; i) { html buildTimelineItemHtml(timelineData[i], i); } $(#timelineList).append(html); currentRenderCount Math.min(nextCount, timelineData.length); isLoading false; }这里isLoading是防重复加载的关键。滚动事件触发频率非常高如果不加锁可能在一次滚动里触发多次loadMoreItems导致数据重复追加。加一个 isLoading 标志后在本次渲染完成前忽略后续请求基本就不会出问题。5. 常见问题与排查技巧5.1 进度条不动或宽度不更新这是最多的反馈。排查顺序我一般这样来确认total是否大于 1如果只有一条数据按total - 1计算会除以 0需要特殊处理。确认$(#progressBar)是否能选中元素很多情况是页面里同一个id出现两次jQuery 只返回第一个。确认css(width, percent %)的父容器宽度是否正常如果父容器是display: none或宽度为 0百分比设置无效。另外如果你在 Chrome 开发者工具里看到 progressBar 的宽度有值但页面没变化十有八九是 CSS 里写死了width: 50%之类的高优先级规则。加样式时记得用更具体的类名或加!important验证不要一上来就怪 jQuery。5.2 自动播放出现重复跳转很大概率是定时器被重复创建。我自己的习惯是写一个resetTimer()方法在任何可能触发播放状态改变的操作前都调用function resetTimer() { if (playTimer) { clearInterval(playTimer); playTimer null; } }然后点击进度条、拖拽进度、点击节点、切换数据源时第一行都先resetTimer()。这样即使状态乱了至少定时器只有一个。5.3 时间轴节点错位左右交替布局下如果某个节点的内容特别长很容易撑破布局。我建议给.timeline-content设置max-width和overflow-wrap: break-word.timeline-content { max-width: 100%; overflow-wrap: break-word; word-break: break-word; }还有个更隐蔽的坑当nth-child(even)对节点做左右交替时如果你中间加了“加载更多”按钮它会成为列表的子元素参与奇偶计算导致后续节点位置全部错乱。解决方法是把“加载更多”按钮放到列表容器外或者使用nth-of-type专门指定.timeline-item类型。5.4 接口返回 JSON 解析失败热词里有人搜“jquery content type怎么设置”我猜是遇到接口返回 JSON 但一直进不了 success 回调。常见原因是服务端返回的 Content-Type 不是application/jsonjQuery 没法自动识别。两个解决办法服务端能改就改响应头返回Content-Type: application/json; charsetutf-8。服务端不能改时在$.ajax里显式指定dataType: json强迫 jQuery 按 JSON 解析。$.ajax({ url: /api/timeline, type: GET, dataType: json, success: function(res) { // 正常处理 }, error: function(xhr, status, err) { console.log(status, err); } });如果用的后端框架返回的是字符串但内容长得像 JSON也会报错。先console.log(res)看输出类型比一直瞎猜来得快。5.5 播放到最后一个节点后进度条“掉头”这个其实不是 bug是业务逻辑不同。有的产品希望播完一轮自动回到第一个节点重新播有的希望停在最后一个节点并显示“已完成”。我建议做成可配置项var config { loop: true, // 循环播放 playInterval: 3000, // 播放间隔 ms initialIndex: 0 // 初始节点 };loop: false时播完最后一节点就自动暂停进度条保持 100%同时播放按钮文案变为“重新播放”。用户再次点击时回到起点重新播放。6. 一个提升体验的小技巧最后分享一个实际使用中让我觉得体验感提升很大的细节在节点切换时给内容卡片加一点淡入动画但不要给时间轴圆点加动画。原因是圆点的 active 状态切换需要“立刻可见”淡入会让人觉得反应慢半拍而内容卡片加淡入能缓解信息突然切换的突兀感。.timeline-content { transition: opacity 0.3s ease, transform 0.3s ease; } .timeline-item.active .timeline-content { opacity: 1; transform: translateY(0); } .timeline-item:not(.active) .timeline-content { opacity: 0.6; transform: translateY(8px); }这种“非活动节点半透明、离得远一点”的视觉处理比单纯标红当前点要更有空间感。用户扫一眼就知道播放进度走到哪了配合进度条百分比双重信息提示基本不会再有人问“现在播到第几个了”。这套组件的完整思路就是这样。核心其实不复杂就是数据、渲染、状态、定时器四样东西的配合。难点在于把细节抠到位进度条计算用什么做分母、拖拽和自动播放怎么互斥、滚动加载如何防止重复请求、节点布局如何应对长文本……把这些点都收拾干净一个看起来简单的时间轴也能做得稳定好用。如果你需要源码做参考把上面几个代码块按顺序拼起来就是一个最小可运行版本剩下的样式和交互都可以按自己的项目情况继续扩展。本文还有配套的精品资源点击获取
返回列表