视差效果介绍和使用
视差网站风靡一时,看看这些:
如果您不熟悉它们,它们就是页面的视觉结构在您滚动时发生变化的站点。 通常,页面内的元素与页面上的滚动位置成比例地缩放、旋转或移动。
我们的演示页面包含视差效果
您是否喜欢视差站点是一回事,但您可以非常自信地说它们是性能黑洞。 这样做的原因是,浏览器往往会针对滚动时(取决于滚动方向)出现在屏幕顶部或底部的新内容进行优化,一般来说,浏览器在变化很小的情况下工作得最好在滚动期间视觉上。 对于视差站点而言,这种情况很少见,因为整个页面上的大型视觉元素会发生多次变化,导致浏览器重新绘制整个页面。
像这样概括视差站点是合理的:
- 背景元素,当您上下滚动时,会改变它们的位置、旋转和比例。
- 页面内容,例如文本或较小的图像,以典型的从上到下的方式滚动。
我们之前介绍了 滚动性能 以及提高应用程序响应能力的方法,本文建立在此基础之上,因此如果您还没有这样做,可能值得一读。
所以问题是,如果您正在构建一个视差滚动站点,您是否陷入了昂贵的重绘,或者您是否可以采取其他方法来最大限度地提高性能? 让我们来看看我们的选择。
选项 1:使用 DOM 元素和绝对位置
这似乎是大多数人采用的默认方法。 页面中有一堆元素,每当触发滚动事件时,都会进行一堆视觉更新来转换它们。 我已经整理了一个 演示页面 ,它就是这样做的。
如果您在帧模式下启动 DevTools 时间轴并滚动,您会注意到有昂贵的全屏绘制操作,如果您滚动很多,您可能会在单个帧中看到多个滚动事件,每个事件都会触发布局工作。
DevTools 在单个框架中显示大型绘制和多个事件触发的布局。
要记住的重要一点是,要达到 60fps(与 60Hz 的典型显示器刷新率相匹配),我们只需超过 16 毫秒即可完成所有工作。 在第一个版本中,我们每次收到滚动事件时都会执行视觉更新,但正如我们在之前的文章中讨论的那样 ,使用 requestAnimationFrame 和 滚动性能 ,这与浏览器的更新计划不一致,所以我们要么错过帧,要么在每个帧中做太多的工作。 这很容易给您的网站带来不自然和不自然的感觉,从而导致用户失望和小猫不开心。
让我们将更新代码从滚动事件移到一个 requestAnimationFrame
回调并简单地在滚动事件的回调中捕获滚动值。 我们的 第二个演示 展示了这一点。
如果您重复滚动测试,您可能会注意到轻微的改进,尽管不多。 原因是我们通过滚动触发的布局操作并不是那么昂贵,但在其他用例中确实可能如此。 现在至少我们 一个布局 在每一帧
DevTools 在单个框架中显示大型绘制和多个事件触发的布局。
我们现在可以每帧处理 1 个或 100 个滚动事件,但至关重要的是,我们只存储最新的值,以便在 requestAnimationFrame
回调运行并执行我们的视觉更新。 关键是您已经从每次收到滚动事件时都尝试强制视觉更新转变为请求浏览器为您提供适当的窗口来执行这些更新。 你不甜吗?
这种方法的主要问题, requestAnimationFrame
与否,是因为我们基本上为整个页面设置了一层,并且通过移动这些视觉元素,我们需要大量(且昂贵)的重绘。 通常来说,绘画是一个阻塞操作(尽管这种情况正在 改变 ),这意味着浏览器不能做任何其他工作,而且我们经常超出框架的 16 毫秒预算,事情仍然很卡顿。
选项 2:使用 DOM 元素和 3D 变换
除了使用绝对位置,我们还可以采用另一种方法,即对元素应用 3D 变换。 在这种情况下,我们看到应用了 3D 变换的元素为每个元素赋予了一个新层,并且在 WebKit 浏览器中,它通常还会导致切换到硬件合成器。 相比之下,在选项 1 中,我们为页面设置了一个大图层,当发生任何变化时需要重新绘制该图层,并且所有绘制和合成都由 CPU 处理。
这意味着使用 此 选项时,情况会有所不同:我们可能为应用 3D 变换的任何元素设置一层。 如果我们从这一点上做的只是对元素进行更多的转换,我们就不需要重新绘制图层,GPU 可以处理移动元素并将最终页面合成在一起。
这是 另一个演示 正在使用的 3D 变换。 如果您滚动,您会发现情况有了很大改善。
很多时候人们只是使用 -webkit-transform: translateZ(0);
破解并看到神奇的性能改进,虽然这在今天有效,但存在问题:
- 它不是跨浏览器兼容的。
- 它通过为每个转换的元素创建一个新层来强制浏览器的手。 很多层会带来其他性能瓶颈,因此请谨慎使用!
- 已 某些 WebKit 端口 (倒数第四个!)。
如果你走 3D 翻译路线要小心,它是你问题的临时解决方案! 理想情况下,我们会从 2D 变换中看到与 3D 类似的渲染特征。 浏览器正在以惊人的速度发展,所以希望在这之前我们会看到。
最后,您应该尽可能避免绘画,并简单地在页面周围移动现有元素。 例如,视差网站的典型方法是使用固定高度的 div 并更改其背景位置以提供效果。 不幸的是,这意味着每次通过时都需要重新绘制元素,这可能会降低性能。 相反,如果可以的话,您应该创建元素(将其包装在 div 中 overflow: hidden
如有必要)并简单地翻译它。
选项 3:使用固定位置的画布或 WebGL
我们要考虑的最后一个选项是在页面后面使用一个固定位置的画布,我们将在其中绘制转换后的图像。 乍一看,这似乎不是最高效的解决方案,但这种方法实际上有一些好处:
- 由于只有一个元素,即画布,我们不再需要那么多的合成器工作。
- 我们正在有效地处理单个 硬件加速 位图。
- Canvas2D API 非常适合我们希望执行的转换,这意味着开发和维护更易于管理。
使用画布元素为我们提供了一个新层,但它只是 一个 层,而在选项 2 中,我们实际上为 每个 应用了 3D 变换的元素提供了一个新层,因此将所有这些层合成在一起的工作量增加了。 鉴于不同的跨浏览器转换实现,这也是当今最兼容的解决方案。
如果您查看 的演示 并在 DevTools 中对其进行测试,您会发现性能非常好。 对于这种方法,我们只需使用画布 drawImage
API 调用,我们给它我们的背景图像和我们的每个彩色斑点,以便在屏幕上的正确位置绘制。
/**
* Updates and draws in the underlying visual elements to the canvas.
*/
function updateElements () {
var relativeY = lastScrollY / h;
// Fill the canvas up
context.fillStyle = "#1e2124";
context.fillRect(0, 0, canvas.width, canvas.height);
// Draw the background
context.drawImage(bg, 0, pos(0, -3600, relativeY, 0));
// Draw each of the blobs in turn
context.drawImage(blob1, 484, pos(254, -4400, relativeY, 0));
context.drawImage(blob2, 84, pos(954, -5400, relativeY, 0));
context.drawImage(blob3, 584, pos(1054, -3900, relativeY, 0));
context.drawImage(blob4, 44, pos(1400, -6900, relativeY, 0));
context.drawImage(blob5, -40, pos(1730, -5900, relativeY, 0));
context.drawImage(blob6, 325, pos(2860, -7900, relativeY, 0));
context.drawImage(blob7, 725, pos(2550, -4900, relativeY, 0));
context.drawImage(blob8, 570, pos(2300, -3700, relativeY, 0));
context.drawImage(blob9, 640, pos(3700, -9000, relativeY, 0));
// Allow another rAF call to be scheduled
ticking = false;
}
/**
* Calculates a relative disposition given the page’s scroll
* range normalized from 0 to 1
* @param {number} base The starting value.
* @param {number} range The amount of pixels it can move.
* @param {number} relY The normalized scroll value.
* @param {number} offset A base normalized value from which to start the scroll behavior.
* @returns {number} The updated position value.
*/
function pos(base, range, relY, offset) {
return base + limit(0, 1, relY - offset) * range;
}
/**
* Clamps a number to a range.
* @param {number} min The minimum value.
* @param {number} max The maximum value.
* @param {number} value The value to limit.
* @returns {number} The clamped value.
*/
function limit(min, max, value) {
return Math.max(min, Math.min(max, value));
}
这种方法确实适用于您处理大图像(或其他可以轻松写入画布的元素)的情况,当然处理大块文本会更具挑战性,但根据您的站点,它可能被证明是最合适的解决方案。 如果您 必须 处理画布中的文本,则必须使用 fillText
API 方法,但它是以可访问性为代价的(您只是将文本光栅化为位图!),您现在必须处理换行和一大堆其他问题。 如果你可以避免它,你真的应该这样做,并且使用上面的转换方法可能会更好地为你服务。
鉴于我们尽可能地采用这一点,没有理由假定视差工作应该在画布元素内完成。 如果浏览器支持它,我们可以使用 WebGL。 这里的关键是 WebGL 拥有所有 API 到显卡的最直接路径,因此,它是实现 60fps 的最有可能的候选者,尤其是在网站的效果很复杂的情况下。
您的直接反应可能是 WebGL 太过分了,或者它在支持方面并不普遍,但是如果您使用 Three.js 之 那么您总是可以回退到使用画布元素,并且您的代码以一致的方式抽象和友好的方式。 我们需要做的就是使用 Modernizr 来检查适当的 API 支持:
// check for WebGL support, otherwise switch to canvas
if (Modernizr.webgl) {
renderer = new THREE.WebGLRenderer();
} else if (Modernizr.canvas) {
renderer = new THREE.CanvasRenderer();
}
然后使用 Three.js 的 API,而不是自己处理上下文。 这是一个 演示 ,假设您的浏览器也支持!
作为对这种方法的最后一个想法,如果您不是向页面添加额外元素的忠实拥护者,您始终可以 使用画布作为背景元素 在 Firefox 和基于 WebKit 的浏览器中 显然,这并不普遍,所以像往常一样,你应该谨慎对待它。
这是你的选择
开发人员默认使用绝对定位元素而不是任何其他选项的主要原因可能只是无处不在的支持。 这在某种程度上是虚幻的,因为目标浏览器可能会提供极差的渲染体验。 即使在当今的现代浏览器中,使用绝对定位元素也不一定会带来良好的性能!
转换,当然是 3D 类型,让您能够直接使用 DOM 元素并实现稳定的帧速率。 这里成功的关键是尽可能避免绘画,而只是尝试移动元素。 请记住,WebKit 浏览器创建层的方式不一定与其他浏览器引擎相关,因此请务必在使用该解决方案之前对其进行测试。
如果您只针对顶级浏览器,并且能够使用画布呈现网站,那么这可能是您的最佳选择。 当然,如果您要使用 Three.js ,您应该能够根据您需要的支持非常轻松地在渲染器之间进行交换和更改。
结论
我们评估了一些处理视差站点的方法,从绝对定位的元素到使用固定位置的画布。 当然,您所采取的实施将取决于您要实现的目标以及您正在使用的特定设计,但知道您有选择总是很好的。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Web 网页滚动性能优化
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论