ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

原生JavaScript实现点击显示时间与闪烁联动效果

原生JavaScript实现点击显示时间与闪烁联动效果 前几天在整理前端交互素材时看到一句话“哈哈。点击观看时间如何证明联动闪。”乍一看是短视频标题但作为前端开发者我却从中读到了一个很常见的交互需求用户点击一个按钮页面另一块区域立即显示当前时间并通过闪烁高亮提醒用户“这里更新了”。本文就把这个“点击 → 时间 → 联动 → 闪烁”完整拆开用原生 JavaScript 一步步实现同时结合时间格式化、动画复位、防抖与服务器时间等细节帮你彻底吃透这个场景。如果你正在学习 DOM 事件、Date 对象、CSS 动画或者想做一个带“更新提示”的小工具页面这篇文章会比较适合你。代码不需要安装框架和依赖复制到本地就能跑。1. 从一个标题拆解需求时间联动闪烁是什么1.1 一句话看懂这个效果所谓“时间如何证明联动闪”你可以把它理解成三个动作的串联用户点击页面上的按钮系统获取当前时间并把它显示在另一个区域目标区域出现短暂的高亮闪烁效果提醒用户“内容已经更新”。这种交互在业务系统中非常常见。最典型的是“刷新提示”后台数据更新后前端收到消息把某个模块的数据替换成最新内容同时让模块边框闪烁一下。再比如数据大屏里监控指标发生变化时对应卡片会短暂高亮方便运维人员快速定位异常点。1.2 三个核心技术点这个案例虽然小但涉及三个基础且高频的知识点事件监听通过addEventListener绑定按钮点击行为时间处理使用Date对象获取、格式化当前时间动画联动通过操作 DOM 的 class 属性触发 CSS 动画。这三个知识点几乎每个前端项目都会用到。理解清楚后你可以把同样思路迁移到 Vue、React 或者原生小程序中只是写法略有差异核心逻辑不变。1.3 为什么用原生 JavaScript 实现很多同学一上来就想用 Vue 或 React。但如果你还在学习阶段我建议先用原生 JavaScript 把原理走通。原因很简单原生代码没有框架语法糖能让你看清“点击后到底发生了什么”DOM 操作、class 切换、动画触发都是框架底层做的事情先理解底层再学框架会轻松很多这个场景只有几十行代码用原生实现反而更轻量学完可以直接复制到任意 HTML 页面中使用。下面我们直接开始搭建。2. 环境准备与项目结构2.1 运行环境本文示例基于现代浏览器Chrome、Edge、Firefox、Safari和 ES6 语法环境运行不需要安装任何第三方包。你需要准备的只有两样东西一个浏览器一个文本编辑器VS Code、记事本、WebStorm 都可以。如果你当前操作系统支持 Python可以用 Python 自带一个本地静态服务方便后面调试# 进入项目目录后执行 python3 -m http.server 8080然后浏览器访问http://localhost:8080。如果你用的是 VS Code也可以安装 Live Server 插件右键 HTML 文件选择 Open with Live Server效果一样方便。2.2 目录结构我们先创建一个名为time-flash-demo的文件夹结构如下time-flash-demo/ ├── index.html ├── css/ │ └── style.css ├── js/ │ └── main.js目录结构清晰是为了把 HTML、CSS、JavaScript 分离方便后续维护。你完全可以把 CSS 和 JS 直接写在一个 HTML 里但小项目更应该养成“结构、样式、逻辑分离”的习惯后面代码变多时优势很明显。2.3 文件职责说明index.html页面骨架负责展示按钮、时间文本和目标区域css/style.css页面样式以及闪烁动画的定义js/main.js事件绑定、时间获取、DOM 更新和动画触发逻辑。接下来我们先从 HTML 开始。3. 核心概念拆解事件、时间与动画在写完整代码前我先把三个核心概念拆开讲清楚。别急着复制代码理解这些细节能帮你少踩很多坑。3.1 事件监听addEventListener 的使用浏览器交互都是事件驱动的。用户点击按钮、按下键盘、移动鼠标浏览器都会产生一个事件对象。原生 JavaScript 绑定点击事件的标准写法是const btn document.getElementById(flashBtn); btn.addEventListener(click, function () { console.log(按钮被点击了); });这里有两个细节需要注意第一addEventListener比 HTML 里的onclick...更推荐。它允许同一个事件绑定多个处理函数不会互相覆盖而且可以通过removeEventListener解绑控制更灵活。第二回调函数里的this指向会随调用方式变化。如果你使用箭头函数this会继承外层作用域。这里建议先使用普通函数避免初学阶段被this绕晕。3.2 时间对象与时间戳Date 与 Date.nowJavaScript 中获取当前时间最常用的是new Date()。const now new Date(); console.log(now.toString()); console.log(now.getTime());new Date()得到一个 Date 对象里面保存了当前系统的日期和时间now.getTime()返回从 1970 年 1 月 1 日 00:00:00 UTC 到当前时间的毫秒数也就是时间戳。时间戳是一个纯数字适合传给后端做时间比较也适合存储。但是直接展示给用户时需要把它格式化成年月日时分秒的字符串。值得注意的是new Date()获取的是用户电脑本地时间。如果用户电脑系统时间不准页面上显示的时间也会不准。所以后面我们会在工程建议里提到涉及关键业务时建议以服务器时间为准。3.3 时间格式化避免出现 8:3:5很多人第一次获取时间时会直接拼接结果得到类似8:3:5这种不好看的结果。原因是getHours()、getMinutes()、getSeconds()返回的是数字凌晨 8 点 3 分 5 秒时分和秒不会自动补零。解决办法是写一个补零函数function padZero(num) { return String(num).padStart(2, 0); }padStart(2, 0)是 ES6 字符串方法意思是如果字符串长度不足 2 位就在开头用0补齐。这样8会变成083会变成03。3.4 CSS 动画与 class 切换提到闪烁效果很多同学会想到 JavaScript 里的setInterval定时修改背景色。但更推荐的做法是把动画写在 CSS 里JavaScript 只负责切换 class。这样做的好处是职责分离CSS 负责“怎么闪”颜色变化、频率、次数JavaScript 负责“什么时候闪”点击时新增 class动画结束后移除。示例 CSS.target-box.flash { animation: flashBorder 1s ease-in-out 3; } keyframes flashBorder { 0% { border-color: #ff6b6b; background-color: #fff3f3; } 50% { border-color: #f59f00; background-color: #fff9db; } 100% { border-color: #ddd; background-color: #fff; } }这里animation后面的参数含义是动画时长 1 秒缓动函数ease-in-out播放 3 次。3.5 动画复位为什么动画只触发一次很多同学写完代码后会发现一个问题第一次点击按钮闪烁效果正常第二次点击数字已经更新了但闪烁效果不出现。原因很简单目标元素已经拥有flash这个 class第二次再添加时class 并没有发生“从无到有”的变化浏览器认为样式没变动画自然不会重新触发。解决办法是先把flash移除让浏览器重新计算样式然后再添加flash。targetBox.classList.remove(flash); void targetBox.offsetWidth; targetBox.classList.add(flash);这里的void targetBox.offsetWidth是一个小技巧。读取offsetWidth会触发浏览器强制回流也就是让浏览器立即计算一次页面布局。这一步执行后flash类才算真正被移除了后面的添加操作才能重新触发动画。这个坑非常经典在 Vue 中也有对应解法比如通过key属性强制重新渲染。知道原理后你以后遇到类似问题就能举一反三。4. 完整实战点击获取时间并联动闪烁接下来我们实现完整案例。我先把文件结构重申一遍time-flash-demo/ ├── index.html ├── css/ │ └── style.css └── js/ └── main.js4.1 创建 index.html在项目目录下新建index.html内容如下!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title时间联动闪烁 Demo/title link relstylesheet hrefcss/style.css /head body main classcontainer h1点击观看时间如何证明联动闪/h1 p classdesc点击下方按钮目标区域会同步显示当前时间并闪烁提醒。/p div classaction-area button idflashBtn typebutton点击获取当前时间/button span idclickTime classclick-time尚未点击/span /div div idtargetBox classtarget-box span idtargetTime--:--:--/span /div /main script srcjs/main.js/script /body /html说明flashBtn是触发按钮clickTime是按钮右侧的提示文字记录点击状态targetBox是需要闪烁的目标容器targetTime是目标容器里显示时间的文本节点。这里把script标签放在body末尾是为了保证 DOM 加载完成后再执行 JavaScript避免出现document.getElementById找不到元素的问题。如果你非要把script放在head里就必须额外监听DOMContentLoaded事件。4.2 创建 css/style.css新建css文件夹然后创建style.css* { box-sizing: border-box; } body { margin: 0; font-family: Microsoft YaHei, PingFang SC, Helvetica, Arial, sans-serif; background-color: #f7f9fc; } .container { max-width: 640px; margin: 80px auto; padding: 24px; text-align: center; background-color: #fff; border-radius: 16px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08); } .desc { color: #666; } .action-area { margin: 24px 0; display: flex; align-items: center; justify-content: center; gap: 16px; flex-wrap: wrap; } button { padding: 12px 24px; font-size: 16px; cursor: pointer; border: none; border-radius: 6px; background-color: #4c6ef5; color: #fff; transition: background-color 0.2s; } button:hover { background-color: #3b5bdb; } button:active { background-color: #2f4ac2; } .click-time { color: #888; font-size: 14px; } .target-box { margin: 32px auto 0; width: 280px; height: 120px; border: 2px solid #ddd; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 28px; font-weight: 600; color: #333; transition: border-color 0.3s; } .target-box.flash { animation: flashBorder 0.8s ease-in-out 3; } keyframes flashBorder { 0% { border-color: #ff6b6b; background-color: #fff3f3; transform: scale(1); } 50% { border-color: #f59f00; background-color: #fff9db; transform: scale(1.03); } 100% { border-color: #ddd; background-color: #fff; transform: scale(1); } }这里重点看一下.target-box.flash。它只在元素同时具备target-box和flash两个 class 时生效负责启动动画。动画里我加入了一个小小的transform: scale缩放让闪烁效果更明显。需要注意transform是动画性能优化中比较推荐的属性它不会触发整页布局重排比动画一个元素的width或top要高効很多。4.3 创建 js/main.js新建js文件夹然后创建main.js(function () { use strict; const flashBtn document.getElementById(flashBtn); const clickTime document.getElementById(clickTime); const targetBox document.getElementById(targetBox); const targetTime document.getElementById(targetTime); function padZero(num) { return String(num).padStart(2, 0); } function formatNow(date) { const hours padZero(date.getHours()); const minutes padZero(date.getMinutes()); const seconds padZero(date.getSeconds()); return hours : minutes : seconds; } function triggerFlash() { targetBox.classList.remove(flash); void targetBox.offsetWidth; targetBox.classList.add(flash); } function handleClick() { const now new Date(); const timeText formatNow(now); targetTime.textContent timeText; clickTime.textContent 点击时间 timeText 时间戳 now.getTime() ; triggerFlash(); } flashBtn.addEventListener(click, handleClick); })();代码整体是一个立即执行函数目的是不污染全局变量。逐行看核心逻辑getElementById获取四个页面元素padZero负责补零formatNow把 Date 对象格式化为HH:mm:ss字符串triggerFlash先移除动画 class强制回流后重新添加确保连续点击也能重新触发闪烁handleClick是事件处理函数负责更新时间文本、更新时间戳提示、触发闪烁。4.4 运行与验证在项目目录下启动本地服务python3 -m http.server 8080或者使用 VS Code 的 Live Server 插件。浏览器访问页面后你会看到按钮、时间和目标区域。第一次点击按钮目标区域的--:--:--会变成当前时间并且边框闪烁 3 次。点击五次、十次你会发现每次点击后时间都会更新闪烁都会重新触发不会出现第二次无效的情况。如果你在 Chrome 开发者工具的 Console 面板里执行document.getElementById(targetTime).textContent可以看到当前目标区域的最新时间文本说明 DOM 内容确实被更新了。4.5 编码规范细节这段代码虽然短但有几个好习惯值得学习变量统一提到上面方便维护函数职责单一formatNow只负责格式化triggerFlash只负责闪烁使用textContent而不是innerHTML因为这里只需要纯文本textContent更安全不容易被注入 HTML 标签。如果你所在的团队要求更严格还可以把triggerFlash和formatNow等函数封装到单独的模块文件里。但当前项目规模下一个文件已经足够清晰。5. 进阶扩展多目标、服务器时间与防抖基础版本跑通后我建议你再做三个方向的扩展。这些扩展很贴近真实业务需求也是面试中容易被追问的点。5.1 多按钮联动基于>button classflash-btn>const buttons document.querySelectorAll(.flash-btn); function handleMultiClick(event) { const targetId event.currentTarget.dataset.target; const targetBox document.getElementById(targetId); const timeText formatNow(new Date()); targetBox.textContent targetId : timeText; targetBox.classList.remove(flash); void targetBox.offsetWidth; targetBox.classList.add(flash); } buttons.forEach(function (btn) { btn.addEventListener(click, handleMultiClick); });这种做法的好处是将来新增一个“磁盘”监控区域只需要在 HTML 里加一个按钮和一个目标区域不需要改 JavaScript 逻辑。5.2 使用服务器时间校验本地时间可能因为用户系统设置不准确而出现偏差。在业务记录“最后刷新时间”“更新时间”时最好以后端返回时间为准。前端用法很简单后端返回一个时间戳前端直接用new Date(timestamp)创建时间对象fetch(/api/current-time) .then(function (res) { return res.json(); }) .then(function (data) { const serverDate new Date(data.timestamp); targetTime.textContent formatNow(serverDate); }) .catch(function () { targetTime.textContent 获取服务器时间失败; });这里的关键点是理解“时间戳是相对 UTC 的绝对时间”不受时区影响。你只需要把服务器返回的毫秒时间戳传给Date构造函数浏览器会自动转换成本地时区显示。如果后端返回的是秒需要先乘以 1000 再传给Date。5.3 点击频率控制防抖与节流用户可能快速连续点击按钮十几次每次都触发动画和 DOM 更新虽然这个案例里代价不大但如果是请求服务器接口就会造成资源浪费。常见的两种方案防抖用户不断点击时只执行最后一次节流用户不断点击时每隔一段时间只执行一次。简单的防抖实现function debounce(fn, delay) { let timer null; return function () { const context this; const args arguments; if (timer) { clearTimeout(timer); } timer setTimeout(function () { fn.apply(context, args); }, delay); }; } const handleClickDebounced debounce(function () { console.log(执行更新); }, 300); flashBtn.addEventListener(click, handleClickDebounced);需要注意加了防抖后用户点击按钮不会立刻看到反馈所以交互设计上要权衡。高频点击场景建议用“节流”保证至少每隔一段时间执行一次而不是一直拖到最后。6. 常见问题与排查思路下面整理几个我在实际开发中遇到过的问题也是新手问得比较多的地方。问题现象常见原因解决思路点击按钮后没有反应JS 文件路径错误或元素 id 不匹配打开浏览器控制台查看报错确认getElementById的 id 与 HTML 中一致时间显示为NaN:NaN:NaN传入Date构造函数的参数格式不正确确认是毫秒时间戳还是秒秒需要乘以 1000闪烁效果只生效一次没有移除flashclass动画无法重新触发先remove再读取offsetWidth强制回流最后add页面一打开就自动闪在 JS 中直接调用了闪烁函数而不是在事件回调中调用检查是否漏写了事件绑定或错误地把函数执行结果赋给了事件按钮点击后页面刷新button没有设置typebutton在表单中触发了默认提交行为表单内的按钮建议显式指定typebutton闪烁效果卡顿动画了width、height、top等布局属性改用transform、opacity等不会触发重排的属性排查思路可以按这个顺序来先打开浏览器开发者工具看 Console 有没有报错再看 Network 面板确认静态资源是否加载成功最后在事件处理函数里加console.log确认事件绑定是否生效。这三个步骤能解决大多数前端入门问题。7. 工程实践与性能建议7.1 封装组件思维虽然原生 JavaScript 写起来很直接但如果你要在多个页面复用这个时间联动闪烁功能建议封装成一个函数或类。下面是一个简单的封装示例思路function TimeFlash(options) { this.btn document.getElementById(options.btnId); this.target document.getElementById(options.targetId); this.timeText this.target.querySelector(.time-text); this.init(); } TimeFlash.prototype.init function () { var self this; this.btn.addEventListener(click, function () { self.updateTime(); }); }; TimeFlash.prototype.updateTime function () { var now new Date(); this.timeText.textContent formatNow(now); this.target.classList.remove(flash); void this.target.offsetWidth; this.target.classList.add(flash); };封装的好处是业务代码只关心“哪个按钮更新哪个区域”内部细节全部隐藏。不过 ES6 的class语法会更直观如果团队允许使用 ES6建议直接写 class。7.2 CSS 动画性能优化闪烁动画涉及背景色和边框颜色变化颜色变化会触发绘制但性能通常可以接受。不过在一个页面存在大量动画时要注意以下几点优先使用transform和opacity它们会进入合成器处理性能更好避免同时给几十个元素加高频率闪烁动画如果有用户喜欢减弱动作效果可以通过prefers-reduced-motion媒体查询关闭动画。CSS 写法示例media (prefers-reduced-motion: reduce) { .target-box.flash { animation: none; } }这个细节容易被忽略但能实际提升无障碍访问体验。7.3 可访问性与安全边界闪烁效果不能太刺眼。Web 无障碍规范中有一个建议页面任何区域在 1 秒内闪烁次数不应超过 3 次。所以我们把动画播放次数设计为 3 次时长控制在 1 秒左右这是一个比较稳妥的量级。此外如果你把innerHTML拿来做数据展示存在被注入脚本的风险。本文案例中所有可变数据都用textContent写入这是更安全的方式。如果你必须拼接 HTML请先对内容进行 HTML 转义。7.4 日志与错误收集在小型 Demo 中console.log足够用。但在正式项目中建议在关键节点埋点例如点击事件触发成功时间接口请求失败目标元素不存在。不要小看这些日志线上问题排查时一条有效的日志能帮你省下几个小时。8. 总结与后续学习方向实现以后再回头看标题其实很多看似随机的文案背后都藏着具体的交互诉求。“点击观看时间如何证明联动闪”拆开就是“点击事件 时间对象 状态联动 闪烁动画”这恰好是前端入门阶段非常值得练手的一个综合案例。你现在可以打开本地页面点击试试然后试着把目标区域换成列表、表格或者消息通知体验一下状态联动的乐趣。如果你想继续深入建议下一步学习这几个方向requestAnimationFrame实现更流畅的自定义动画事件委托用父级监听替代多个子元素监听状态管理思想在 Vue/React 中如何用响应式数据代替手动 DOM 操作服务器时间同步理解时间戳、时区和后端校验逻辑。如果你在跑这个案例时遇到报错或者想基于它扩展其他功能欢迎在评论区贴出你的代码片段和浏览器版本我们一起排查。
返回列表