您现在的位置是:网站首页> 编程资料编程资料
Web Animations API实现一个精确计时的时钟示例_JavaScript_
2023-05-24
408人已围观
简介 Web Animations API实现一个精确计时的时钟示例_JavaScript_
正文
在 JavaScript 中,当事情准时发生时,很自然地会想到使用计时器函数。 但是,当某件事由于其他事情依赖于它而在准确的时刻发生时,你很快就会发现计时器会存在一个不准时的问题。而本文所要介绍的 Web Animations API 可以在某些情况下替代计时器函数,同时保持精确。
当你需要处理精确的视觉呈现时,你就会发现你花费了太多时间来解决 JavaScript 无法准确解决代码何时实际执行的问题。
例如,下面就举了一个计时器准确性的问题。
JavaScript 计时器问题
在 JavaScript 中,每个任务都会经过一个队列。 包括你的代码、用户交互、网络事件等都会放入各自的任务队列,进行事件循环处理。 这么做能够保证任务按顺序发生。例如,当事件触发或计时器到期时,你在回调中定义的任务将进入到队列。 一旦事件循环轮到了它,你的代码就会被执行。
可是,当在任务队列中执行计数器函数时,问题就会暴露了。
低精度
在将任务放入队列之前,我们可以准确定义超时应该等待多长时间。 但是,我们无法预测的是目前队列中会出现什么。这是因为 setTimeout 保证在将事物放入队列之前的最小延迟。 但是没有办法知道队列中已经有什么。
曾经我不得不为一个网站实现随机翻转图块,其中一个错误是由休眠标签引起的。 因为每个图块都有自己的计时器,所以当标签激活时,它们都会同时触发。那个案例如下代码所示:
Timeouts
Animations
#demo { display: flex; background-color: white; color: black; flex-flow: column nowrap; align-items: center; padding: 2rem; gap: 2rem; } .row { display: flex; gap: 0.5rem; } .square { display: flex; width: 5rem; height: 5rem; position: relative; transform-style: preserve-3d; } .square > * { flex: 1 0 100%; -webkit-backface-visibility: hidden; backface-visibility: hidden; background-color: green; } .square > *:last-child { background-color: rgb(227, 227, 0); position: absolute; width: 100%; height: 100%; transform: rotateY(0.5turn); } (function () { "use strict"; const flip_keyframe = { transform: [ "rotateX(0turn)", "rotateX(0.5turn)", ] }; const timing_options = { duration: 1000, fill: "forwards" } function create(element) { const animation = element.animate(flip_keyframe, timing_options); animation.pause(); return animation; } function reset(animation) { animation.pause(); animation.currentTime = 0; } const id = "demo"; const demo = document.getElementById(id); const sections = demo.querySelectorAll("section"); const first_row_animations = Array.from( sections[0].lastElementChild.children ).map(create); const second_row_animations = Array.from( sections[1].lastElementChild.children ).map(create); const button = document.querySelector("button"); button.addEventListener("click", function (event) { const start_time = document.timeline.currentTime; first_row_animations.forEach(reset); second_row_animations.forEach(reset); first_row_animations.forEach(function (animation, index) { setTimeout(function () { animation.play(); }, 250 * index); }); second_row_animations.forEach(function (animation, index) { animation.startTime = start_time + (250 * index); }); setTimeout(function () { const start = Date.now(); while (Date.now() - start < 400) {} }, 500); }); }()); 
为了解决这个问题,我想到了 Web Animations API。
Web Animations API
Web Animations API 引入了时间线的概念。 默认情况下,所有动画都与文档的时间轴相关联。 这意味着动画共享相同的“内部时钟”——即从页面加载开始的时钟。
共享时钟使我们能够协调动画。无论是某种节奏还是一种模式,你都不必担心某些事情会延迟或超前发生。
开始时间
要使动画在某个时刻开始,请使用 startTime 属性。 startTime 的值以页面加载后的毫秒数为单位。 开始时间设置为 1000.5 的动画将在文档时间轴的 currentTime 属性等于 1000.5 时开始播放。
你是否注意到开始时间值中的小数点了吗? 是的,你可以使用毫秒的分数来精确时间。 但是,精确度取决于浏览器设置。
另一个有趣的事情是开始时间也可以是负数。 你可以自由地将其设置为未来的某个时刻或过去的某个时刻。 将该值设置为 -1000,你的动画状态就像页面加载时已经播放了一秒钟一样。 对于用户来说,动画似乎在他们甚至还没有考虑访问你的页面之前就已经开始播放了。
下面我们给出一个示例一起来看下如何使用 Web Animations API。
示例:精确计时的时钟
这个例子是一个精确计时的时钟,代码如下:
::.
:root { --face-size: 15rem; } body { display: flex; flex-direction: column; align-items: center; justify-content: center; font-family: sans-serif; } body > * { margin: 1rem; } #analog-clock { width: var(--face-size); height: var(--face-size); position: relative; border: 3px solid #555; border-radius: 50%; font-weight: 400; } .dot { --size: 9px; position: absolute; left: calc(50% - calc(var(--size) / 2)); top: calc(50% - calc(var(--size) / 2)); width: var(--size); height: var(--size); background-color: #333; border-radius: 50%; filter: drop-shadow(1px 1px 1px #333); } .hand { position: absolute; bottom: 50%; left: calc(50% - calc(var(--width) / 2)); width: var(--width); transform-origin: center bottom; } .hand > * { position: absolute; height: 100%; width: 100%; border-radius: 4px; } .hand .body { background-color: #333; } .hand .shadow { background-color: black; opacity: 0.2; filter: drop-shadow(0 0 1px black); } .second { --width: 1px; height: 50%; transform-origin: center 80%; margin-bottom: calc(var(--face-size) * -0.1) } .second .body { background-color: black; } .minute { --width: 3px; height: 35%; } .hour { --width: 5px; height: 25%; } .day { --size: 2ch; position: absolute; left: calc(50% - calc(var(--size) / 2)); top: calc(50% - calc(var(--size) / 2)); width: var(--size); height: var(--size); transform: translate(calc(var(--face-size) * 0.2)); } .tick { --width: 2px; --height: 29px; --shift: translateY(calc(var(--face-size) / -2)); position: absolute; width: var(--width); height: var(--height); background-color: #666; top: 50%; left: calc(50% - calc(var(--width) / 2)); transform-origin: top center; } .tick > span { --width: calc(calc(var(--face-size) * 3.141592653589793) / 24); position: absolute; width: var(--width); top: 3px; left: calc(var(--width) / -2); text-align: center; } .hour-ticks .tick:nth-child(even) > span { display: none; } .hour-ticks .tick:nth-child(odd) { background: none; } .hour-ticks .tick { transform: rotate(calc(var(--index) * 15deg)) var(--shift); } .minute-ticks .tick { --width: 1px; --height: 5px; --shift: translateY(calc(var(--face-size) / -2.5)); background-color: black; transform: rotate(calc(var(--index) * 6deg)) var(--shift); } .minute-ticks .tick:nth-child(5n+1) { display: none; } #digital-clock { font-size: 1.5rem; line-height: 1; } #digital-clock > span { display: inline-block; vertical-align: top; } .digit { display: inline-block; overflow: hidden; max-width: 1ch; } .digit.wide { max-width: 2ch; } .digit > span { display: inline-flex; align-items: flex-start; } .digit.wide > span > span { min-width: 2ch; text-align: right; } .day .digit > span > span { text-align: center; } const ms = 1; const s = ms * 1000; const m = s * 60; const h = m * 60; const d = h * 24; const start_time = (function () { const time = new Date(); const document_time = document.timeline.currentTime; const hour_diff = time.getHours() - time.getUTCHours(); const current_time = (Number(time) % d) + (hour_diff * h); return document_time - current_time; }()); const single_digit_keyframes = [ {transform: "translateX(0)"}, {transform: "translateX(calc(var(--len, 10) * -1ch)"} ]; const double_digit_keyframes = [ {transform: "translateX(0)"}, {transform: "translateX(calc(var(--len) * -2ch)"} ]; function range(len) { return new Array(len).fill(true); } function digits(len = 10, zero_based = true) { const digit = document.getElementById("digit").content.cloneNode(true); digit.firstElementChild.style.setProperty("--len", len); digit.firstElementChild.firstElementChild.append( ...range(len).map(function (ignore, index) { const span = document.createElement("span"); span.textContent = zero_based ? index : index + 1; return span; }) ); if (len > 10) { digit.firstElementChild.classList.add("wide"); } return digit; } (function build_analog_clock() { const clock = document.getElementById("analog-clock"); const tick_template = document.getElementById("tick"); const hour_marks_container = clock.querySelector(".hour-ticks"); const minute_marks_container
相关内容
- element-table如何实现自定义表格排序_vue.js_
- 解决element-ui中Popconfirm气泡确认框的事件不生效问题_vue.js_
- JavaScript实现简易QQ聊天界面_javascript技巧_
- JavaScript实现QQ聊天室功能_javascript技巧_
- 解决element-ui的table表格控件表头与内容列不对齐问题_vue.js_
- JavaScript制作简单计算器功能_javascript技巧_
- ElementUI中利用table表格自定义表头Tooltip文字提示_vue.js_
- 关于element中表格和表单的封装方式_vue.js_
- 微信小程序自动化部署的全过程_javascript技巧_
- 使用Element实现表格表头添加搜索图标和功能_vue.js_
点击排行
本栏推荐
