仅使用 CSS 的高性能响应式网站之旅

Avatar of 34 Cross
34 Cross

DigitalOcean 提供适用于旅程各个阶段的云产品。 立即开始使用 $200 免费信用额度!

以下是 34 Cross 的 Rajoshi Ghosh 和 Tanmai Gopal 的客座文章。 他们给我发了邮件,向我展示了他们的新网站以及它在拥有酷炫功能、视觉效果丰富和响应式的情况下是如何高效的。 我想,嘿,你应该写一篇关于它的文章! 这就是它。 沉浸在数学中。

更新: 本文发布后,他们更新了网站。 他们保留了本文提到的网站,地址为 old.34cross.in”,我将更改本文中的链接指向该地址。

另一次更新: 似乎“旧”网站也已失效,我们将删除指向它的所有链接。

在 34 Cross,我们希望开发新的网站,使其具有响应式、移动友好型,并且能够仅使用 HTML 和 CSS 轻松地在 2G 网络上加载。 在本文中,我们将告诉您我们在网站设计和速度优化方面遇到的挑战以及如何克服这些挑战。 首先,我们将重点关注滚动视差效果。 接下来,我们将展示一些仅通过 CSS 即可实现的交互式功能。 最后,我们将提供一份有关性能的一般性注意事项清单。

第 1 部分。 仅使用 CSS 的视差效果

我们基于 Keith Clark 的(可能是第一个)CSS 视差 概念 构建了我们的 CSS 视差。 他关于视差的关键思想非常简单。 本质上,当您滚动时,离您“更近”的元素移动得更快,离您“更远”的元素移动得更慢。 它是不同元素以不同速度移动的分层效果。

这对于网站来说很不寻常,因为网站通常将所有元素放置在同一个平面上,当您滚动时,所有元素都以相同的速度移动。 但是,如果我们将网站视为投影在我们计算机屏幕上的 3D 世界,我们可以将一些东西移动到屏幕后面很远的地方,让一些东西留在前面。 然后垂直滚动使更远的东西移动得更慢,前面的东西移动得更快。 瞧,视差! 正如它在现实世界中工作的那样。

1.1. 简要介绍 CSS 3D 几何

CSS 的 3D 变换使我们可以像这样查看网站的元素

C 是计算机屏幕中心,屏幕的左上角(从用户的角度来看)是原点。 网页元素可以放置在任何位置,浏览器渲染引擎将这些元素投影到我们的屏幕上,让我们查看它们。 这与在 3D 中绘图本质上相同。

第一步是定义透视

  1. 选择一个元素作为您的 3D 投影表面。 它不需要是整个屏幕。
  2. 告诉浏览器您希望投影表面离您的眼睛有多远。

这两件事都是通过在元素上定义 perspective 属性来完成的。 perspective 属性本质上定义了上图(图 1)中长度 pp 的值越低,3D 效果越强烈。 值越大,效果越弱。

1.2. 实现视差

让我们定义一个元素,通过它我们可以查看我们的 3D 世界。

  1. 我们希望整个视窗成为我们进入 3D 世界的窗口,因此我们将创建一个 #container 元素来覆盖整个视窗。
  2. 让我们将 p 的值定义为 1px。 默认情况下,透视原点是元素的中心(点 C)。
  3. 我们将视差元素分成组。 我们创建的组将有一个元素在背景中(通过将其沿 -z 轴向后移动)以及一个元素在前景中。

这将导致以下操作

特殊注意事项

  • 由于我们并不真正理解的原因,我们需要将视差效果分成组。 Chrome 实际上不需要这样做,但 Firefox 需要。
  • preserve-3d#group1 的重要指令,它要求它尊重其父元素的 3d 属性。 否则,透视指令将被忽略。

现在,我们在前景平面中从视窗顶部有 500px 的间隙。 背景看起来更小,因为它被推开了。 由于它的投影(图 1 中的 d),它周围还有一些空白。

接下来我们要做的是让它看起来高度为 500px,以便它填补 500px 的间隙。 从图 1,投影高度为 h_1

基本三角学告诉我们

p/(p-z) = h_1/h

这给了我们投影高度

h_1 = h * (1 + z/(p-z))

由于我们将 z 设置为 -1px,将 p 设置为 1px,因此我们得到

h_1 = h / 2

这意味着转换会将我们的图像缩小 1/2,因此将原始元素放大 2 倍会使它看起来高度为 500px。

让我们看看它的实际效果。 其他所有 CSS 与之前相同。

较远的元素现在高度为 500px,但似乎仍然在元素顶部有一些空白。 在新标签页中打开上面的 Pen ,然后垂直调整窗口大小。 我们假装的前景粉红色 div 顶部的空白将不断变化。 这些空白来自哪里?

