欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Web?Animations?API實(shí)現(xiàn)一個(gè)精確計(jì)時(shí)的時(shí)鐘示例

 更新時(shí)間:2022年07月29日 11:31:22   作者:前端修羅場(chǎng)  
這篇文章主要為大家介紹了Web?Animations?API實(shí)現(xiàn)一個(gè)精確計(jì)時(shí)的時(shí)鐘示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

在 JavaScript 中,當(dāng)事情準(zhǔn)時(shí)發(fā)生時(shí),很自然地會(huì)想到使用計(jì)時(shí)器函數(shù)。 但是,當(dāng)某件事由于其他事情依賴于它而在準(zhǔn)確的時(shí)刻發(fā)生時(shí),你很快就會(huì)發(fā)現(xiàn)計(jì)時(shí)器會(huì)存在一個(gè)不準(zhǔn)時(shí)的問(wèn)題。而本文所要介紹的 Web Animations API 可以在某些情況下替代計(jì)時(shí)器函數(shù),同時(shí)保持精確。

當(dāng)你需要處理精確的視覺(jué)呈現(xiàn)時(shí),你就會(huì)發(fā)現(xiàn)你花費(fèi)了太多時(shí)間來(lái)解決 JavaScript 無(wú)法準(zhǔn)確解決代碼何時(shí)實(shí)際執(zhí)行的問(wèn)題。

例如,下面就舉了一個(gè)計(jì)時(shí)器準(zhǔn)確性的問(wèn)題。

JavaScript 計(jì)時(shí)器問(wèn)題

在 JavaScript 中,每個(gè)任務(wù)都會(huì)經(jīng)過(guò)一個(gè)隊(duì)列。 包括你的代碼、用戶交互、網(wǎng)絡(luò)事件等都會(huì)放入各自的任務(wù)隊(duì)列,進(jìn)行事件循環(huán)處理。 這么做能夠保證任務(wù)按順序發(fā)生。例如,當(dāng)事件觸發(fā)或計(jì)時(shí)器到期時(shí),你在回調(diào)中定義的任務(wù)將進(jìn)入到隊(duì)列。 一旦事件循環(huán)輪到了它,你的代碼就會(huì)被執(zhí)行。

可是,當(dāng)在任務(wù)隊(duì)列中執(zhí)行計(jì)數(shù)器函數(shù)時(shí),問(wèn)題就會(huì)暴露了。

低精度

在將任務(wù)放入隊(duì)列之前,我們可以準(zhǔn)確定義超時(shí)應(yīng)該等待多長(zhǎng)時(shí)間。 但是,我們無(wú)法預(yù)測(cè)的是目前隊(duì)列中會(huì)出現(xiàn)什么。這是因?yàn)?setTimeout 保證在將事物放入隊(duì)列之前的最小延遲。 但是沒(méi)有辦法知道隊(duì)列中已經(jīng)有什么。

曾經(jīng)我不得不為一個(gè)網(wǎng)站實(shí)現(xiàn)隨機(jī)翻轉(zhuǎn)圖塊,其中一個(gè)錯(cuò)誤是由休眠標(biāo)簽引起的。 因?yàn)槊總€(gè)圖塊都有自己的計(jì)時(shí)器,所以當(dāng)標(biāo)簽激活時(shí),它們都會(huì)同時(shí)觸發(fā)。那個(gè)案例如下代碼所示:

<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">&#8227; 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);
    });
}());

為了解決這個(gè)問(wèn)題,我想到了 Web Animations API

Web Animations API

Web Animations API 引入了時(shí)間線的概念。 默認(rèn)情況下,所有動(dòng)畫都與文檔的時(shí)間軸相關(guān)聯(lián)。 這意味著動(dòng)畫共享相同的“內(nèi)部時(shí)鐘”——即從頁(yè)面加載開(kāi)始的時(shí)鐘。

共享時(shí)鐘使我們能夠協(xié)調(diào)動(dòng)畫。無(wú)論是某種節(jié)奏還是一種模式,你都不必?fù)?dān)心某些事情會(huì)延遲或超前發(fā)生。

開(kāi)始時(shí)間

