【译】使用Mobify.js让你的图片自动响应化

在Web开发社区,响应式图片已经成为最大的挫败之一。缘由也很简单:页面平均大小产品能从去年的1MB达到了惊人的1.5MB。其中图片大小的增加比例就占了页面大小增加的60%或更多,而且这个比例还在不断攀升。javascript

绝大多数的页面是能够下降页面大小的,若是你借助基于设备宽度、像素密度和现代图像格式(例如WebP)等优化条件的话。这些减重的方法能够加快载入时间,让用户参与更多、停留更长时间。不过这里不是争论是否应该为不一样的设备优化图像,而是关于应该怎么去作。css

理想状况下,咱们应该继续使用img标签,而后浏览器会下载适配设备宽度、适配页面布局的图片。然而,这样的功能其实并不存在。实现接近这种功能的方法之一是在javascript执行过程当中改变img元素的src属性,但超前的预解析器(或预加载)扼杀了它的可能性。html

克服这个问题的第一步是建立一个基于标记的解决方案,该方案容许基于设备的功能来替换图像的来源。随着W3C响应式图片交流社区创造的picture元素(尽管目前尚未浏览器实现了它)的引入,这个问题已经被解决了。java

不过,picture元素的引入也带来了新问题:
开发人员如今必须在每个断点为每个图片生成一个独立的asset。而开发者真正须要的是一个可以将一张高分辨率图片自动转化为适配设备的小图片的方案。理想状况下,这个自动化解决方案可以让每张图片只请求一次,而且充分语义化和向后兼容。 Mobify.js的图像API提供了该方案。git

<picture> 元素成为将来的最佳实践

picture元素是当前代替img元素的先行者,由于它使得开发者可以为不一样的屏幕分辨率指定不一样的图片,解决了性能和art direction(尽管新提出的srcN提议值得考虑)问题。典型的设置包括定义断点,为不一样断点生成图像,而后为图像写入picture标记。咱们看看如何通使下面的图片变得响应性:github

请输入图片描述

咱们使用了320、5十二、1024和2048像素基线。web

首先,咱们须要为每张图片生成不一样的分辨率的副本,能够经过命令行工具,例如 Image Optim或使用Photoshop的“存储为web所用格式”功能。接下来,咱们使用下面的标记:chrome

<picture>
        <source src="responsive-obama-320.png">
        <source src="responsive-obama-512.png" media="(min-width: 512px)">
        <source src="responsive-obama-1024.png" media="(min-width: 1024px)">
        <source src="responsive-obama-2048.png" media="(min-width: 2048px)">
        <noscript><img src="responsive-obama-320.png"></noscript>
    </picture>

上面的代码有个问题,在它的配置中,咱们的图片可能不会对移动设备优化。下面是一张缩放到320像素宽的图片:segmentfault

请输入图片描述

图片中的人物已经很难区分。为了更好的适应小屏幕,咱们须要借助art direction的力量,
为小屏幕裁切这张图片。后端

请输入图片描述

