数字动画
本教程将探讨如何创建一个用于数字动画的小型 Web 组件,以及在现代浏览器上使其正常运行需要注意的所有陷阱。
我们将要建造的东西如下:
免责声明:上面的 GIF 动画并未显示动画的所有步骤——而动画完成后的效果要好得多!
HTML
在 HTML 中,我们将使用:
<ui-number start="7580" end="8620" duration="2000"></ui-number>
其他属性包括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);
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)})`
]
现在,让我们向组件的 shadowDOM添加styles一些辅助标签:<span>
this.attachShadow({ mode: 'open' }).innerHTML = `
<span part="number" style="${styles.join(';')}">${
suffix ? `<span part="suffix">${suffix}</span>`:''}
</span>`;
注意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}; } }
`);
第一个——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); }
现在,剩下的就是将实例样式表添加到shadowRoot:
this.shadowRoot.adoptedStyleSheets = [stylesheet];
以上就是网页组件的全部内容。如果您想尝试一下,请将以下代码添加到页面中:
Here's my <ui-number start="7580" end="8620"></ui-number> number
<script src="https://browser.style/ui/number/index.js" type="module"></script>
— 你将获得:
数字是内联的,并且在实例挂载后立即开始动画。让我们用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>
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();
}
}
大部分是基本样式设置,重点在于:
--playstate: var(--scroll-trigger, running);
在这里,我们将动画的播放状态设置为另一个属性,然后@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>
为什么不直接使用 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