注:这篇文章发表于September 30th, 2011 ,作者是Jason Grigsby
在响应式图像Part 1中,我站在一个比较概况的层面,解释了什么是响应式图像,这其中需要解决的问题以及一些共同面临的议题。在这篇文章中,我会对于响应式图像中设计的具体技术做一个更详细的论述,并且来看看这些技术的可用性。如果你还没有读过part 1,也许你会想要先读一下以便了解我在文中用到的一些术语。
当我两个月以前开始做这个工作的时候,我觉得工作的最后我可以说,“这是最有效的三种方法。去下载它们并整合进你的系统吧。”现在看来,这实在是太天真了。
现在我发现的是并没有一个综合的解决方案。相反,我们做了好几个月实验,每个实验都有它的优点与缺点。
因为这个原因,我们应该了解一些通用的元素和挑战,这样,对于我们的解决方案中的每个部分,我们就能选择相应最好的技术了。
被放弃的一些方法
动态库标签(Dynamic Base Tag)
很多早期技术都使用Javascript来动态改变库标签。新的库标签将会在路径中加入用于指示需要解析的图像大小的信息。文件加载以后,库标签就会移除。
但是,这种方法会遇到我在part 1中所说的race conditions的问题。
(译者注:关于race conditons,参见http://linux.chinaitlab.com/man/linux/HOWTO/Secure-Programs-HOWTO/avoid-race.html,race condition是由于事件发生的相对时间之间的依赖而引起的异常行为问题。)
我发现Google Chrome会同时下载移动端和桌面端的图片。Scott Jehl发现这个问题是由于内部和外部Javascript不同处理方式间的区别引起的。他对webkit提交了一个bug,并将其标记为“不可修复”的,原因如下:
插入库标签会改变页面上随后的所有的URLs。任何脚本都有可能插入一个库标签来避免双重加载,除非有一个挂起的脚本(a pending script)加载,否则不会加载任何事物。这意味着预加载被禁止了,这就脱离了讨论范围了。
在理论上说,你仍然可以使用内联动态库标签,但是Filament Group主要使用的还是基于cookies的办法,因为这种方法似乎更安全。
暂时的图像(Temporary images)
另外一个早期的技术是让图像的src指向一个暂时的图像,然后让Javascript用一个正确的文件路径来替换这个图像的源。在大多数情况下,这个图像是一个一像素的透明的gif图像,它使用了缓存技术,这样,无论它在页面中被引用多少次,浏览器都只需要请求一次。
这种技术的问题在于一旦Javascript失效,浏览器将永远不会加载图像。
基于Javascript的解决方案
你在哪里存储不同版本图像的路径?
如果图像指向的是‘small.jpg’,那么,对于需要加载大图像的大屏幕,你将‘large.jpg’的信息放在哪里?
URL参数
一种解决方法是将不同版本图像的路径作为url参数放在图像属性中。它最简单的形式如下:
- <img src=”small.jpg?full=large.jpg”>
如果你有好几个不同大小的图像,只需要在url中增加一部分值就可以了。这种方法的关键在于将其和一个.htaccess文件绑定。
潜在的CDN,代理,以及缓存问题
使用URL参数的最大缺点在于在内容分发网络以及使用代理(代理在对内容进行缓存的时候不会注意到url参数)的情况下会引起问题。一些缓存算法会忽略任何有url参数的内容,这意味着由于图像不会被缓存,页面会变慢。
另外一些缓存算法只会缓存它们看到的第一种版本的图像。如果使用代理缓存看到图像的第一个人恰好是在移动终端上看到图像的,那么随后使用同样的代理缓存的人访问网站的人看到的都将是适用于移动端大小的图像,不论他们使用什么平台,除非这样的缓存过期了。
这在多大程度上会成为一个问题呢?就此我询问了Steve Souders。他说这足以构成一个问题因此你不能忽略它。这引发了Bryan 和Stephanie Rieger对于缓存和CDNs的一些评论。
因此,我认为我们应该找到一些不需要使用url参数的技术。
这种方法的一些例子:
响应式图像JS主分支(https://github.com/filamentgroup/Responsive-Images)
响应式图像切换(https://github.com/allmarkedup/responsive-images-alt )
响应式图像及能感知上下文的大小改变(http://www.craig-russell.co.uk/responsive-images-and-context-aware-image-sizing/)
使用Doubletake.js的响应式图像(http://www.grahambird.co.uk/lab/doubletake/ )
使用PHP及jQuery的响应式图像(http://www.jamesfairhurst.co.uk/posts/view/responsive_images_with_php_and_jquery/)
使用cookies的响应式图像(http://blog.keithclark.co.uk/responsive-images-using-cookies/ )
感知上下文的响应式图像(https://github.com/ahume/Responsive-Images )
数据属性
文件路径不是放在url参数中,而是放在一个或者更多的数据属性中。例如:
- <img src=”small.r.jpg” data-fullsrc=”large.jpg”>
哪一个元素要加入数据属性以及要加入多少都是依赖于具体技术。
循环检查每一个图像
我所知道的这种技术的唯一的缺点是需要循环检查每一个图像的数据属性,然后根据屏幕大小修改源属性。这对于台式机浏览器来说可能不是个大问题,因为在台式机中是循环被大量使用的地方。
这种方法的一些例子:
响应式图像基于数据属性的JS分支(https://github.com/filamentgroup/Responsive-Images/tree/data-attribute-based )
测试响应式图像(http://www.monoliitti.com/images/ )
使用noscript标记创建响应式图像(http://www.headlondon.com/our-thoughts/technology/posts/creating-responsive-images-using-the-noscript-tag)
约定的文件存储结构
在这种方法中,文件路径不是包含在HTML文件中,而是假定图像在服务器上是一种约定俗成的方式放置的。例如,所有的小图像可能都是在/images/sml/文件夹中,而大图像都是在/images/lrg/图像中。
如果是这样的话,html页面就不用为两种图像都提供路径。它只需要提供图像的文件名(例如,boat.jpg),然后让Javascript来修改图像源以便与屏幕大小适应(例如,如果是台式机,图片源就是/images/lrg/boat.jpg for desktop)。
这种方法的一些例子:
响应式图像切换(https://github.com/allmarkedup/responsive-images-alt )
自适应的图像(http://adaptive-images.com/ )
动态文件名
我在part 1中提到我们或许需要任意图像尺寸。这个问题的一些解决方案是基于一种假设,即你可以通过url传递你所想要的大小,然后得到这样大小的图像。
因为图像大小调整是即时完成的,因此没有必要在Html文件中存储不同文件路径。Javascript会修改文件名,将类似于‘boat.jpg’的名字改成‘boat-480×200.jpg’。同时,也不存在缓存或者CDNs的问题,因为任何一个图像都是独特的。
有些图像确实不能被调整大小
这种方法对于人工选择不同大小的图像并没有提供很好的解决方案。它假设大小调整后的图像能在任何情况下工作,这显然是不能的。
这种方法的一些例子:
响应式图像JS中有意义的一些分支(https://github.com/filamentgroup/Responsive-Images/tree/meaningful-base)
响应式图像及tinySrc(http://blog.trasatti.it/2011/05/responsive-images-and-tinysrc.html )
.htaccess的角色(或类似的重写规则)
这种解决方案是基于服务器重写规则的。例子通常是使用Apache 的.htaccess文件写的,单它们可能是任何类型的重写规则。
我们来看看一个.htaccess文件的片段(来源于Responsive Images JS cookie-based branch),来看重写规则是如何使用的:
RewriteEngine On
#large cookie, large image
RewriteCond %{HTTP_COOKIE} rwd-screensize=large
RewriteCond %{QUERY_STRING} large=([^&]+)
RewriteRule .* %1 [L]
for the url contains a value for large. This .htaccess file is looking for something like:第一条开启了重写规则。接下来是一对条件(RewriteCond)。第一个条件检查是否有一个名为rwd-screensize值为large的cookie。第二个条件检查url的查询语句是否包含一个针对大图像的值。这个.htaccess文件试图找到一些:
- <img src=”small.jpg?largelarge=large.jpg”>
如果两个条件都满足——cookie被设置为针对大图像且查询语句中有针对大图像的值——则重写规则会发送在查询语句中指定的文件(在上面的例子中,是large.jpg)。
rwd-screensize的cookie值是通过Javascript测试屏幕大小以后进行设置的。
你如何避免浏览器下载好几个图像?
排除了一些基本的方式,我们现在开始谈论最棘手的部分。正如第1部分所提到的,在浏览器开始下载图像之前拦截浏览器,以便可以评估并改变这些图像的来源是棘手的问题,并可能导致race conditions。
现在,动态库标签已被排除,还剩下两种主要技术。
设置cookie
这是Filament Group为Boston Globe提出的解决方法。在文件头部加入了Javascript,这样就能很快做出评估了。
在它决定了屏幕大小以后,它就会设置一个cookie。每个随后的图像请求都会包含这个cookie,服务器端可以使用cookie来决定发送给用户的最好图像。
可能存在的问题
如果浏览器不支持cookie或者用户禁用了它们,插入到文件头部的Javascript就不起作用了。
另外,Yoav Weiss做过一些测试,证明IE9在这种情况下仍然会重复下载文件,Firefox在脚本处于外部的时候也会重复下载文件。这意味着cookies可能带来race condition的问题,而正是这个问题让我们放弃了动态库标签方法。
这种方法的一些例子:
响应式图像的cookie分支(https://github.com/filamentgroup/Responsive-Images/tree/cookie-driven)
使用cookies的响应式图像(http://blog.keithclark.co.uk/responsive-images-using-cookies/)
响应式图像切换(https://github.com/allmarkedup/responsive-images-alt )
#p#
Noscript标记
在过去的几个月里,使用noscript标记作为一种阻止多余下载的新的技术已经出现了。我看到的第一个使用这种技术的是Mairead Buchan,她描述这种技术有着“涉水河马的优雅( the elegance of a wading hippo)”。我认为这种技术还是有一定前景的。
Antti Peisa独立实现了一个更为清晰的noscript方法。下面是html:
- <noscript data-large=’Koala.jpg’ data-small=’Koala-small.jpg’ data-alt=’Koala’>
- <img src=’Koala.jpg’ alt=’Koala’ />
- </noscript>
- The values for the various sizes of image tags are stored in the data attributes on the noscript tag itself. Antti then provides sample jQuery code used to process the image:
- $(‘noscript[data-large][data-small]‘).each(function(){
- var src = screen.width >= 500 ? $(this).data(‘large’) : $(this).data(‘small’);
- $(‘<img src=”‘ + src + ‘” alt=”‘ + $(this).data(‘alt’) + ‘” />’).insertAfter($(this));
- });
这些代码检索了整个文件来找到有合适数据属性的noscript标记。它对屏幕大小进行了测试,然后插入一个有着合适文件路径的新的图像标记。
No race conditions!
当使用noscript标记时,没有给定的race conditions。在noscript标记中的图像从来不会开始下载。Mairead解释说“这是有效的,因为noscript标记的孩子不会被添加到DOM中”
这是有道理的。浏览器知道在它开始加载页面之前Javascript是否有效。如果Javascript有效,就不用担心noscript标记中的项目了。如果它们没有被添加到DOM中,则它们肯定不会被下载了。
这种技术同样是有缺点的,当Javascript没有启用或者是不依赖于cookies或htaccess文件时,这种方法就不太有效。
可能的陷阱
最大的陷阱是声称支持Javascript但是却实现得很糟糕的设备。例如,黑莓4.5有Javascript,但是Javascript不能操作DOM。在Ergo中,noscript不会被用到因为尽管脚本是可用的,但是脚本不能成功加载一个新的图像标记,所以没有图像会显示出来。
需要注意的是,这是我的估计。我知道黑莓4.5是如何工作的,但是我并没有做过实际测试。
尽管这种方法不会引起race condition,但要确保Javascript执行的速度够快。插进所有这些图像可能需要浏览器将页面回流(reflow the page)。这也可能导致浏览器加载变慢因为它不能预加载。
由于对执行速度有一定需求,因此将jQuery对Antti’s javascript的依赖移除并将代码放在文件头部是一种较好的做法。
这种方法的一些例子
使用noscript标记创建响应式图像( http://www.headlondon.com/our-thoughts/technology/posts/creating-responsive-images-using-the-noscript-tag)
测试响应式图像(http://www.monoliitti.com/images/ )
没有cookies或者服务器端逻辑的响应式的感知上下文的图像(https://gist.github.com/1200270)
屏幕大小是正确的依据吗?
这些技术大多数都是依据屏幕大小来决定应该使用什么尺寸的图像。Andy Hume指出屏幕大小可能是具有误导性的。他写到:
解决这个问题的内容驱动的方法是根据图像是否会被拉伸来决定应该加载什么图像。如果一个图像被拉伸了,它看起来就会像素化或者模糊。在这种情形下,我们会想要加载一个粳稻分辨率的图像。
Andy为响应式图像提出的解决方案可以解决这个问题(并提供了对nginx的支持)。
波士顿环球时报的响应式图像失败了
我期待环球时报的发布有一阵子了,因为它是工程以及设计的壮举。它的大浏览量使得它可以用来测试响应式图像的不同方法并来看看什么方法才是有效的。
他们选用的技术结合了数据属性和cookies。不幸的是,环球时报网站上的响应式图像以及失效了。这个问题大家有目共睹,他们正在想办法解决。
这一结果的后果是我们还没有任何一项响应式图像相关的技术的大规模部署,因此也就不能依靠实践来证明什么技术是经过实践检验有效的。
Most promising javascript only techniques
在我看来,cookies加上数据源(data-src)以及noscript是最有希望的两种技术。两种技术都存在问题,但是与其他方法相比它们的缺陷更少。
Server端的解决方案
大多数Javascript技术都几乎不需要来自于服务器端的支持。有一些替代方案可以用来平衡服务器端的负载。
用户代理字符串解析
一些人已经提出了一些解决方案,这些方案包含轻量级的用户代理字符串解析来区别不同手机。如果用户代理可以确认是iPhone或者Android,那么也就意味着移动设备是iPhone或者Android,可以根据这个信息来设置合适大小的图像。
不像其他的很多开发者,在利用用户代理字符串进行设备检测时,我没有遇到什么问题。如果你想要在手机上进行试验,你需要通过WURFL, Device Atlas等来进行真实的设备检测。简单化的正则表达式匹配和有关屏幕尺寸的假设是行不通的。
设备检测
有几个不同的方法依靠设备检测以确定屏幕尺寸并提供适当的图像。设备检测数据库包含了一些基本信息如屏幕尺寸。
Sencha.io Src (旧称 TinySRC)
James Pearce创建了一个非常棒的服务TinySRC。他后来为Sencha工作,TinySRC成为了Sencha.io Src。Sencha.io Src能自动为你修改图像大小。你需要在你的图像标签中以如下方式引用Sencha.io Src:
http://src.sencha.io/http://www.myapp.com/myimg.jpg
当一个浏览器请求了上述地址时,Sencha.io Src会查找发出请求的设备的用户代理,以决定什么样的图像尺寸是合适的。然后它会从你的服务器抓取图像并修改图像大小。然后它会将大小修改过以后的图像进行缓存以便后来的请求可以更快响应。
除了上述的自动模式,Sencha.io Src还可以让你自己指定图像修改以后的大小。
将Responsive Images JS与 Sencha.io Src结合
Andrea Trasatti引用了Scott Jehl的Responsive Images JS方法来将Responsive Images JS与Tinysrc结合起来。脚本利用Javascript找到屏幕大小然后使用htaccess来从Sencha.io Src请求正确大小的图像。
Andrea的这个方法很早就写成了。它仍然使用动态库标签、url参数,并且会引起“对每一个图像都有一个HTTP请求,这个请求本可能避免的”。但是所有这些缺陷都可以通过结合一些新的方法来修复。
可能的缺陷
首先,如果你对设备检测有反感的话,那么你就会不太想用Sencha.io Src或者你需要在使用它时来自己指定你想要的图像大小。
顺便说一句,我发现一件很有趣的事情,人们讲设备的检测和用户代理字符串的坏话,但接下来却建议使用TinySRC。我曾经看到一个幻灯片,前面驳回了设备检测,后面的好几张却在讲TinySRC是多么出色。在一个更为实际的层面,你需要评估一下这个服务是否会长期存在并考虑一下如果所有指向sencha的url突然间消失了会发生什么。我非常了解James,他当然是想要尽可能地让这个服务永远运行下去。但尽管如此,一个服务的长期可用性仍然是需要考虑的。
基于WURFL的解决方案
WURFL是最大的开源设备数据库。在这个月早期参加了突破性的开发(Breaking Development)会议以后,Carson McDonald收到启发,开发出一个针对图像的基于WURFL的解决方案。
(顺便说一句,突破性的开发(Breaking Development)会议是北美移动网络相关的最好会议。你可以注册参加一下。)
Carson指出他的方法在CDN以及及缓存方面会有一些问题,因为不同大小的图像都是来自于同一个url。
图像大小修改服务
Google的mod_pagespeed
Google的mod_pagespeed Apache module将很多任务自动化了,并且包含一个选项让你可以即时剪裁图像。有很多方式剪裁图像(GD, ImageMagick等)。我之所以要提出mod_pagespeed是因为我不曾考虑过它,直到在一个论坛看到有人推荐它。我不知道是否有人试过用它来解决响应式图像的问题。
将客户端和服务器端的方法结合起来
自适应的图像
你现在应该已经意识到了,很少有方法是你应用以后就可以高枕无忧的,你还需要做一些小的修改。两种最接近即插即用的方式是Sencha.io Src和Adaptive-Images.com.
A自适应图像是由Matt Wilcox开发的。它在头部修改了响应式图像的前提,假设页面的标记包含的是大的图像而不会从移动设备版本开始启用。
这种方法包含三部分:
1.一个头部的小的Javascript片段用来设置有屏幕高度和宽度的cookie。
- <script>document.cookie=’resolution=’+Math.max(screen.width,screen.height)+’; path=/’;</script>
2.一个.htaccess文件将所有的对图像的请求写到了一个php文件中。你可以指定不进行此重写的目录。
3.一个php文件用来在你自己配置的断点之上来来图像大小。
Matt的方法的最好的地方在于你可以将你的图像文件分开,这样你就可以排除那些不需要重设大小的图像,你可以在不对现有方法做任何改变的情况下实现这个技术。现有的页面会立刻拥有不同的图像大小。
现在来谈谈问题
你一定在想一切不会就这么简单,对吗?
Matt在下面做出了评论并指出默认的设置会导致如果Javascript缺失的话就会传送一个小的图像。该标记将指向一个大的版本,但PHP文件返回一个小的版本。所有这一切都可以配置的。
他同样指出,race condition的结果不会是下载好几个图像。我认为race condition还存在好几个不同的缺陷,但我还将继续和Matt进行探讨。
我也忽略了一个事实,即不管图像大小如何,url都是一样的,这可能导致CDN以及代理缓存方面的问题,就和前面提到的一样。
Yiibu profile approach
Brian 和 Stephanie Rieger在突破性的开发大会上展示了他们为browser.nokia.com所作的工作。在这个项目中,他们提出了一种新的方式来将客户端的信息和设备检测结合起来。
当一个浏览器除此从服务器端请求某些东西时,它们对设备一无所知。因此它们就会查询一个设备检测数据库来看是否能得到屏幕大小(以及其他一些细节)。接下来,它们会查询本地数据库获得默认信息。它们利用数据库中信息来决定浏览器工作的细节,发送合适的HTML、Javascript以及图像。
一旦浏览器获知这些信息,一个Javascript会开始运行来检测包括屏幕大小在内的浏览器的不同方面。然后它将这些信息存储在一个profile cookie中。
在第二次请求的时候,服务器端会接收到profile cookie并将它与默认数据库中的信息进行比较。它可能更新默认数据库。它会将服务器端的信息和客户端的特征检测数据结合起来放在一个修订后的文件(a revised profile)中。
我描述的可能不是特别清楚,你可以看看他们的幻灯片:
◆ 适应:为什么响应式设计是从服务器端开始的(Adaptation: Why responsive design actually begins on the server )
◆ 务实的响应式设计(Pragmatic Responsive Design )
这种混合式技术解决了初次加载的问题,并且不会引起race conditions以及其他一些只依赖于客户端的方案可能有的问题。这种技术不局限于图像,还可以用来解决内容以及Javascript上的问题。
听起来很不错。有什么收获?
这是很复杂的系统,需要对架构做很大的改变以获得支持。Bryan 和Stephanie公布了这个方法,但是代码还不能够下载。也许代码很快就能下载到了,但是他们在这个Nokia Browser project之后享受了一个假期。
也许最大的问题在于我们大多数都不是Riegers。他们已经在移动网络上开发多年,他们对于设备的默认知识是超群的。天才们的成功总是很难复制的。
同样的例子还有波士顿环球时报的项目。这个团队包括jQuery Mobile team的重心力量以及提出了响应式Web设计的人。我们的项目很少能有这样的配置。
总结
在我回顾这些不同技术的时候,我一直在想Andy Hume在part 1中回复的话:
我们现有的解决方案对于你提到的浏览器现有的(以及未定义的)行为有太多依赖。例如,如果某种特定的超前预分析器(a particular type of look-ahead pre-parser (http://goo.gl/TyzTi))在对HTML进行解析或者执行script之前就开始进行下载,那么现有的响应式图像设计就会失效了。(我隐约觉得我们总有一天会在这个上面跌跟头。)一种可能的方式是我们和浏览器供应商协调以便让浏览器是未来友好的(future-friendly.)。
这的确是事实。这些技术都基于一个期待,即浏览器下载事物的顺序和我们现在看到的一样。如果顺序改变了或者浏览器开始更多地进行预解析了,所有的一切都灰飞烟灭了。
这个系列的第三部分,我将继续关注改变图像标签或者替换它的方法,看有没有针对多文件源的更好的方法。
一些资源和致谢
这这篇文章中,我叙述了18种不同技术。我的笔记被发表在一个Google spreadsheet中,你可以来查看更为详细的评论。感谢公布了他们的想法和实验的每一个人,我从每一个人身上都学到了很多。
这篇文章如果没有Scott Jehl、 Bryan 和Stephanie Rieger的协助是写不出来的。尤其是Scott,他帮助我整理出主要的响应式图像的JS库的问题。感谢你们三个人,提出了很多真诚的问题,并拿出时间来解释你们一直以来做的工作。
原文:http://www.cloudfour.com/responsive-imgs-part-2/
译文:http://www.webapptrend.com/2012/01/1355.html
【编辑推荐】