<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在 JavaScript 中,當事情準時發生時,很自然地會想到使用計時器函數。 但是,當某件事由於其他事情依賴於它而在準確的時刻發生時,你很快就會發現計時器會存在一個不準時的問題。而本文所要介紹的 Web Animations API
可以在某些情況下替代計時器函數,同時保持精確。
當你需要處理精確的視覺呈現時,你就會發現你花費了太多時間來解決 JavaScript 無法準確解決程式碼何時實際執行的問題。
例如,下面就舉了一個計時器準確性的問題。
在 JavaScript 中,每個任務都會經過一個佇列。 包括你的程式碼、使用者互動、網路事件等都會放入各自的任務佇列,進行事件迴圈處理。 這麼做能夠保證任務按順序發生。例如,當事件觸發或計時器到期時,你在回撥中定義的任務將進入到佇列。 一旦事件迴圈輪到了它,你的程式碼就會被執行。
可是,當在任務佇列中執行計數器函數時,問題就會暴露了。
在將任務放入佇列之前,我們可以準確定義超時應該等待多長時間。 但是,我們無法預測的是目前佇列中會出現什麼。這是因為 setTimeout 保證在將事物放入佇列之前的最小延遲。 但是沒有辦法知道佇列中已經有什麼。
曾經我不得不為一個網站實現隨機翻轉圖塊,其中一個錯誤是由休眠標籤引起的。 因為每個圖塊都有自己的計時器,所以當標籤啟用時,它們都會同時觸發。那個案例如下程式碼所示:
<article id="demo"> <section> <h3>Timeouts</h3> <div class="row"> <div class="square"> <div></div> <div></div> </div> <div class="square"> <div></div> <div></div> </div> <div class="square"> <div></div> <div></div> </div> <div class="square"> <div></div> <div></div> </div> <div class="square"> <div></div> <div></div> </div> <div class="square"> <div></div> <div></div> </div> </div> </section> <section> <h3>Animations</h3> <div class="row"> <div class="square"> <div></div> <div></div> </div> <div class="square"> <div></div> <div></div> </div> <div class="square"> <div></div> <div></div> </div> <div class="square"> <div></div> <div></div> </div> <div class="square"> <div></div> <div></div> </div> <div class="square"> <div></div> <div></div> </div> </div> </section><button type="button">‣ Run</button> </article>
#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 引入了時間線的概念。 預設情況下,所有動畫都與檔案的時間軸相關聯。 這意味著動畫共用相同的“內部時鐘”——即從頁面載入開始的時鐘。
共用時鐘使我們能夠協調動畫。無論是某種節奏還是一種模式,你都不必擔心某些事情會延遲或超前發生。
要使動畫在某個時刻開始,請使用 startTime
屬性。 startTime 的值以頁面載入後的毫秒數為單位。 開始時間設定為 1000.5
的動畫將在檔案時間軸的 currentTime
屬性等於 1000.5
時開始播放。
你是否注意到開始時間值中的小數點了嗎? 是的,你可以使用毫秒的分數來精確時間。 但是,精確度取決於瀏覽器設定。
另一個有趣的事情是開始時間也可以是負數。 你可以自由地將其設定為未來的某個時刻或過去的某個時刻。 將該值設定為 -1000
,你的動畫狀態就像頁面載入時已經播放了一秒鐘一樣。 對於使用者來說,動畫似乎在他們甚至還沒有考慮存取你的頁面之前就已經開始播放了。
下面我們給出一個範例一起來看下如何使用 Web Animations API。
這個例子是一個精確計時的時鐘,程式碼如下:
<template id="tick"> <div class="tick"><span></span></div> </template> <template id="digit"><span class="digit" style="--len: 10;"><span></span></span></template> <div id="analog-clock"> <div class="hour-ticks"></div> <div class="minute-ticks"></div> <div class="day"></div> <div class="hand second"><div class="shadow"></div><div class="body"></div></div> <div class="hand minute"><div class="shadow"></div><div class="body"></div></div> <div class="hand hour"><div class="shadow"></div><div class="body"></div></div> <div class="dot"></div> </div> <div id="digital-clock"> <span class="hours"></span><span>:</span><span class="minutes"></span><span>:</span><span class="seconds"></span><span>.</span><span class="milliseconds"></span> </div>
: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 = clock.querySelector(".minute-ticks"); const day = clock.querySelector(".day"); hour_marks_container.append(...range(24).map(function (ignore, index) { const tick = tick_template.content.cloneNode(true); const shifted = index + 1; tick.firstElementChild.style.setProperty("--index", shifted); tick.firstElementChild.firstElementChild.textContent = shifted; return tick; })); minute_marks_container.append(...range(60).map(function (ignore, index) { const tick = tick_template.content.cloneNode(true); tick.firstElementChild.style.setProperty("--index", index); tick.firstElementChild.firstElementChild.remove(); return tick; })); }()); (function build_digital_clock() { const clock = document.getElementById("digital-clock"); const hours = clock.querySelector(".hours"); const minutes = clock.querySelector(".minutes"); const seconds = clock.querySelector(".seconds"); const milliseconds = clock.querySelector(".milliseconds"); hours.append(digits(24)); minutes.append(digits(6), digits()); seconds.append(digits(6), digits()); milliseconds.append(digits(), digits(), digits()); }()); (function start_analog_clock() { const clock = document.getElementById("analog-clock"); if (clock === null) { return; } const second = clock.querySelector(".second"); const minute = clock.querySelector(".minute"); const hour = clock.querySelector(".hour"); const hands = [second, minute, hour]; const hand_durations = [m, h, d]; const steps = [60, 60, 120]; const movement = []; hands.forEach(function (hand, index) { const duration = hand_durations[index]; const easing = `steps(${steps[index]}, end)`; movement.push(hand.animate( [ {transform: "rotate(0turn)"}, {transform: "rotate(1turn)"} ], {duration, iterations: Infinity, easing} )); const shadow = hand.querySelector(".shadow"); if (shadow) { movement.push(shadow.animate( [ {transform: "rotate(1turn) translate(3px) rotate(0turn)"}, {transform: "rotate(0turn) translate(3px) rotate(1turn)"} ], {duration, iterations: Infinity, iterationStart: 0.9, easing} )); } }); movement.forEach(function (move) { move.startTime = start_time; }); }()); (function start_digital_clock() { const clock = document.getElementById("digital-clock"); if (clock === null) { return; } const milliseconds = clock.querySelector(".milliseconds"); const seconds = clock.querySelector(".seconds"); const minutes = clock.querySelector(".minutes"); const hours = clock.querySelector(".hours"); const sections = [seconds, minutes]; const durations = [s, m, h]; const animations = []; Array.from( milliseconds.children ).reverse().forEach(function (digit, index) { animations.push(digit.firstElementChild.animate( single_digit_keyframes, { duration: ms * (10 ** (index + 1)), iterations: Infinity, easing: "steps(10, end)" } )); }); sections.forEach(function (section, index) { Array.from( section.children ).forEach(function (digit) { const nr_digits = digit.firstElementChild.children.length; animations.push(digit.firstElementChild.animate( single_digit_keyframes, { duration: ( nr_digits === 10 ? durations[index] * 10 : durations[index + 1] ), iterations: Infinity, easing: `steps(${nr_digits}, end)` } )); }); }); Array.from(hours.children).forEach(function (digit) { const nr_digits = digit.firstElementChild.children.length; animations.push( digit.firstElementChild.animate( double_digit_keyframes, { duration: d, iterations: Infinity, easing: `steps(${nr_digits}, end)` } ) ); }); animations.forEach(function (animation) { animation.startTime = start_time; }); }()); (function set_up_date_complication() { const day = document.querySelector(".day"); if (day === null) { return; } function month() { const now = new Date(); return digits( (new Date(now.getFullYear(), now.getMonth() + 1, 0)).getDate(), false ); } function create_animation(digit) { const nr_digits = digit.firstElementChild.children.length; const duration = d * nr_digits; return digit.firstElementChild.animate( double_digit_keyframes, { duration, easing: `steps(${nr_digits}, end)`, iterationStart: (d * ((new Date()).getDate() - 1)) / duration } ); } const new_day = day.cloneNode(); new_day.append(month()); day.replaceWith(new_day); Array.from(new_day.children).forEach(function (digit) { const complication = create_animation(digit); complication.startTime = start_time; complication.finished.then(set_up_date_complication); }); }());
效果如下:
因為時鐘是一種精密儀器,所以我讓秒針和分針在它們對應的值發生變化的那一刻改變它們的位置。 下面的程式碼說明了如何進行精確計時:
const clock = document.getElementById("analog-clock"); const second = clock.querySelector(".second"); const minute = clock.querySelector(".minute"); const hour = clock.querySelector(".hour"); const s = 1000; const m = s * 60; const h = m * 60; const d = h * 24; const hands = [second, minute, hour]; const hand_durations = [m, h, d]; const steps = [60, 60, 120]; const movement = hands.map(function (hand, index) { return hand.animate( [ {transform: "rotate(0turn)"}, {transform: "rotate(1turn)"} ], { duration: hand_durations[index], iterations: Infinity, easing: `steps(${steps[index]}, end)` } ); }); movement.forEach(function (move) { move.startTime = start_time; });
秒針每轉一圈需要 60000 毫秒,而分針比秒針慢 60 倍。
為了將時鐘指標的操作與相同的時間概念聯絡起來(以確保分針在秒針完成旋轉的那一刻準確地更新其位置),我使用了 startTime
屬性。
另一方面,數位時鐘有點違反直覺。每個數位都是一個帶有溢位的容器:overflow: hidden;
。在裡面,有一排從零到一的數位坐在等寬的單元格中。通過將行水平平移單元格的寬度乘以數位值來顯示每個數位。與模擬時鐘上的指標一樣,這是為每個數位設定正確持續時間的問題。雖然從毫秒到分鐘的所有數位都很容易做到,但小時數需要一些技巧。
讓我們看一下 start_time
變數的值:
const start_time = (function () { const time = new Date(); const hour_diff = time.getHours() - time.getUTCHours(); const my_current_time = (Number(time) % d) + (hour_diff * h); return document.timeline.currentTime - my_current_time; }());
為了計算所有元素必須開始的確切時間,我取了 Date.now()
的值(自 1970 年 1 月 1 日以來的毫秒數),從中去掉一整天,並通過 與 UTC 時間的差異。 這給我留下了自今天開始以來經過的毫秒數。 這是我的時鐘需要顯示的唯一資料:小時、分鐘和秒。
為了將該值轉換為正常格式,我需要根據從載入此頁面到呼叫 Date.now()
所經過的時間來調整它。 為此,我從 currentTime
中減去它。
動畫共用相同的時間參考,通過調整它們的 startTime
屬性,你可以將它們與你需要的任何模式對齊。
Web Animations API 帶有強大的 API,可讓你顯著減少工作量。 它還具有精確度,為實現一些需要精確性的應用程式提供了可能性。
希望我在本文中提供的範例能讓你更好地瞭解它。
以上就是Web Animations API實現一個精確計時的時鐘範例的詳細內容,更多關於Web Animations API時鐘計時的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45