捕获 – 提高自适应网页性能

响应式设计现在被广泛认为是构建新网站的主要方法。有充分的理由:响应式设计工作流程是为不同设备屏幕尺寸和分辨率构建定制视觉体验的最有效方法。

然而,响应式设计只是构建丰富、引人入胜的移动体验的冰山一角。

图片来源:For a Future-Friendly Web by Brad Frost

响应式网站的性能问题

性能是网站最重要的功能之一,但经常被忽视。许多开发人员都面临着性能挑战 - 为了创建高性能网站,你需要花费大量时间优化网站的后端。更需要花费时间去 了解浏览器的工作原理,以便尽可能快地渲染页面。

在创建响应式网站时,性能挑战更加困难,因为你只有一套标记,用于所有类型的设备。你遇到的一个问题是响应式图像问题 - 如何确保为 Retina Macbook Pro 设计的大图像不会在旧款 Android 手机上下载?如何防止桌面广告在小屏幕设备上渲染?

我们经常在完美条件下进行测试 - 使用快速计算机、快速互联网和靠近服务器,因此很容易忽视性能问题。为了让你了解这个问题的严重程度,我们对一些顶级响应式电子商务网站进行了分析,结果表明,平均响应式网站首页包含 **87.2 个资源**,由 **1.9 MB 的数据** 组成。

可以通过手动调整网站来解决响应式性能问题,但是手动进行性能优化既复杂又重复,因此非常适合创建工具。通过捕获,我们希望使创建高性能自适应网络体验变得尽可能容易。

介绍捕获

捕获是我们开发的一个客户端 API,它可以让开发人员在任何资源开始加载之前完全控制 DOM。对于响应式网站来说,根据设备的条件控制要加载的资源是一个挑战:所有当前的解决方案都需要你对现有的网站进行重大更改,要么使用服务器端用户代理检测,要么强迫你打破语义网络标准(例如,将 src 属性更改为 data-src)。

我们控制资源的方法是捕获源标记,使其无法被浏览器解析,然后在禁用资源的情况下重建文档。

能够在客户端控制资源让你对网站的性能拥有前所未有的控制权。

捕获是 Mobify.js 1.1 的一项关键功能,Mobify.js 1.1 是我们用于使用客户端模板创建移动和平板网站的框架。在我们 2.0 版本中,我们重新设计了 Mobify.js,使其成为一个更模块化的库,可以在任何现有的网站中使用,捕获是其主要功能。

响应式图像问题的解决方案

人们解决响应式图像问题的一种方法是修改现有的后端标记,将所有 img 元素的 src 更改为类似 data-src 的内容,并在更改后添加 <noscript> 备用方案。这样做的原因在 这篇 CSS-Tricks 文章 中有所讨论 -

“指向马匹图像的 src 将在图像被浏览器解析后立即开始下载。**没有实际的方法可以阻止它**“.

有了捕获,这种情况就不再存在了。

例如,假设你有一个 img 元素,你想为带有 Retina 屏幕的设备修改它,但你不想让 src 属性中的原始图像加载。使用捕获,你可以执行以下操作

if (window.devicePixelRatio && window.devicePixelRatio >= 2) {
    var bannerImg = capturedDoc.getElementById("banner");
    bannerImg.src = "retinaBanner.png"
}

因为我们可以在任何资源加载之前访问 DOM,所以我们可以在图像下载之前动态地替换图像的 src。后者的示例非常基础 - 一个更好的示例是突出显示捕获功能的强大之处,以演示 picture 填充的完美实现。

图片填充

Picture 元素是 W3C 用于处理自适应图像的官方 HTML 扩展。有 填充程序可以让你在今天使用 Picture 元素,但它们都不能完美地填充 - 目前实施的最佳填充程序需要在 img 元素周围添加 <noscript> 标签,以支持没有 Javascript 的浏览器。使用捕获,你可以完全避免这种混乱。

