我最近发布了一门针对希望提高 Web 动画技能的设计师和开发人员的 CSS 动画课程。在制作课程时,我遇到了一个问题,即内容在资产下载之前就开始动画。本文介绍了我为解决这个问题并确保所有动画按预期播放而采取的方法。
我们都经历过这种情况。例如,我们希望在加载时淡入一个英雄标题,所以我们添加了淡入关键帧,设置动画,但动画在背景图像下载之前就开始了。我们看到一个半加载的图像淡入,更糟糕的是,我们的徽标或标题在背景准备好之前就出现了。
谢天谢地,我们有办法解决它。
问题:并行事件
当我们加载一个网站时,浏览器会尝试通过同时下载和渲染 HTML 和 CSS 以及其他资产(如图像)来尽可能地加快速度。
这种加载方式意味着我们可以更快地看到一些布局和文本内容,但如果你创建了一个大的杂志风格的标题,图像可能无法及时到达。
让我们使用一些内置的浏览器技巧来阻止动画,直到正确的时间。
load
事件和 animation-play-state
浏览器为我们提供了一个方便的 JavaScript load
事件,用于指示内容已完成加载。该事件将针对图像和脚本等元素触发。我们可以使用它来控制动画播放的时间。
我们将使用一些 JavaScript 来监听加载事件,并使用 animation-play-state
在事件发生之前暂停我们的动画。
以下 JavaScript 代码应该可以解决问题。
document.body.classList.add('js-loading');
window.addEventListener("load", showPage);
function showPage() {
document.body.classList.remove('js-loading');
}
代码的作用如下。第一行将 js-loading
类添加到 body
元素。然后它设置了一个事件监听器。
事件监听器会等待 load
事件发生,然后运行函数 removeLoadingClass
。此时,所有图像和其他资产都已下载。
最后,removeLoadingClass
从 body
标签中删除该类。
此代码应该添加到页面 HTML 中,例如 head
中。如果从外部文件加载,则 CSS 可能在该代码执行之前加载并解析,这将使动画有机会在我们准备就绪之前开始。
让我们使用此类让页面上的任何动画都等到内容准备就绪。
等待一张图像
这种方法会等待页面上的所有资产加载。你可能只想等待一张图像,例如标题中的图像。幸运的是,每个图像都有加载事件。在 在 JavaScript 中测量图像宽度 中介绍的方法可以提供帮助。
我们可以调整 JavaScript 代码,使其专注于一个特定的图像。
// Adjust the "querySelector" value to target your image
var img = document.querySelector("img");
document.body.classList.add('js-loading');
img.addEventListener("load", removeLoadingClass);
function removeLoadingClass() {
document.body.classList.remove('js-loading');
}
animation-play-state
属性
现代浏览器很好地支持 animation-play-state 属性。它告诉浏览器当前动画是正在运行还是暂停。默认情况下,动画是“运行”的。
我们可以使用此属性在加载内容时让页面上的所有动画“暂停”。我们将把它添加到我们的 CSS 中。
.js-loading *,
.js-loading *:before,
.js-loading *:after {
animation-play-state: paused !important;
}
此代码会在 body
具有 js-loading
类时,将页面上所有内容的播放状态设置为“暂停”。
它将确保它也适用于所有 :before
和 :after
伪元素。
当 JavaScript 从 body
标签中删除 js-loading
类时,该规则不再适用,所有动画都将处于预期的运行状态。
这种方法的一个好处是,我们不需要在样式表中更改任何其他内容!
如果 JavaScript 失败怎么办?
如果我们依靠 JavaScript 来处理屏幕上的内容显示,这始终是一个值得问的问题。有时 JavaScript 会失败。它可能被禁用。插件可能会做不可预测的事情。虽然在大多数情况下这将有效,但 JavaScript 始终是有点不受我们控制的,所以最好考虑一下如果 JavaScript 未按预期工作会发生什么。
在这种情况下,我认为我们没事。如果 JavaScript 没有运行,它就不会应用 js-loading
类。动画将立即播放。这可能会导致背景图像在动画时加载时出现一些怪异,但这是最坏的情况,也是一个合理的回退。
实际操作
让我们测试一下,看看它是否有效。我们需要一张大图片。我在 Unsplash 上找到了这张非常漂亮的 NASA 照片。它的尺寸超过了 2MB。
当页面加载时,我们应该会看到最初的空白屏幕。为了真正看到它在行动,我们可以使用 Chrome 的内置节流功能。打开检查器,选择“网络”选项卡,然后打开包含速度预设的下拉菜单。从此处,我们选择“良好 3G”,这应该足够慢,让我们看到它在行动。