由于这个文件不是原始图片的简单缩放版本,须要给它一个特殊的命名结构(因此,用responsive-obama-mobile.png替代了responsive-obama-320.png

<picture>
    <source src="responsive-obama-mobile.png">
    <source src="responsive-obama-512.png" media="(min-width: 512px)">
    <source src="responsive-obama-1024.png" media="(min-width: 1024px)">
    <source src="responsive-obama-2048.png" media="(min-width: 2048px)">
    <noscript><img src="responsive-obama-512.png"></noscript>
</picture>

可是若是咱们要支持高DPI(点每英寸)设备呢?
picture 元素的规范中有一个srcset属性,该属性能让咱们很容易为不一样分辨率指定不一样的图片。如下是咱们使用picture元素后的代码:

<picture>
<source srcset="responsive-obama-mobile.png 1x, responsive-obama-mobile-2x.png 2x">
<source srcset="responsive-obama-512.png 1x, responsive-obama-1024.png 2x" media="(min-width: 512px)">
<source srcset="responsive-obama-1024.png 1x, responsive-obama-1024.png 2x" media="(min-width: 1024px)">
<source srcset="responsive-obama-2048.png 1x, responsive-obama-4096.png 2x" media="(min-width: 2048px)">
<noscript><img src="responsive-obama-512.png"></noscript>

这里引入了一对新文件(esponsive-obama-mobile-2x.pngresponsive-obama-4096.png),它们也必须生成。到目前为止,咱们拥有同一个图片的6个不一样版本的副本。

让咱们更进一步。若是咱们想使用现代化的格式,例如WebP来读取咱们的图像,须要经过判断浏览器是否支持吗?忽然,咱们须要生成的文件数量从6个增长到12个。老实说,没人愿意由于不一样的分辨率而给每一个图片生成多个版本的图片副本,而且须要在代码标记上更新这些图像版本。
咱们须要让它自动化!

理想的响应式图片工做流程

理想的工做流程是这样的,它容许开发者上传分辨率尽量高的图片同时仍然使用img元素来达到自动为不一样的浏览器重置图片大小和压缩图片的目的。img元素很伟大,这个简单的标签解决了简单的问题:为互联网的用户展现图片。理想状况下,咱们能经过一种高效的方法继续沿用这个元素而且向后兼容。而后,当art direction arises 和衡量图片下限的需求不能知足时,咱们可使用picture元素,它语法中内建的分支逻辑很完美。

这个理想的工做流能够经过Mobify.js的响应式图像API来实现。Mobify.js是一款改善响应式网站的开源库,提供了响应式图片、javascript和css的优化,自适应模板等等。它的图像API可以自动重置大小和压缩imgpicture元素,必要状况下,在后端甚至不须要改变任何一行标记代码。简单的上传高分辨率资料而后让API去关心接下来的事情。

不修改后端代码自动让图片响应化

响应式图片之因此成为一个难以解决的问题是因为超前的预解析器,它阻止咱们经过javascript改变img元素的src属性。预解析器是浏览器的一个功能,经过生成一个独立的线程(独立于渲染线程以外),定位资源并使其并行下载,从而让资源尽快的开始下载。预解析器的工做在响应式设计出现以前颇有意义,但在如今的多设备的状况下,代码标记的图像不必定是咱们但愿用户看到的;所以,咱们须要思考一种容许开发者控制资源加载的同时不牺牲预加载的好处的API。在这个问题上,若是想要了解更多细节,能够考虑看看teve Souders的“I <3 Image Bytes.”

不少开发者为了不预加载采用的一种方法是经过img的data-src属性手动改变src属性,巧妙地让预加载器忽略了这些图像,而后使用javascript将src属性的值用data-src的替换。

经过Mobify.js 的 捕获 API,咱们避免了上面的方法,让咱们在保证性能的状况下又不失语义(无需 <noscript>data-src )。捕获技术阻止了初始页面资源的预加载,但这并不阻碍并行下载。使用Mobify.js的图像API和捕获技术相结合,咱们能经过一个javascript标签实现图像的自动响应化。

下面是调用API的的代码:

Mobify.Capture.init(function(capture){
    var capturedDoc = capture.capturedDoc;
    var images = capturedDoc.querySelectorAll('img, picture');
    Mobify.ResizeImages.resize(images, capturedDoc) 
    capture.renderCapturedDoc();
});

它获取了页面的全部图片,而后重写的src的值以下:

http://ir0.mobify.com/<format><quality>/<maximum width>/<maximum height>/<url>

例如,若是该API在安卓最新版的chrome下运行,设备的css像素为320,像素比例为2,那么图片就会从

<img src='cdn.mobify.com/mobifyjs/examples/assets/images/forest.jpg'>

变为

<img src='//ir0.mobify.com/webp/640/http://cdn.mobify.com/mobifyjs/examples/assets/images/forest.jpg'>

forest图片会被调整到640px宽,而且,由于chrome支持WebP,咱们能够所以受益,将来能够下降图片的体积。在第一次请求以后,图片就会被Mobify的CDN缓存,以备下次使用。由于这个图片不须要任何art direction,咱们能够继续使用img元素。

你能够看看这个自动调整图片大小的例子。打开网站调试工具,肯定原始图片没有被下载!

使用这个方案,咱们简化了工做流程。咱们仅仅上传了一个高分辨率的图片,而后让API实现图片的自动化调整大小。中间不须要代理过程,没有改变任何属性-只是一断javascript。去吧,试着复制下面的代码粘贴在head元素里(请注意它必须写在其余资源标签以前)。

<script>!function(a,b,c,d,e){function g(a,c,d,e){var f=b.getElementsByTagName("script")[0];a.src=e,a.id=c,a.setAttribute("class",d),f.parentNode.insertBefore(a,f)}a.Mobify={points:[+new Date]};var f=/((; )|#|&|^)mobify=(\d)/.exec(location.hash+"; "+b.cookie);if(f&&f[3]){if(!+f[3])return}else if(!c())return;b.write('<plaintext style="display:none">'),setTimeout(function(){var c=a.Mobify=a.Mobify||{};c.capturing=!0;var f=b.createElement("script"),h="mobify",i=function(){var c=new Date;c.setTime(c.getTime()+3e5),b.cookie="mobify=0; expires="+c.toGMTString()+"; path=/",a.location=a.location.href};f.onload=function(){if(e)if("string"==typeof e){var c=b.createElement("script");c.onerror=i,g(c,"main-executable",h,mainUrl)}else a.Mobify.mainExecutable=e.toString(),e()},f.onerror=i,g(f,"mobify-js",h,d)})}(window,document,function(){var a=/webkit|msie\s10|(firefox)[\/\s](\d+)|(opera)[\s\S]*version[\/\s](\d+)|3ds/i.exec(navigator.userAgent);return a?a[1]&&+a[2]<4?!1:a[3]&&+a[4]<11?!1:!0:!1},

// path to mobify.js
"//cdn.mobify.com/mobifyjs/build/mobify-2.0.1.min.js",

// calls to APIs go here
function() {
  var capturing = window.Mobify && window.Mobify.capturing || false;

  if (capturing) {
    Mobify.Capture.init(function(capture){
      var capturedDoc = capture.capturedDoc;

      var images = capturedDoc.querySelectorAll("img, picture");
      Mobify.ResizeImages.resize(images);

      // Render source DOM to document
      capture.renderCapturedDoc();
    });
  }
});
</script>

(请注意这段脚本不存在单点故障。若是Mobify.js载入失败,那么脚本会退出,你的网站依然会正常载入。若是图片调整服务器挂了或者你处于一个开发环境中而且图片在外网不可访问,那么就会载入原始的图片。)

你也能够利用完整的文档。支持上面代码的浏览器以下:全部的基于Webkit/Blink内核的浏览器,火狐4+,Opera 11+, and Internet Explorer 10+。

对于咱们大多数的用例来讲,自动化调整img元素实在是太棒了。可是,就像演示的奥巴马的例子,art direction对于某些特定类型的图片是必要的。 那么咱们如何才能继续使用picture元素来支持art direction而不是添加同个图片的6个版本呢?图像API一样会调整picture元素,也就是说你可使用picture元素实现art direction,而把调整大小的功能交给API。

调整 <picture> 元素

固然不一样的浏览器自动化调整图片大小是可行的,而自动化的art direction确实不可能。picture元素是最可能的方案来实如今不一样的断点指定不一样的图片,由于它内置的分支逻辑定义语法很健壮(尽管前文也提到了, srcN也提供了很是接近的功能)。不过,为picture元素添加标记和为每一个图片建立6个版本让人感受十分复杂:

<picture>
    <source srcset="responsive-obama-mobile.png 1x, responsive-obama-mobile-2x.png 2x">
    <source srcset="responsive-obama-512.png 1x, responsive-obama-1024.png 2x" media="(min-width: 512px)">
    <source srcset="responsive-obama-1024.png 1x, responsive-obama-1024.png 2x" media="(min-width: 1024px)">
    <source srcset="responsive-obama-2048.png 1x, responsive-obama-4096.png 2x" media="(min-width: 2048px)">
    <noscript><img src="responsive-obama-512.png"></noscript>
</picture>

当使用图像API和picture元素相结合, 代码标记明显简化了:

<picture>
    <source src="responsive-obama-mobile.png">
    <source src="responsive-obama.png" media="(min-width: 512px)">
    <img src="responsive-obama.png">
</picture>

这里的source元素会自动重写img元素(像前面的例子同样)。同时,注意上面的标记不须要使用noscript来阻止第二次请求,由于捕捉(Capturing)容许你保留标记的语义。

Mobify.js一样能用于已变动的picture元素,便于显式的定义不一样的断点须要多宽的图片,而再也不一览与设备的宽度。 举个例子, 你有一张平板电脑一半宽的图片,那么根据浏览器的最大宽度指定该图片的宽度所生成的图片,它会比实际须要的尺寸大一些:

请输入图片描述

为了解决这个问题,图像API能够改变picture标记,使得咱们能够从新设定每一个source元素的宽度,而不是为每一个判定指定不一样的src属性。例如,咱们能够写成下面这样:

<picture data-src="responsive-obama.png">
    <source src="responsive-obama-mobile.png">
    <source media="(min-width: 512px)">
    <source media="(min-width: 1024px)" data-width="512">
    <source media="(min-width: 2048px)" data-width="1024">
    <img src="responsive-obama.png">
</picture>

注意那个使用了data-src属性的picture元素,这里定义了一个高分辨率的原始图片做为一个起点,这个图片会被用于其余断点的尺寸调整。

它在浏览器中是怎么工做的

  • 若是浏览器宽度介于0到511像素(例如一部智能手机), 那么使用responsive-obama-mobile.png (出于对art
    direction的考虑)。

  • 若是浏览器宽度介于512到1023像素,那么使用responsive-obama.png,由于该媒体查询下source没有指定src。自动的决定宽度由于
    data-width没有指定。

  • 若是浏览器宽度介于1024到2047像素,那么使用responsive-obama.png,
    由于该媒体查询下source没有指定src。调整宽度为512像素,它被定义在data-width属性中。

  • 若是浏览器宽度为2048像素或更大,那么使用responsive-obama.png,
    由于该媒体查询下source没有指定src。调整宽度为1024像素,它被定义在data-width属性中。

  • 若是不支持javascript,恢复为旧的img标签。

图像API会遍历全部的picture元素,并将它转变以下:

<picture data-src="http://cdn.mobify.com/mobifyjs/examples/assets/images/responsive-obama-mobile.jpg">
    <source src="//ir0.mobify.com/webp/400/http://cdn.mobify.com/mobifyjs/examples/assets/images/responsive-obama-mobile.jpg">
    <source src="//ir0.mobify.com/webp/400/http://cdn.mobify.com/mobifyjs/examples/assets/images/responsive-obama.jpg" media="(min-width: 512px)">
    <source src="//ir0.mobify.com/webp/512/http://cdn.mobify.com/mobifyjs/examples/assets/images/responsive-obama.jpg" media="(min-width: 1024px)" data-width="512">
    <source src="//ir0.mobify.com/webp/512/http://cdn.mobify.com/mobifyjs/examples/assets/images/responsive-obama.jpg" media="(min-width: 2048px)" data-width="1024">
    <img src="responsive-obama.jpg">
</picture>

Picture polyfill (包含在Mobify.js里)会被执行而后根据媒体查询选择适当的图片。当浏览器厂商支持了原生picture后,它也能运行良好。

这里有一个使用已变动picture元素标记( the modified picture element ) 的例子,你能够看看。

不经过捕获的方式使用图像API

使用捕获须要将脚本写在head标签里,这会阻塞javascript的调用而且会推迟资源的初始化下载。该延迟的时间长度在3G连接下大概为0.5秒(例如dns解析和资源的捕获和下载),在4G或wifi下耗时会相对少些,大约有60毫秒的延迟。这个延迟的小代价换到的是易于使用,和向下兼容以及语义化。

不经过捕获的方式使用图像API来避免阻塞javascript请求,你须要将全部img元素src属性改成x-src属性(你也能够适当的添加noscript编辑来检测浏览器javascript的支持状况)而且在head标签内粘贴下面的异步脚本:

<script src="//cdn.mobify.com/mobifyjs/build/mobify-2.0.1.min.js">
<script>
    var intervalId = setInterval(function(){
        if (window.Mobify) {
            var images = document.querySelectorAll('img[x-src], picture');
            if (images.length > 0) {
                Mobify.ResizeImages.resize(images);
            }
            // When the document has finished loading, stop checking for new images
            if (Mobify.Utils.domIsReady()) {
                clearInterval(intervalId)
            }
        }
    }, 100);
</script>

这段脚本会异步载入Mobify.js ,当载入完成后,开始正常载入图片就像载入html文档同样。

在WebApp中使用图像API

若是你正在使用一个客户端javascript MVC框架,例如Backbone 或 AngularJS,你仍然可使用Mobify.js的图像API。首先,将Mobify.js库包含到你的app中:

<script src="//cdn.mobify.com/mobifyjs/build/mobify-2.0.1.min.js"></script>

而后,使用Mobify.js文档中列出的方法重写图片的URL。

Mobify.ResizeImages.getImageUrl(url)

该方法接受一个绝对URL而后返回图像调整后的URL。最简单的方式将图片传入这个方法是经过建立 模板辅助函数(template helper) (例如, {{image_resize '/obama.png' }} 在 Handlebars.js)执行了getImageUrl 方法来自动生成图像的URL。

在后端使用你的图像尺寸调整(Image-Resizing)

图像已经被Mobify's Performance Suite 尺寸调整服务器 调整了,它支持自动化调整WebP,CDN缓存等等。不过每月免费调整的数量有一个默认的限制,不过别担忧,若是你对流量的需求比较大,那么大声喊Mobify 一声,咱们会尽量的给予帮助。该API也容许你使用不一样的图像调整服务,例如Sencha.io Src,或是你本身的后端服务。

浏览器厂商怎么样能更好的支持相应式图像?

Webkit团队最近已经实现了src-set属性,那么Blink和Gecko也将在不久实现。这是正确道路上前进的一大步,这意味着浏览器厂商已经严重重视了相应式图像的问题。然而,它不能解决 art direction问题,也不能避免生成多个分辨率版本文件的步骤。

开发社区最近也一块儿讨论了响应式图片的问题。其中有一个有趣的提案是Ilya Grigorik提出的客户端提醒,该提议包括在每一个请求头部发送设备属性例如DPR和高度。我喜欢这个方案,由于它能让咱们继续使用img标签,而后在咱们须要建立分支逻辑实现art direction只须要使用picture(或srcN)便可。尽管添加额外的HTTP头和使用内容协商来解决这个问题已经被证实有效,对与拥有成千上万图像的网站来讲,使用该方案来定位图像恐怕不太可行。经过服务层或代理重写图像,能够解决这个问题,不过这两张方式均可能被设置的有问题。在我看来,若是在客户端能更好的控制资源加载,那这些个问题咱们是可以处理解决的。

若是开发者更好的控制了资源的加载,那么响应式图片应该会被看成一个简单的问题来处理。之因此如此多的响应图像解决方案是基于代理实习,是由于在文档到达浏览器以前,图像必须被重写。这是对预加载器尝试尽快下载图片行为的适应。不过代理的方案可能在安全性和扩展性方面会有不少问题,若是咱们有一个简单的方式来和预加载器交互,那么不少基于代理的方案就显得冗余了。

那么如何在更好的控制资源加载的同时,仍然享受预加载器带来的好处呢?这里关键点是咱们不但愿简单的关闭预加载器-它并行下载的能力是一个巨大的胜利,而且已经被浏览器实现了。(请注意捕获API不会阻塞并行下载。)其中一个方法是提供一个beforeload事件,该事件在资源载入以前触发。该事件在safari浏览器中经过一个扩展就能够实现了,在某些浏览器中也可使用,不过容量有限。若是如今可以使用该方案的话,那么就再也不须要捕获了。下面是一个使用beforeload事件的一个基础例子:

function rewriteImgs(event) {
    if (event.target === "IMG") {
        var img = event.target;
        img.src = "//ir0.mobify.com/" + screen.width + "/" + img.src;
    }
}
document.addEventListener("beforeload", rewriteImgs, true);

关键的挑战在于以某种方式在执行构图循环时候(in the main rendering loop)让预加载器和javascript交互良好。有一个当前正在浏览器中开发的一个新系统,叫作 Service Worker,
它的目的是容许开发者截获网络请求来协助构建离线web应用。然而,当前的实现不容许拦截初始请求。这是由于载入一个额外的脚本会阻塞其余资源的载入-不过我相信这个状况会改变,例如经过不牺牲性能的内联脚本的方式。若是你有以下的需求,能够考虑使用 Mobify.js 来自动处理响应式图片:

总结

响应式图片方面的问题有许多的解决方案,工做自动化的同时仍然能够art direction的方案驱动将来的Web开发的解决方案。

  • 只须要为每一个资源提供一份高版本的图片,让API实现提供基于设备条件(宽度,WebP支持状况等等)的小图片。
  • 每一个图片之发生一次请求。
  • 须要100%的语义和向下兼容旧标记而且不须要改变后端(使用捕获的状况)。
  • 已经开始使用picture元素来自动调整大小,只专一与使用它来实现art direction。

原文 Automate Your Responsive Images With Mobify.js 做者 Shawn Jansepar

相关文章
相关标签/搜索