打开示例,并确保在 Web 检查器中打开网络选项卡,以查看哪些资源被下载

以下是示例源代码中的重要代码段


    
    
    
    
    

请注意,有一个使用 src 属性的 img 元素,但浏览器只下载了正确的图像。你可以在这里查看此示例的代码(请注意,填充程序仅在示例中可用,而不是在库本身中 - 至少目前是这样的)

并非所有网站都使用修改过的 src 属性和 <noscript> 标签来解决响应式图像问题。如果你不想依赖于修改 src 或为网站的每个图像添加 <noscript> 标签,那么另一种选择是使用服务器端检测来替换图像、脚本和其他内容。不幸的是,这种解决方案也存在很多挑战。

当唯一需要担心的设备是 iPhone 时,使用服务器端用户代理检测很容易,但是随着大量新设备的推出,维护一个包含所有设备信息(如屏幕宽度、设备像素比等)的字典是一项非常痛苦的任务;更不用说有些东西你无法通过服务器端用户代理检测,例如实际的网络带宽。

你还可以用捕获做什么?

解决响应式图像问题是捕获的绝佳用例,但还有很多其他用例。以下是一些更有趣的示例

标记中的媒体查询用于控制资源加载

在这个示例中,我们使用图像和脚本属性中的媒体查询来确定哪些图像和脚本将加载,只是为了让你了解你可以用捕获来做什么。你可以在这里找到此示例

使用模板完全重写页面

Mobify.js 1.1 的主要功能是客户端模板,它可以在响应式设计无法提供足够灵活性或更改后端过于痛苦和乏味时,完全重写现有网站的页面。当你需要快速建立移动网站时,它特别有用。这不再是 Mobify.js 的主要功能,但仍可以使用捕获来实现。

查看这个基本示例

在这个示例中,我们获取了现有页面的一部分,并将它们用于呈现给浏览器的全新标记中。

用生气的猫填充你的页面

当然,没有什么比用生气的猫替换页面中的所有图像更有用!当然,要以高性能的方式进行 ;-)。

再次打开 Web 检查器,你会看到网站上的原始图像没有下载。

性能

那么有什么问题呢?使用捕获会降低性能吗?是的,会,但我们认为,通过控制资源可以获得的性能提升超过了捕获带来的轻微损失。在首次加载时,库(以及主可执行文件,如果没有连接在一起)必须下载并执行,而这里的加载时间将根据设备的往返延迟而有所不同(范围从大约 ~60 毫秒到 ~300 毫秒)。但是,由于库被缓存以及即时 (JIT) 编译器使编译效率更高,因此每次后续请求的损失至少会减少一半。 你可以自己运行测试!

我们还尽力将库的大小保持在最小限度 - 在发布这篇博文时,库的最小化 gzip 压缩大小为 4 KB。

为什么要使用捕获?

我们创建了 Capturing,以赋予前端开发人员更多对性能的控制权。其他解决方案无法解决这个问题的原因是,前端和后端的职责变得越来越交织在一起。后端的职责应该是生成语义化的 Web 标记,而前端的职责应该是从后端获取标记,并以最佳方式在设备上进行视觉呈现,并以高性能的方式进行处理。响应式设计解决了第一个问题(以视觉方式呈现数据),而 Capturing 则有助于解决下一个问题(通过使用前端技术(例如确定屏幕大小和带宽以控制资源加载)来提高网站性能)。

如果您想继续遵守语义 Web 的规则,并且想要一种简单的方式来控制前端的性能,我们强烈建议您 查看 Mobify.js 2.0

如何开始使用 Capturing?

访问我们的 快速入门指南,了解有关如何使用 Capturing 设置的说明。

下一步是什么?

我们已经开始了 Mobify.js 2.0 的官方开发者预览,其中只包含 Capturing 部分,但我们将继续添加更多有用的功能。