要使動(dòng)畫在某個(gè)時(shí)刻開(kāi)始,請(qǐng)使用 startTime 屬性。 startTime 的值以頁(yè)面加載后的毫秒數(shù)為單位。 開(kāi)始時(shí)間設(shè)置為 1000.5 的動(dòng)畫將在文檔時(shí)間軸的 currentTime 屬性等于 1000.5 時(shí)開(kāi)始播放。

你是否注意到開(kāi)始時(shí)間值中的小數(shù)點(diǎn)了嗎? 是的,你可以使用毫秒的分?jǐn)?shù)來(lái)精確時(shí)間。 但是,精確度取決于瀏覽器設(shè)置。

另一個(gè)有趣的事情是開(kāi)始時(shí)間也可以是負(fù)數(shù)。 你可以自由地將其設(shè)置為未來(lái)的某個(gè)時(shí)刻或過(guò)去的某個(gè)時(shí)刻。 將該值設(shè)置為 -1000,你的動(dòng)畫狀態(tài)就像頁(yè)面加載時(shí)已經(jīng)播放了一秒鐘一樣。 對(duì)于用戶來(lái)說(shuō),動(dòng)畫似乎在他們甚至還沒(méi)有考慮訪問(wèn)你的頁(yè)面之前就已經(jīng)開(kāi)始播放了。

下面我們給出一個(gè)示例一起來(lái)看下如何使用 Web Animations API。

示例:精確計(jì)時(shí)的時(shí)鐘

這個(gè)例子是一個(gè)精確計(jì)時(shí)的時(shí)鐘,代碼如下:

<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);
    });
}());

效果如下:

因?yàn)闀r(shí)鐘是一種精密儀器,所以我讓秒針和分針在它們對(duì)應(yīng)的值發(fā)生變化的那一刻改變它們的位置。 下面的代碼說(shuō)明了如何進(jìn)行精確計(jì)時(shí):

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;
});

秒針每轉(zhuǎn)一圈需要 60000 毫秒,而分針比秒針慢 60 倍。

為了將時(shí)鐘指針的操作與相同的時(shí)間概念聯(lián)系起來(lái)(以確保分針在秒針完成旋轉(zhuǎn)的那一刻準(zhǔn)確地更新其位置),我使用了 startTime 屬性。

另一方面,數(shù)字時(shí)鐘有點(diǎn)違反直覺(jué)。每個(gè)數(shù)字都是一個(gè)帶有溢出的容器:overflow: hidden;。在里面,有一排從零到一的數(shù)字坐在等寬的單元格中。通過(guò)將行水平平移單元格的寬度乘以數(shù)字值來(lái)顯示每個(gè)數(shù)字。與模擬時(shí)鐘上的指針一樣,這是為每個(gè)數(shù)字設(shè)置正確持續(xù)時(shí)間的問(wèn)題。雖然從毫秒到分鐘的所有數(shù)字都很容易做到,但小時(shí)數(shù)需要一些技巧。

讓我們看一下 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;
}());

為了計(jì)算所有元素必須開(kāi)始的確切時(shí)間,我取了 Date.now() 的值(自 1970 年 1 月 1 日以來(lái)的毫秒數(shù)),從中去掉一整天,并通過(guò) 與 UTC 時(shí)間的差異。 這給我留下了自今天開(kāi)始以來(lái)經(jīng)過(guò)的毫秒數(shù)。 這是我的時(shí)鐘需要顯示的唯一數(shù)據(jù):小時(shí)、分鐘和秒。

為了將該值轉(zhuǎn)換為正常格式,我需要根據(jù)從加載此頁(yè)面到調(diào)用 Date.now() 所經(jīng)過(guò)的時(shí)間來(lái)調(diào)整它。 為此,我從 currentTime 中減去它。

總結(jié)

動(dòng)畫共享相同的時(shí)間參考,通過(guò)調(diào)整它們的 startTime 屬性,你可以將它們與你需要的任何模式對(duì)齊。

Web Animations API 帶有強(qiáng)大的 API,可讓你顯著減少工作量。 它還具有精確度,為實(shí)現(xiàn)一些需要精確性的應(yīng)用程序提供了可能性。

希望我在本文中提供的示例能讓你更好地了解它。

以上就是Web Animations API實(shí)現(xiàn)一個(gè)精確計(jì)時(shí)的時(shí)鐘示例的詳細(xì)內(nèi)容,更多關(guān)于Web Animations API時(shí)鐘計(jì)時(shí)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論