让我们来看看 CSS @keyframes
动画,特别是如何 **暂停** 以及其他控制方式。有一个专门用于此的 CSS 属性,可以通过 JavaScript 控制,但细节中有很多细微差别。我们还将看看我首选的设置方法,它提供了很多控制。提示:它涉及 CSS 自定义属性。
暂停动画的重要性
最近,在使用 CSS 制作的幻灯片(您将在本文后面看到)时,我检查了 DevTools 的 **图层** 面板中的动画。我注意到了一些之前从未想过的事情:当前不在视窗内的 动画仍在运行!
也许这并不那么令人意外。我们知道视频就是这样做的。视频会一直播放,直到您暂停它。但这让我开始思考这些正在播放的动画是否仍在使用 CPU/GPU?它们是否会消耗不必要的处理能力,从而降低页面其他部分的速度?
在 DevTools 中检查 **性能** 面板中的帧没有提供更多关于此的信息,因为我无法看到“屏幕外”帧。但是,当我向下滚动离开我的“仅使用 CSS 的幻灯片”的第一个幻灯片,然后等待并向后滚动时,它处于第五个幻灯片。动画并没有暂停。动画会一直运行,直到您暂停它们。
因此,我开始研究如何、为什么以及何时应该暂停动画。鉴于上面的发现,性能是一个明显的原因。另一个原因是控制。用户不仅喜欢拥有控制权,而且应该拥有控制权。几年前,我的妻子遭受了严重的脑震荡。从那以后,她一直在避免带有太多动画的网页,因为这些动画会让她头晕。因此,我认为可访问性可能是允许动画暂停的最重要的原因。
总而言之,这些都是很重要的事情。我们专门讨论 CSS 关键帧动画,但从广义上讲,这意味着我们正在讨论
- 性能
- 控制
- 可访问性
暂停动画的基础知识
在 CSS 中真正暂停动画的唯一方法是使用 animation-play-state
属性,其值为 paused
。
.paused {
animation-play-state: paused;
}
在 JavaScript 中,该属性为“驼峰式命名法”的 animationPlayState
,设置方法如下
element.style.animationPlayState = 'paused';
我们可以通过读取 animationPlayState
的当前值来创建一个播放和暂停动画的切换按钮
const running = element.style.animationPlayState === 'running';
…然后将其设置为相反的值
element.style.animationPlayState = running ? 'paused' : 'running';
设置持续时间
暂停动画的另一种方法是将 animation-duration
设置为 0s
。动画实际上在运行,但由于它没有持续时间,您不会看到任何动作。
但如果我们将其值改为 3s
它有效,但有一个主要缺点:动画在技术上仍在运行。动画只是在其初始位置和序列中的下一个位置之间切换。
直接移除动画
我们可以完全移除动画,并通过类将其添加回来,但与 animation-duration
一样,这实际上不会暂停动画。
.remove-animation {
animation: none !important;
}
由于我们真正想要的是真正的暂停,让我们坚持使用 animation-play-state
并研究其他使用方法。
使用数据属性和 CSS 自定义属性
让我们在 CSS 中使用 数据属性 作为选择器。我们可以随意命名它们,因此我将在所有想要播放/暂停动画的元素上使用 [data-animation]
属性。这样,它可以与其他动画区分开来
<div data-animation></div>
该属性是选择器,animation
简写是我们将设置所有内容的属性。我们将添加一些 CSS 自定义属性(使用 Emmet 简写)作为值
[data-animation] {
animation:
var(--animn, none)
var(--animdur, 1s)
var(--animtf, linear)
var(--animdel, 0s)
var(--animic, infinite)
var(--animdir, alternate)
var(--animfm, none)
var(--animps, running);
}
有了它,任何具有该数据属性的动画都将完全准备好接受动画,并且我们可以 使用自定义属性控制动画的各个方面。一些动画将有一些共同点(例如持续时间、缓动类型等),因此自定义属性上也设置了回退值。
为什么要使用 CSS 自定义属性?首先,它们可以在 CSS 和 JavaScript 中读取和设置。其次,它们可以显著减少我们需要编写的 CSS 量。而且,由于我们可以在 @keyframes
中设置它们(至少在撰写本文时在 Chrome 中可以这样做),它们提供了全新的工作方式来处理动画!
对于动画本身,我使用类选择器并更新来自 [data-animation]
选择器的变量
<div class="circle a-slide" data-animation></div>
为什么要使用类和数据属性?在此阶段,data-animation
属性也可以是普通类,但我们将在后面以更高级的方式使用它。请注意,.circle
类名实际上与动画无关 - 它只是一个用于为元素设置样式的类。
/* Animation classes */
.a-pulse {
--animn: pulse;
}
.a-slide {
--animdur: 3s;
--animn: slide;
}
/* Keyframes */
@keyframes pulse {
0% { transform: scale(1); }
25% { transform: scale(.9); }
50% { transform: scale(1); }
75% { transform: scale(1.1); }
100% { transform: scale(1); }
}
@keyframes slide {
from { margin-left: 0%; }
to { margin-left: 150px; }
}
我们只需要更新要更改的值,因此如果我们在 data-animation
选择器的回退值中使用一些通用值,我们只需要更新动画自定义属性 --animn
的名称。
示例:使用复选框技巧暂停
为了使用老式的 复选框技巧 暂停所有动画,让我们在动画之前创建一个复选框
<input type="checkbox" data-animation-pause />
并在 checked
时更新 --animps
属性
[data-animation-pause]:checked ~ [data-animation] {
--animps: paused;
}
就是这样!单击复选框时,动画会在播放和暂停之间切换 - 无需 JavaScript。
仅使用 CSS 的幻灯片
让我们将这些想法付诸实践!
我最近一直在使用 <details>
标签。它是制作 手风琴 的显而易见的候选者,但它也可以用于制作 工具提示、切换提示、下拉菜单(设计成 <select>
的外观) 、大型菜单……应有尽有。毕竟,它是官方的 HTML 公开元素。除了所有 HTML 元素都接受的全局属性和全局事件之外,<details>
只有一个 open
属性和一个 toggle
事件。因此,与复选框技巧一样,它非常适合切换状态 - 但更加简单
details[open] {
--state: 1;
}
details:not([open]) {
--state: 0;
}
我决定做一个幻灯片,其中幻灯片通过称为 autoplay
的主要动画自动切换,并且每个单独的幻灯片都有自己独特的次要动画。animation-play-state
由 --animps
属性控制。每个单独的幻灯片都可以有自己独特的动画,在 --animn
属性中定义
<figure style="--animn:kenburns-top;--index:0;">
<img src="some-slide-image.jpg" />
<figcaption>Caption</figcaption>
</figure>
次要动画的 animation-play-state
由 --img-animps
属性控制。我在 Animista 中找到了一些不错的 Ken Burns 风格的动画,并在幻灯片的 --animn
属性中切换它们。
从另一个动画暂停动画
为了防止 GPU 过载,理想情况下,主动画应该暂停任何辅助动画。我们之前简要提到了这一点,但只有 Chrome(在撰写本文时,而且它确实有点不稳定)可以从 @keyframe
动画更新 CSS 自定义属性——你可以在以下示例中看到,其中 --bgc
属性和 --counter
属性在不同的帧中被修改
辅助动画的初始状态,--img-animps
属性,需要处于 paused
状态,即使主动画正在运行
details[open] ~ .c-mm__inner .c-mm__frame {
--animps: running;
--img-animps: paused;
}
然后,在主动画 @keyframes
中,该属性更新为 running
@keyframes autoplay {
0.1% {
--img-animps: running; /* START */
opacity: 0;
z-index: calc(var(--z) + var(--slides))
}
5% { opacity: 1 }
50% { opacity: 1 }
51% { --img-animps: paused } /* STOP! */
100% {
opacity: 0;
z-index: var(--z)
}
}
为了使此功能在除 Chrome 之外的其他浏览器中也能正常运行,初始值需要设置为 running
,因为它们无法从 @keyframe
更新 CSS 自定义属性。
这是一个幻灯片,带有一个“细节技巧”播放/暂停按钮——不需要 JavaScript
prefers-reduced-motion
启用 有些人不喜欢动画,或者至少不喜欢过多的动画。这可能仅仅是个人喜好,但也可能是因为存在医疗状况。我们在这篇文章的开头就谈到了动画的无障碍性。
macOS 和 Windows 都有选项,允许用户告知浏览器他们更喜欢在网站上减少动画。这使我们能够使用 prefers-reduced-motion
特性查询,Eric Bailey 对此进行了全面介绍.
@media (prefers-reduced-motion) { ... }
让我们使用 [data-animation]
选择器来减少动画,通过为它提供不同的值来应用于启用 prefers-reduced-motion
时的情况:
alternate
= 运行不同的动画once
= 将animation-iteration-count
设置为 1slow
= 更改animation-duration
属性stop
= 将animation-play-state
设置为paused
这些只是建议,它们实际上可以是任何你想要的东西。
<div class="circle a-slide" data-animation="alternate"></div>
<div class="circle a-slide" data-animation="once"></div>
<div class="circle a-slide" data-animation="slow"></div>
<div class="circle a-slide" data-animation="stop"></div>
更新后的媒体查询
@media (prefers-reduced-motion) {
[data-animation="alternate"] {
/* Change animation duration AND name */
--animdur: 4s;
--animn: opacity;
}
[data-animation="slow"] {
/* Change animation duration */
--animdur: 10s;
}
[data-animation="stop"] {
/* Stop the animation */
--animps: paused;
}
}
如果这太笼统了,而你更喜欢对每个动画类都有唯一的备用动画,请像这样对选择器进行分组
.a-slide[data-animation="alternate"] { /* etc. */ }
这是一个使用复选框模拟 prefers-reduced-motion
的 Pen。在 Pen 中向下滚动以查看每个圆圈的行为变化
使用 JavaScript 暂停
要使用 JavaScript 重新创建“暂停所有动画”复选框,请遍历所有 [data-animation]
元素并切换相同的 --animps
自定义属性
<button id="js-toggle" type="button">Toggle Animations</button>
const animations = document.querySelectorAll('[data-animation');
const jstoggle = document.getElementById('js-toggle');
jstoggle.addEventListener('click', () => {
animations.forEach(animation => {
const running = getComputedStyle(animation).getPropertyValue("--animps") || 'running';
animation.style.setProperty('--animps', running === 'running' ? 'paused' : 'running');
})
});
这与复选框技巧完全相同,使用相同的自定义属性:--animps
,只是由 JavaScript 而不是 CSS 设置。如果我们想要支持旧版本的浏览器,我们可以切换一个类,它将更新 animation-play-state
。
IntersectionObserver
使用 为了自动播放和暂停所有 [data-animation]
动画——从而避免不必要地过载 GPU——我们可以使用 IntersectionObserver
。
首先,我们需要确保没有任何动画正在运行
[data-animation] {
/* Change 'running' to 'paused' */
animation: var(--animps, paused);
}
然后,我们将创建观察器并在元素在视窗中处于 25% 或 75% 时触发它。如果匹配到后者,动画将开始播放;否则它将暂停。
默认情况下,所有具有 [data-animation]
属性的元素都将被观察,但如果启用了 prefers-reduced-motion
(设置为“reduce”),则具有 [data-animation="stop"]
的元素将被忽略。
const IO = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const state = (entry.intersectionRatio >= 0.75) ? 'running' : 'paused';
entry.target.style.setProperty('--animps', state);
}
});
}, {
threshold: [0.25, 0.75]
});
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
const elements = mediaQuery?.matches ? document.querySelectorAll(`[data-animation]:not([data-animation="stop"]`) : document.querySelectorAll('[data-animation]');
elements.forEach(animation => {
IO.observe(animation);
});
你必须调整 threshold
值,以及是否需要在动画触发后取消观察某些动画等。如果你动态加载新内容或动画,你可能需要重写观察器的一些部分。不可能涵盖所有场景,但将此用作基础应该可以帮助你开始使用自动播放和暂停 CSS 动画!
<audio>
添加到幻灯片
奖励:使用最少的 JavaScript 将 这是一个将音乐添加到我们构建的幻灯片的想法。首先,添加一个 audio
标签
<audio src="/asset/audio/slideshow.mp3" hidden loop></audio>
然后,在 JavaScript 中
const audio = document.querySelector('your-audio-selector');
const details = document.querySelector('your-details-selector');
details.addEventListener('toggle', () => {
details.open ? audio.play() : audio.pause();
})
很简单,对吧?
我在这里做了一个“无声电影”(带音频)的演示,让你了解我极客的过去。🙂
这篇文章太棒了!
是否可以控制 css @keyframes 播放百分比?比如使用一个 input[type=”range”]。
谢谢!
是的,你可以通过设置一个负的“animation-delay”来实现这一点,然后让一个范围的“max”设置为 animation-duration。
然后,当你更新“animation-delay”时,将它设置为
0 - input.value
,这样你就可以得到负值。我在这里做了一个示例
早上好,Mads Stoumann 先生!
关于播放和暂停 css 动画的这个工作太棒了。
我不是 css 专家,但我想知道你是否拥有允许你播放、暂停和奖励星球大战开头文字的 css 动画的代码,就像这样,请!
星球大战 3D 动画 css。这样就可以播放、停止和奖励,以便更好地阅读!
非常感谢
https://www.google.com/search?q=star+wars+intro+css+animation+text+&tbm=isch&ved=2ahUKEwjCha-YxPnxAhVQsJUCHWrQA-oQ2-cCegQIABAA&oq=star+wars+intro+css+animation+text+ &gs_lcp=CgNpbWcQDFDBFFicamCsemgBcAB4AIABzwGIAa4KkgEGMTAuMi4xmAEAoAEBqgELZ3dzLXdpei1pbWfAAQE&sclient=img&ei=k-P6YILwJ9Dg1BR1W1z0B1QP6&sclient=img&ei=k-P6YILwJ9Dg1BR_1x8hr2&bQP6