要添加的下一个功能是自动调整图像大小,允许您根据浏览器窗口的大小动态下载图像,而无需修改现有标记(除了插入一小段 JavaScript 代码!)。

我们还计划创建其他只能通过 Capturing 解决的 polyfill,例如 新的 HTML5 模板标签

我们期待您的反馈,并且我们很高兴看到其他开发人员将如何使用我们新的 Mobify.js 2.0 库!

关于 Shawn Jansepar

@shawnjan8 Shawn 是 Mobify 的软件工程师,他在 Mobify.js 开源库和 Mobify Cloud 上工作。他喜欢在前端和后端进行开发,并专注于用户体验。当他不在开发时,您可能会发现他在打曲棍球/玩电子游戏、吃饭或旅行。Shawn 拥有西蒙弗雷泽大学的计算机科学理学士学位。

Shawn Jansepar 的更多文章…

关于 Robert Nyman [名誉编辑]

Mozilla Hacks 的技术布道师和编辑。发表关于 HTML5、JavaScript 和开放 Web 的演讲和博客文章。Robert 是一位坚定的 HTML5 和开放 Web 信徒,自 1999 年起就一直从事 Web 前端开发工作 - 在瑞典和纽约市。他还经常在 http://robertnyman.com 上发布博客文章,并热爱旅行和结识新朋友。

Robert Nyman [名誉编辑] 的更多文章…


