当前位置:网站首页>[practice] use the web animations API to realize a clock with accurate timing
[practice] use the web animations API to realize a clock with accurate timing
2022-07-28 04:25:00 【Front end Shura field】
stay JavaScript in , When things happen on time , It's natural to think of using timer functions . however , When something happens at the right time because other things depend on it , You will soon find that the timer is not on time . And what this article will introduce Web Animations API You can replace the timer function in some cases , While maintaining accuracy .
When you need to deal with Accurate visual presentation , You will find that you spend too much time to solve JavaScript It cannot accurately solve the problem of when the code actually executes .
for example , The following is a question about the accuracy of timers .
JavaScript Timer problem
stay JavaScript in , Each task will go through a queue . Include your code 、 User interaction 、 Network events will be put into their respective task queues , Perform event loop processing . Doing so ensures that tasks occur in sequence . for example , When the event triggers or the timer expires , The task you defined in the callback will enter the queue . Once the event cycle has its turn , Your code will be executed .
But , When the counter function is executed in the task queue , The problem will be exposed .
Low precision
Before putting the task in the queue , We can define exactly how long we should wait for the timeout . however , What we cannot predict is what will appear in the current queue . This is because setTimeout Ensure the minimum delay before putting things in the queue . But there is no way to know what is already in the queue .
Once I had to flip tiles randomly for a website , One of the errors is caused by the sleep tag . Because each tile has its own timer , So when the tag is activated , They all trigger at the same time . The case is shown in the following code :
<article id="demo">
<section>
<h2>Timeouts</h2>
<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>
<h2>Animations</h2>
<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);
});
}());

To solve this problem , I think of it. Web Animations API.
Web Animations API
Web Animations API The concept of timeline is introduced . By default , All animations are associated with the timeline of the document . This means that animations share the same “ Internal clock ”—— That is, the clock starting from page loading .
Sharing the clock enables us to coordinate animation . Whether it's a rhythm or a pattern , You don't have to worry that something will happen late or ahead .
Starting time
To make the animation start at a certain moment , Please use startTime attribute . startTime The value of is in milliseconds after the page is loaded . The start time is set to 1000.5 The animation of will be on the timeline of the document currentTime Attribute is equal to the 1000.5 Start playing .
Have you noticed the decimal point in the start time value ? Yes , You can use Fraction of milliseconds To pinpoint the time . however , Accuracy depends on browser settings .
Another interesting thing is The start time can also be negative . You are free to Set to a future moment or a past moment . Set the value to -1000, Your animation status is like it has been playing for a second when the page is loaded . For users , The animation seems to have started playing before they even consider visiting your page .
Let's give an example to see how to use Web Animations API.
Example : An accurate clock
This example is a clock with accurate timing , The code is as follows :
<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);
});
}());
The effect is as follows :
Because the clock is a kind of precision instrument , So I let the second and minute hands change their positions at the moment when their corresponding values change . The following code shows how to do precise timing :
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;
});
The second hand needs 60000 millisecond , And the minute hand is slower than the second hand 60 times .
In order to connect the operation of the clock pointer with the same concept of time ( To ensure that the minute hand accurately updates its position at the moment when the second hand completes its rotation ), I use the startTime attribute .
On the other hand , Digital clock is a little counterintuitive . Each number is a container with overflow :overflow: hidden;. On the inside , There is a row of numbers from zero to one sitting in cells of equal width . Each number is displayed by multiplying the width of the horizontal translation cell of the row by the numerical value . Like the pointer on the analog clock , This is the problem of setting the correct duration for each number . Although all numbers from milliseconds to minutes are easy to do , But hours require some skill .
Let's take a look start_time The value of the variable :
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;
}());
In order to calculate the exact time when all elements must start , I took Date.now() Value ( since 1970 year 1 month 1 Milliseconds in days ), Take out the whole day , And pass And UTC Time difference . This leaves me with the number of milliseconds since the beginning of today . This is the only data my clock needs to display : Hours 、 Minutes and seconds .
To convert this value to normal format , I need to load this page and call Date.now() The time it takes to adjust it . So , I started from currentTime Subtract it from .
summary
Animations share the same time reference , By adjusting their startTime attribute , You can align them with any pattern you need .
Web Animations API With powerful API, It can significantly reduce your workload . It also has accuracy , It provides the possibility to realize some applications that need accuracy .
I hope the example I provide in this article will give you a better understanding of it .
边栏推荐
- Network visualization: features of convolution kernel and CNN visualization (through the attention part of gradient visualization network)
- [reach out to Party welfare] the easiest way to scan the H5 page in wechat
- Cyber Nuwa, how to make digital people?
- About me writing a custom cell
- Combination of Oracle and Premier League statistics and presentation
- Power consumption: leakage power
- Null security and exception
- 金仓数据库KingbaseES安全指南--6.1. 强身份验证简介
- Linux - MySQL advanced (day19)
- DNS series (III): how to avoid DNS spoofing
猜你喜欢

The simulation test disconnects the server from the public network
![[yolov5 practice 5] traffic sign recognition system based on yolov5 -yolov5 integration pyqt5](/img/81/89b8e38801f706ef396943a79ef4c5.png)
[yolov5 practice 5] traffic sign recognition system based on yolov5 -yolov5 integration pyqt5

Machine learning 07: Bayesian learning

重要的 SQL Server 函数 - 数字函数
![[coding and decoding] Huffman coding and decoding based on Matlab GUI [including Matlab source code 1976]](/img/af/27e3794f93166d8ecad9b42dafaf0f.png)
[coding and decoding] Huffman coding and decoding based on Matlab GUI [including Matlab source code 1976]

What technical capabilities should a qualified software testing engineer have?

H265/hevc noun explanation -- CTU, CTB, Cu, CB, Tu, PU, TB, Pb, LCU, slice, tile, chroma, luma, I frame, B frame, P frame

23 openwrt switch VLAN configuration

Some personal understandings of openpose

虚拟机类加载机制
随机推荐
Machine learning 07: Bayesian learning
There are so many ways to view the web source code! Do you know?
Learn regular expressions (regexp)
Experience sharing of automatic test for students with monthly salary of 28K
Classification cluster analysis
Program life | test engineers only know a little? Seven shortcuts teach you to learn new technology quickly
金仓数据库KingbaseES安全指南--6.1. 强身份验证简介
Information system project manager (2022) - key content: Project Risk Management (11)
Detailed explanation of pl/sql parameters ("box model")
VAE generation model (with VAE implementation MNIST code)
21 days, fat brother personally takes you to play oauth2
校园流浪猫信息记录和分享的小程序源码
Esp8266 WiFi module and mobile communication
7/27(板子)染色法判定二分图+求组合数(递推公式)
【无标题】
The unsatisfied analysis of setup and hold timing is the solution
月薪28K学员 自动化测试经验分享
Chinese Remainder Theorem of X problem
Information system project manager (2022) - key content: Project Portfolio Management (19)
[untitled]