发布于 2026-01-06 3 阅读
0

数字动画

数字动画

本教程将探讨如何创建一个用于数字动画的小型 Web 组件,以及在现代浏览器上使其正常运行需要注意的所有陷阱。

我们将要建造的东西如下:

动画歌曲

免责声明:上面的 GIF 动画并未显示动画的所有步骤——而动画完成后的效果要好得多!

HTML

在 HTML 中,我们将使用:

<ui-number start="7580" end="8620" duration="2000"></ui-number>
Enter fullscreen mode Exit fullscreen mode

其他属性包括iteration,可以是-1“无穷大”或任何正整数,以及suffix,可以是百分比符号、货币符号或类似符号。

duration单位为毫秒。

接下来,我们来看 JavaScript 部分。

JavaScript

我们网页组件的基础是:

class uiNumber extends HTMLElement {
  constructor() {
    super();
    if (!uiNumber.adopted) {
      const adopted = new CSSStyleSheet();
      adopted.replaceSync(`
        ... styles here ...
      `);
      document.adoptedStyleSheets = 
      [...document.adoptedStyleSheets, adopted];
      uiNumber.adopted = true;
    }
  }
}
uiNumber.adopted = false
customElements.define('ui-number', uiNumber);
Enter fullscreen mode Exit fullscreen mode

uiNumber.adopted这样可以确保我们需要添加的全局样式只添加一次。我们也可以CSS.registerProperty在 JavaScript 中使用样式表,但由于我们需要添加更多样式,而且这些样式也应该只声明一次,所以我们将坚持使用已引入的样式表。

接下来,我们需要获取我们在 HTML 中声明的所有属性:

const start = parseInt(this.getAttribute('start'));
const end = parseInt(this.getAttribute('end'))||1;
const iteration = parseInt(this.getAttribute('iteration'))||1;
const suffix = this.getAttribute('suffix');
const styles = [
`--num: ${start}`,
`--end: ${end}`,
`--duration: ${parseInt(this.getAttribute('duration'))||200}ms`,
`--iteration: ${iteration===-1 ? 'infinite':iteration}`,
`--timing: steps(${Math.abs(end-start)})`
]
Enter fullscreen mode Exit fullscreen mode

现在,让我们向组件的 shadowDOM添加styles一些辅助标签:<span>

this.attachShadow({ mode: 'open' }).innerHTML = `
<span part="number" style="${styles.join(';')}">${
  suffix ? `<span part="suffix">${suffix}</span>`:''}
</span>`;
Enter fullscreen mode Exit fullscreen mode

注意part="number"(和part="suffix"),这将允许我们通过 CSS 定位元素:host::part(number)

接下来是一些跨浏​​览器兼容性修复。由于 Firefox 和 Safari 存在一些问题,我们需要为每个实例创建一个自定义样式表:

const stylesheet = new CSSStyleSheet();
stylesheet.replaceSync(`
  :host::part(number) {
    animation: N var(--duration, 2s) /* more */);
  }
  @keyframes N { to { --num: ${end}; } }
`);
Enter fullscreen mode Exit fullscreen mode

第一个——animation如果将其移至全局采用的样式表,则会破坏 Safari 中的功能。

关键帧中的值${end}应该是var(--end, 10),但这在 Firefox 中不起作用。而且由于插入的是一个实际的唯一数字,所以@keyframes也无法移动!

那么,全局样式表可以添加什么呢?可以添加以下内容:

@property --num {
  syntax: '<integer>';
  initial-value: 0;
  inherits: false;
}
ui-number::part(number) { counter-reset: N var(--num); }
ui-number::part(number)::before { content: counter(N); }
Enter fullscreen mode Exit fullscreen mode

现在,剩下的就是将实例样式表添加到shadowRoot

this.shadowRoot.adoptedStyleSheets = [stylesheet];
Enter fullscreen mode Exit fullscreen mode

以上就是网页组件的全部内容。如果您想尝试一下,请将以下代码添加到页面中:

Here's my <ui-number start="7580" end="8620"></ui-number> number
<script src="https://browser.style/ui/number/index.js" type="module"></script>
Enter fullscreen mode Exit fullscreen mode

— 你将获得:

行内编号

数字是内联的,并且在实例挂载后立即开始动画。让我们用animation-timelineCSS 创建一个更炫酷的组件吧!


动画时间线

首先,让我们用一些额外的标记将组件包裹起来:

<div class="ui-number-card">
  <ui-number start="7580" end="8620" duration="2000"></ui-number>
  <p>Millions of adults have gained literacy skills in the last decade.</p>
</div>
Enter fullscreen mode Exit fullscreen mode

CSS代码如下:

:where(.ui-number-card) {
  aspect-ratio: 1/1;
  background-color: #CCC;
  padding-block-end: 2ch;
  padding-inline: 2ch; 
  text-align: center;
  & p { margin: 0; }
  & ui-number {
    font-size: 500%;
    font-variant-numeric: tabular-nums;
    font-weight: 900;
    &::part(number) {
      --playstate: var(--scroll-trigger, running);
    }
    &::part(suffix) {
      font-size: 75%;
    }
  }
}
@keyframes trigger {
  to { --scroll-trigger: running; }
}
@supports (animation-timeline: view()) {
  :where(.ui-number-card) {
    --scroll-trigger: paused;
    animation: trigger linear;
    animation-range: cover;
    animation-timeline: view();
  }
}
Enter fullscreen mode Exit fullscreen mode

大部分是基本样式设置,重点在于:

--playstate: var(--scroll-trigger, running);
Enter fullscreen mode Exit fullscreen mode

在这里,我们将动画的播放状态设置为另一个属性,然后@keyframes在包装器的动画中更新该属性.ui-number-card

动画位于一个代码块内,因此我们仅在实际支持的情况下(目前仅支持 Chrome)@supports才会控制和运行“暂停/运行”状态。在其他浏览器(例如 Firefox 和 Safari)中,数字动画将立即运行。animation-timeline


演示

您可以在这里查看演示——或者您可以复制/粘贴此代码片段,并在您自己的代码中尝试调整参数:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="https://browser.style/base.css">
    <link rel="stylesheet" href="https://browser.style/ui/number/ui-number.css">
    <style>.ui-number-card{max-width:320px}</style>
</head>
<body>
  <div class="ui-number-card">
    <ui-number start="7580" end="8620" duration="2000"></ui-number>
    <p>Millions of adults have gained literacy skills in the last decade.</p>
  </div>
  <script src="https://browser.style/ui/number/index.js" type="module"></script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

为什么不直接使用 JavaScript 呢?

该网页组件需要 JavaScript,那么为什么不也用 JavaScript 来实现数字动画呢?

JavaScript 是单线程的——就像一条单车道高速公路。我们能把越多的东西转移到 CSS(以及 GPU)上,就能在这条高速公路上跑得越快。在我看来,这要好得多——而且与真正的高速公路不同,这里没有速度限制!

在这种情况下,我们只使用 JavaScript 来初始化和挂载组件实例。所有繁重的工作都由 CSS 完成。


附录

感谢@efpage,我得以制作一些基于 JS 的随机彩色计数器(按右下角重新运行):

封面照片由 Mateusz Dach 拍摄:https://www.pexels.com/da-dk/foto/332835/

文章来源:https://dev.to/madsstoumann/animating-numbers-1oc8