在此演示中按“重新运行”,直到图像完全加载,否则没有任何动画会播放。
查看笔 使用 load 和 animation-play-state 等待图像加载 #2 由 Donovan Hutchinson (@donovanh) 在 CodePen 上编写。
尝试删除 JavaScript(并在重新加载之前清除缓存),以查看差异!
加载微调器
如果你想避免在此时出现空白页面,你应该使用一些加载文本或动画来让人们知道。这取决于你认为延迟时间有多长。如果你有一张非常大的图片,而且页面往往会等待超过一秒或两秒,那么加载微调器可能是个好主意。
另一方面,可能值得进一步压缩图像或将其缩小,以便它更快地加载。尝试一下,看看对你来说什么效果最好。
我希望这种技术可以帮助你让动画协同工作。
如果你想了解有关动画你的网站的更多信息,你可能会喜欢 CSSAnimation.rocks 上的教程。在 Twitter 上关注 @cssanimation。
感谢你对这个问题提供了清晰的解释和解决方案。特别是如果 JavaScript 由于任何原因失败,动画仍将执行,即使不是最佳执行方式。
这非常有用,谢谢。
我在使用 Javascript 时遇到了一些小问题,如果我将该代码放在头部,控制台会显示一个错误“未捕获的类型错误:无法读取空值的 'className' 属性” - 可能是因为 body 不会立即加载,所以它不会添加该类。
如果我将代码放在 body 中,它就可以工作。对此有什么解决方案吗?
谢谢
把它包装在一个 document ready 中,它也可以在头部工作。
document.addEventListener("DOMContentLoaded", function(event) {
//document.body.....
});
问题是浏览器从顶部读取页面。如果你在内容下方添加一个脚本标签,该代码将起作用,或者你可以将代码保留在头部,但将其包装在一个 DOMContentLoaded 事件回调中 (https://mdn.org.cn/en/docs/Web/Events/DOMContentLoaded) 或者 $( document ).ready()(如果你使用的是 jquery)。
嗨,史蒂芬,
好问题!由于此 JavaScript 需要尽快加载,我们可以将其放在中,但正如你所指出的,此时尚未加载。我们可以调整 JavaScript,而是将类放在页面的部分
此处,“document.documentElement” 部分在此情况下指的是 标签。它现在应该像以前一样工作。希望这有帮助!
Donovan
谢谢你们!
多诺万,这个解决方案完美地起作用了。
喜欢这个想法,但是使用任何一个示例,我都会在包含 `document.body.className += " js-loading";` 的行中遇到错误 `Uncaught TypeError: Cannot read property 'className' of null`…
我知道这可能很基础的 JS 知识,但我确实很基础。你知不知道为什么会出现这种情况?谢谢!
很酷的主题。
但添加和删除类的方式很糟糕。
考虑使用 `document.body.classList.add('js-loading')` 和 `document.body.classList.remove('js-loading')`!
继续黑客攻击 #DevTips
好主意 - 谢谢!
好文章。我只想指出,你不需要在 `addEventListener` 调用中使用 `false` 作为第三个参数,因为它是 `useCapture` 参数的默认值。
另外,我建议使用 ClassList API 来添加/删除元素的类。它非常方便。
你对直接在 JavaScript 中操作 `animation-play-state` 有什么想法吗?
我解决了这个问题,可以在不使用 JavaScript 的情况下让图像在开始动画之前加载,但解决方案也有其自身的缺点。
我对图像进行了 base-64 编码并将其内联在 HTML 中。这确保了图像已加载,因为它们是 HTML 的一部分,并且 CSS 只有在 HTML 下载完成后才会开始。
但是,你会使 HTML 膨胀,并且 base-64 编码的图像可能比它们原本更大。
在我的情况下,它对页面加载时间的影响可以忽略不计,但这对每个人来说可能不一样!
这非常有帮助,谢谢!