34 条评论

  1. louisremi

    您如何处理动态生成的內容?如果在加载事件之后我在 DOM 中插入相同的元素,它会被捕获吗?

    2013 年 3 月 19 日 凌晨 1:52

    1. Noah A

      这种捕获只能发生在文档中存在的内容上,这些内容是在从服务器发送时存在的。当然,您可以在将任何内容添加到 DOM 之前,尽可能地操作您使用 AJAX 检索的内容。

      2013 年 3 月 19 日 凌晨 2:28

    2. Shawn Jansepar

      好问题!实际上,Capturing 在您现有的 JS 有机会动态在 DOM 中创建内容之前发生。您首先捕获原始文档,没有任何资源加载或 JavaScript 执行,从那里开始,您可以随意修改捕获的文档。完成修改捕获的文档后,您将其渲染到主文档,此时,您的内联/外部 JavaScript 文件将执行。将 Capturing 视为赋予您成为 HTML 预解析器或修改标记的中间件的能力。

      2013 年 3 月 19 日 凌晨 3:10

      1. Shawn Jansepar

        Errr,键盘稍微滑了一下:“Capturing 实际上在您现有的 JS 有机会动态在 DOM 中创建内容之前发生。”

        2013 年 3 月 19 日 凌晨 3:14

  2. Matt Wilcox

    本质上,这

    pre-parse: off;

    终于来了!:D 那是我觉得有史以来最具破坏性的“性能增强”。

    2013 年 3 月 19 日 凌晨 4:37

    1. Shawn Jansepar

      是的,本质上就是。我不会说它是那里最具破坏性的性能增强——关键是找到平衡。看看这篇关于预解析器与响应式图像的精彩博文:http://blog.cloudfour.com/the-real-conflict-behind-picture-and-srcset/

      2013 年 3 月 19 日 上午 9:51

      1. Matt Wilcox

        我知道这一切——我正是 Adaptive Images 解决方案背后的那个人 :)

        我认为预解析带来的弊大于利。远远超过。这是一个旨在节省毫秒的糟糕解决方案,以一种破坏了节省完整秒的能力的方式实现。目光短浅,构思不周,收益低,而且无法关闭它。

        2013 年 3 月 19 日 上午 10:11

        1. Shawn Jansepar

          啊,酷,Adaptive Images 做得不错!

          我认为这对很多网站都有帮助,当 Steve Sounders 说“推测性下载是浏览器最重要的性能改进之一”时,我想他一定知道一二!但我认为有很多场景,禁用它的能力将非常有益。在这种情况发生之前,请继续使用 Capturing/Mobify.js :)

          2013 年 3 月 19 日 上午 11:23

    2. Andy Davies

      我认为您必须谨慎地声明前瞻性预取器是有史以来最具破坏性的性能增强。

      是的,它与响应式/自适应图像之间的交互需要做一些工作,但您只需比较旧浏览器的瀑布图与新浏览器的瀑布图,即可看到它带来的积极变化。

      2013 年 3 月 19 日 下午 2:20

  3. Ray Schwartz

    这看起来很有希望,迫不及待地想要尝试它!如果我的脚本是异步加载的,会出现任何问题吗?还想知道渐进增强。您会使用 Capturing 来构建图片元素,甚至构建多个来源吗?这样,如果 JS 失败,您将只剩下一个图像标签?

    2013 年 3 月 19 日 凌晨 5:02

    1. Shawn Jansepar

      不,异步加载的脚本没有问题!您用于异步加载其他脚本的脚本将在捕获完成后执行。是的,如果您将 Capturing 与图片元素结合使用,图片元素将按预期完全工作——带有 img 元素回退(不需要 `noscript` 元素!)。

      2013 年 3 月 19 日 上午 9:54

  4. Bruce

    Capturing 真的只是解决了 Twitter Bootstrap 已经解决的问题吗?Capturing 有什么不同?什么时候应该使用 Capturing 而不是 Bootstrap?

    2013 年 3 月 20 日 上午 10:01

    1. Shawn Jansepar

      Capturing 和 Bootstrap 实际上完全不同。Bootstrap 是一种前端框架,可以帮助您创建网站(响应式和非响应式)。Capturing 是一个库,允许您在任何资源加载之前修改文档。因此,如果您有一个 Bootstrap 网站,您可以轻松地在 Mobify.js 中使用 Capturing 库,以获得更多性能优势!

      2013 年 3 月 20 日 下午 6:13

    2. james

      你在说什么,Bruce?

      2013 年 3 月 26 日 上午 8:38

      1. Bruce

        嘿,James,我会尽量解释我当时问这个问题时的想法… Bootstrap-responsive.css 使操作页面内容以处理不同范围的浏览器尺寸变得非常简单。从调整列和内容大小,到控制哪些内容应该显示或不显示以适应各种浏览器尺寸。当然,您不会使用 Bootstrap 来根据业务逻辑有条件地加载内容,但这篇文章并没有关注 Capturing 的这种能力。重点似乎是使用响应式设计来提高移动设备浏览器上的性能。在这方面,我认为 Capturing 以不同的方式解决了相同的问题。

        2013 年 3 月 26 日 下午 1:44

  5. Sérgio Lopes

    哇,非常巧妙的技巧!但是 plaintext 元素已弃用,对吧?他们从规范中删除了它。您认为将来浏览器停止支持它会导致任何问题吗?

    2013 年 3 月 21 日 下午 12:30

    1. Shawn Jansepar

      当 plaintext 被认为是“过时”时,我们才需要担心 :). 当一个元素或属性被认为是已弃用时,W3C 仍然建议浏览器出于向后兼容性的原因仍然实现它们。查看有关已弃用/过时元素的官方 W3C 规范

      http://www.w3.org/TR/html401/conform.html#deprecated

      我们还有一些其他技巧可以用来实现相同的结果,我们只是发现 plaintext 技巧是其中最好的!

      2013 年 3 月 22 日 下午 4:23

      1. Sérgio Lopes

        HTML5 认为 plaintext 已过时。它还规定,Web 浏览器现在 *必须* 将“plaintext”视为“pre”。我还没有发现浏览器这样做,但它已经在规范中了

        http://www.whatwg.org/specs/web-apps/current-work/multipage/obsolete.html

        这难道不冒险吗?

        您还考虑了哪些其他技巧?我只看到了注释标签,但页面 HTML 不能包含注释

        2013 年 3 月 22 日 下午 6:05

        1. Shawn Jansepar

          我的同事 Noah 在下面回答了这个问题——从规范中的措辞来看,似乎浏览器将始终维护对该元素的特殊行为。我们相信 Capturing 变得过时,与其说是因为 Plaintext 的行为发生了变化,不如说是因为浏览器 API 提供了对资源加载的原生控制。

          2013 年 3 月 25 日 上午 7:13

  6. Cindy

    这可能是一个非常愚蠢的问题,但是这个过程对于 WordPress 网站来说如何运作呢?在 WordPress 网站上,许多图像将由非开发人员通过管理员发布。是否有办法设置它?

    2013 年 3 月 22 日 上午 8:28

    1. Shawn Jansepar

      这根本不是一个愚蠢的问题!对于 WordPress 网站,您只需要将标签添加到网站 HEAD 的最顶部,然后在 main.js 中编写修改网站的代码,并将其托管在可访问的某个地方。查看快速入门指南

      http://www.mobify.com/mobifyjs/v2/docs/

      我们也有一份关于如何在 WordPress 网站上插入标签的文档。

      https://cloud.mobify.com/docs/installing-the-mobify-tag/#/wordpress/

      美妙之处在于它最终只是 JavaScript 代码 - 无论你使用的是什么 CMS 或框架都无关紧要。

      2013 年 3 月 25 日 上午 07:20

  7. Alex Bell

    叮当,预解析器已死! 非常聪明,先生们。三个问题:1) 你能更详细地解释一下如何编写纯文本元素来阻止加载吗?你没有真正解释魔法,只是展示了技巧!(我相信它有效,我只想知道为什么。)我一直认为预解析器总是无视一切,领先于脚本执行?2) 你能回答 Sérgio 关于纯文本元素弃用的问题吗?(例如,MDN 强烈反对它。) 3) 浏览器支持:为什么不支持 IE<10?移动浏览器支持情况如何?

    2013 年 3 月 22 日 下午 15:45

    1. Shawn Jansepar

      Alex,问得好!

      1) 纯文本元素是 `pre` 标签的前身,它的作用是指示浏览器忽略格式。由于某种原因,每个浏览器都通过忽略结束标签来实现纯文本元素,导致文档中的所有内容都被吞并到纯文本元素中的一个大字符串中,因此在纯文本元素之前的每个节点都不再被渲染。预解析器的行为方式不同 - Firefox 实际上会开始加载资源(尽可能多的允许的资源),正如你期望预解析器所做的那样,但在纯文本转义之后,请求会在 1-3 KB 后被取消。对于 Webkit,预解析器完全停止了。

      2) 完成 :)

      3) 我们只支持 IE10 的原因是,所有低于 IE10 的 IE 浏览器都会完全处理 document.write。当你使用捕获执行 `render` 时,我们会用 document.write 编写你的整个网站,不幸的是,在旧版本的 IE 中,脚本的执行顺序不是按它们出现的顺序,而是按它们下载的顺序,这是一个严重的问题。我们已经考虑了一些方法来解决这个问题,但还没有找到合适的解决方案(直到最近我们才开始支持桌面浏览器,因为这曾经是一个只支持移动端的框架)。如果你有任何想法,请随时 fork 我们的仓库!这里是捕获文件:https://github.com/mobify/mobifyjs/blob/v2.0/src/capture.js

      2013 年 3 月 26 日 上午 10:44

      1. Alex Bell

        谢谢 - 我想针对 IE<10 脚本执行问题的一个解决方法是完全连接,或者使用像 yepnope 这样的加载器完全异步地编写所有内容。这绝对可行,但它是一个限制,我想我可以代表几乎所有人说 IE9 是一个绝对的要求。不过,纯文本技巧是一个绝妙的发现!

        与此同时,有人提出/起草/提交过任何基于标准的标记解决方案来驯服预解析器吗?必须有更简单的长期解决方案来解决这个问题,不需要任何愚蠢的 noscript 标签或像 Souders(我认为很公平)所批评的捕获的阻塞行为…

        2013 年 3 月 26 日 上午 11:25

  8. Noah Adams

    Sergio,你会注意到,在讨论类似于 `pre` 的语义时,规范中提到了“解析器对该元素有特殊行为。”

    该元素不可关闭的特性(没有标签),以及它对这种捕获技术的实用性,是特殊解析行为的原因。

    显然,这不是该元素的预期用途,但展望未来,我认为很明显,在浏览器预解析器和资源调度程序中拥有更多上下文感知的行为将是有益的,特别是对于即将推出的 picture 元素,以及可能对要加载的内容进行一些编程控制,而不必像这样进行巧妙的 DOM 操作。

    2013 年 3 月 23 日 上午 00:35

  9. Robin Berjon

    很有趣。你是否查看了新提出的 NavigationController?请看

    https://github.com/slightlyoff/NavigationController/

    我之所以问,是因为在我们讨论它的过程中,我写下了以下内容(针对 API 的早期版本,但想法仍然适用)

    https://gist.github.com/darobin/4739457

    我非常有兴趣讨论 NavCon 和 Capture 如何/可以一起工作。

    2013 年 3 月 27 日 上午 04:44

  10. Bryan McQuade

    使用这种捕获技术会在移动网络上将你的页面加载速度降低 1-2 秒。你可以在这里阅读更多关于我的评估的信息(包括显示该技术性能成本的 WebPagetest 测试):https://plus.google.com/117340630485537362092/posts/iv3iPnK3M1b

    2013 年 4 月 6 日 上午 09:02

    1. Shawn Jansepar

      感谢你的深入帖子!我已经更新了上面链接的同一个 G+ 线程,其中包含关于捕获的更准确的测试。在我的测试中,捕获的惩罚大约为 ~0.5 秒。正如我在对你的帖子的回复中所述,你运行的测试使用了一个没有压缩的 mobify.js 文件,而我的测试还将 mobify.js 文件和 main.js 文件合并,这导致捕获的速度大幅提高。

      2013 年 4 月 8 日 下午 14:23

  11. Andre

    https://plus.google.com/+IlyaGrigorik/posts/S6j45VxNESB

    2013 年 4 月 6 日 下午 18:35

  12. Gary Stephenson

    你能描述一下这与 Angular 的工作方式相比如何,以及 Capture/Mobify 在 Angular 应用中是否仍然有用?

    2013 年 4 月 6 日 下午 20:08

  13. Rugby

    为什么不直接添加一个请求头,例如 X-ClientScreen: 1024×768 Retina,它可以由服务器端脚本解析,而不是添加新的 API?

    2013 年 4 月 7 日 上午 01:12

    1. Shawn Jansepar

      问题是,在发送有关客户端的信息时,你应该走多远?你还可以发送有关连接速度等信息。但所有你需要的信息都在客户端本身,将这些信息传递给服务器进行处理似乎很愚蠢。这也使得页面缓存更加困难,因为可以为各种设备提供各种不同的页面。

      此外,即使这种情况真的发生了,而且人们对此感到满意,也需要一段时间才能看到浏览器附带此功能,这无助于当前市场上浏览器的性能。

      2013 年 4 月 7 日 上午 07:43

    2. Shawn Jansepar

      另外,关于客户端和服务器的职责,我在我的文章中提到了我的观点

      “后端的责任应该是生成语义 Web 标记,而前端的责任应该是从后端获取标记,并以最佳方式在设备上以高性能的方式对其进行处理。”

      2013 年 4 月 7 日 上午 07:45

      1. Sean Hogan

        鉴于前端的责任是将语义标记处理成设备上最佳的视觉表示…

        Mobify 如何允许以与着陆页相同的方式转换 AJAX/PJAX 内容?

        2013 年 4 月 7 日 下午 21:57

本文评论已关闭。