这需要详细了解缩放的工作原理。 缩放应用于元素时,会从其中心缩放该元素。 让我们看看缩放前后元素的侧面视图

缩放之前。 视窗位于透视 p 处,元素沿 Z 轴平移 -z(在屏幕后面)
元素在其自身中心缩放后。

回到我们的 HTML,我们看到,即使我们将图像放大到感知大小相同,元素仍然距顶部有一定的偏移量。 让我们尝试通过解决顶部间隙 x 来纠正这一点

  • h 是背景元素的高度(EF
  • s 是应用的缩放比例。 因此,s.h = E_2F_2
  • v/2 是视窗高度的一半
  • e 是从透视原点到元素中心的垂直距离,e_1 是其在视窗上的投影
  • 1 + z/(p-z) 是在 z 距离处任何高度乘以的因子,以得到投影高度。
  • 使用以下约束条件:在视窗投影上:x + E_2'O' + e_1 是视窗高度的一半
  • x + s/2 * h * (1 + z/(p-z)) + e * (1 + z/(p-z)) = v/2
  • x = h/2*(1-s) + h*z*(1-s)/(2*(p-z)) - v*z/(2*(p-z))
  • 应用我们的属性:z = -1, p = 1, s = 2,我们得到
  • x = (v-h)/4

重要的是要注意,间隙 x 取决于视窗高度。 如果我们将背景元素的顶部偏移此值,则顶部边缘将始终对齐。 但是,由于 x 是间隙的感知值,因此背景元素将向上移动 (1 + z/(p-z))^-1 倍。 这意味着我们应该将元素向上移动:((p-z)/p) * x,在我们这种情况下为 2 . x

但是,我们如何应用这种公式版本的 CSS 高度? CSS calc() 来了! 对于高度为 500px 的元素,我们得到

top: calc(250px - 50vh);

让我们看看它的实际效果

完美! 现在我们只需要继续添加更多组即可。 在每个组中,我们将根据需要放置元素。 尝试调整 z-index 值以确保正确的元素位于最上面。

这是一个 Sass 模块,可以帮助您为视差设置基本的平移和缩放数学。

第 2 部分。 纯 CSS 交互性

2.1. 仅使用 CSS 的响应式菜单导航

该网站在有空间的情况下有一个平均的链接行导航栏。 但在小屏幕上,我们想要一个仅使用 CSS 的下拉式导航。 这需要一些交互性(点击以显示)。 很少有 HTML 元素可以在没有任何 JS 的情况下进行交互,例如表单元素,例如复选框。 当这些元素更改状态时,CSS 选择器允许我们 检测这些更改并执行某些操作

工作原理

  1. HTML 中的复选框:<input type="checkbox">
  2. CSS 中的伪状态选择器:input:checked
  3. CSS 中的同级选择器,用于选择相邻的 <div>input:checked ~ div,用于在状态更改时切换 div 的高度
  4. 使用 max-height 来转换 div 的高度,而不是 height,因为最终的高度可能是未知的。

结果

你可以发挥创意,创建可重复使用的过渡效果,比如 “阅读更多” 手风琴。你需要遵守的关键限制是,expandee 元素必须紧随 input 元素,这样才能让兄弟选择器生效。如果你想让触发元素(比如 “阅读更多” 链接或箭头按钮)看起来位于展开元素的下方,那么 position: absolute 将是你的好帮手。

2.2. 只用 CSS 动画滚动

这只是一个技巧,但有一个相当大的限制。

我们只能用 CSS 控制样式属性,而不能控制滚动条位置,这只能通过 JavaScript(以及用户交互)来实现。但我们可以使用 CSS 的 :target 选择器来缓慢地动画化容器 <div>top 值到某个特定值。限制在于用户会失去滚动控制,因为 top 值已被 CSS 设置。

因此,虽然你可以动画化滚动(我们将在本文中演示),但如果你认为用户更喜欢直接滚动,那么你可能不想这样做。

工作原理

  1. 点击锚链接会将一个片段添加到 URL 中。(例如:http://site.com/#id)
  2. CSS 的 :target 伪类选择器在 URL 片段与 ID 匹配时激活(例如:#id:target)。
  3. 这可以用来动画化 top 属性,从而产生滚动效果。

在下面的示例中,我们创建了一些页面内锚链接。与通常将锚链接放置在相应内容附近不同,我们创建了虚假的锚链接,它们可以帮助我们触发 CSS 动画。

第 3 部分. 优化清单

优化页面加载速度是一项永无止境的工作。你需要找到一个平衡点,在将多少时间用于改进现有基础设施,升级基础设施,以及需要投入多少精力来关心这些方面。有一些明显但非常重要的要点。

  1. 减少每次请求传输的数据量:只使用压缩过的最小化资产。
  2. 减少浏览器渲染页面所需的请求数量。每个 CSS 文件、JavaScript 文件和图像文件都是另一个请求。还要在它们上使用浏览器缓存,这样就不需要多次请求它们。
  3. 提高服务器响应时间。对于静态内容使用 nginx,其他情况使用 Apache。调整服务器以获得最佳性能。使用良好的应用层缓存,尤其是当你拥有经常动态生成的内容时(比如通过复杂的 SQL 连接)。
  4. 减少 阻塞 JavaScript 内容
  5. 不要编写无关的 CSS 规则。始终计划高效地编写你的 CSS 高效

3.1. 图片压缩

图片是导致页面加载缓慢的主要原因之一。以下是一个清单,可以帮助你充分利用图片。

3.1.1 调整图片大小和选择合适的格式

不同的图片格式有不同的优势。大多数格式对比例敏感,比如 JPG 和 PNG,而一些格式则对比例不敏感,比如 SVG。

  1. 原生图片尺寸应该与渲染后的图片尺寸完全一致或接近(即,最好将 400x400px 的 JPG 以 400×400 的尺寸显示)。或者使用 SVG 之类的比例不敏感格式。
  2. 图片类型
    • 数百万种颜色(比如照片):JPG 和有损压缩
    • 插图或透明度要求:PNG8,如果确实需要超过 256 种颜色,则使用 PNG 真彩色。

3.1.2 压缩图片

有很多方法可以压缩和优化图片。以下是一些接近底层的技术,可以帮助你从图片中压缩出最后几个不需要的字节。

要手动压缩 JPG,一个好的起点是这个 StackOverflow 答案。使用 ImageMagick 并运行以下命令

convert -strip -interlace Plane -gaussian-blur 0.05 -quality 85% source.jpg result.jpg

如果你不想模糊,请使用以下命令

-sampling-factor 4:2:0
原始文件,27.8 KB
优化后的文件,8.8 KB(使用采样因子而不是模糊)

要手动压缩 PNG

  • 步骤 1:pngcrush 会尝试各种方法来减少调色板、丢弃无用块等。来源:文件大小减小 - YUI 博客
  • 步骤 2:pngquant 将你的 PNG 从 PNG 真彩色格式转换为 8 位格式。对于静态图片,这总是值得尝试,因为大小会减少到大约三分之一!唯一的不足之处是,你需要手动查看图片,确保质量足够好。

提示:对于 JPG 使用渐进式渲染,或者对于 PNG 使用隔行扫描,以获得更好的图片加载效果。请参阅 Coding Horror 上的 渐进式渲染

3.1.3 雪碧图和数据 URI

图片雪碧图是一张包含许多较小图片的大图片,这些图片位于不同的位置。它们通常通过 CSS 的 background-position 来渲染,通过移动位置来显示你需要的那部分图片。关键是:雪碧图减少了对服务器的总体请求数量,正如我们之前所述,这有利于性能。

雪碧图的另一个优势是,所有图片都已加载,因此可以避免延迟加载问题。例如:当悬停过渡显示一张单独的图片时,会有一闪而过,因为图片只有在需要时才会被获取。雪碧图可以避免图片预加载问题。

雪碧图最大的缺点是,自行创建雪碧图可能很麻烦。这篇文章 介绍了大量相关技术。如果你有一组大小相似的图片,使用 ImageMagick 的 montage 来创建雪碧图是一个毫不费力的任务。

montage -mode concatenate -tile 2x10 1.jpg 2.jpg ... out.jpg

这将创建一张 2 列 10 行的图片蒙太奇,命名为 out.jpg

数据 URI 是一种巧妙的技术,可以将所需的图片嵌入到 HTML 或 CSS 中。与雪碧图一样,数据 URI 可以减少对服务器的总体请求数量。但是需要注意的是:数据 URI 在移动设备上会 慢 6 倍

3.2. 缓存和最小化

关于良好最小化和缓存技术的资源已经足够多了,但这里提供一个简单易行的解决方案,值得实施:Pagespeed 的 nginx 模块。Pagespeed 的 Apache 和 nginx 模块包含了许多最佳实践。它们提供了很多过滤器和选项,值得你 详细了解。你可以 构建 自己的 nginx 并包含 Pagespeed 模块,或者使用类似 这个 的 Docker 镜像。

结论

  1. 只用 CSS 可以做很多事情。用 CSS 添加一些交互性本质上是一个技巧,但用声明式的方式添加交互性和过渡效果无疑有着强大的力量。
  2. 始终优化你的图片!在任何可以的情况下使用有损压缩,并投入时间来做好它们。将自动压缩和调整大小功能集成到你的构建流程中。
  3. 页面应该快速加载!可能没有什么比 Google 的 Pagespeed 模块更有效(以最小的努力)了。