2 媒体查询:支持不同的视口
如上一章所述,CSS3是由很多附加模块组合而成的。媒体查询就是其中的一个模块。媒体查询可以让我们根据设备显示器的特性为其设定CSS样式。例如,我们仅使用几行代码,就可以根据诸如视口宽度、屏幕比例、设备方向(横向或纵向)等特性来改变页面内容的显示方式。
本章内容
理解为什么响应式设计需要媒体查询
如何构造CSS3媒体查询
我们能够检测哪些设备特性
编写第一个CSS3媒体查询
为特定视口设定CSS样式
如何在iOS和Android设备上使用媒体查询
2.1 现在就能使用媒体查询
媒体查询已经被广泛使用,而且也被浏览器广泛支持(如Firefox 3.6+、Safari 4+、Chrome 4+、Opera 9.5+、iOS Safari 3.2+、Opera Mobile 10+、Android 2.1+和Internet Explorer 9+)。此外,要对老版本浏览器如Internet Explorer 6、7和8实现兼容修复(虽然基于JavaScript)也很容易。如果你现在想对Internet Explorer 6、7和8进行兼容修复,可以查阅第9章关于解决跨浏览器响应问题的内容。总之,没有什么理由能阻碍我们现在使用媒体查询。
W3C对规范有一套完整的审批流程(如果你有空,可以去看看该流程的官方说明,地址:http://www.w3.org/2005/10/Process-20051014/tr),从工作草案(Working Draft,WD),到候选推荐标准(Candidate Recommendation,CR),再到提议推荐标准(Proposed Recommendation,PR),几年之后,才能成为W3C推荐标准(REC)。所以那些相对成熟的模块使用起来比较安全。比如,CSS3变换模块Level 3(http://www.w3.org/TR/css3-3d-transforms/)从2009年3月起一直处于工作草案状态,相对于候选推荐标准的媒体查询模块,浏览器支持远远不足。 2.2 为什么响应式设计需要媒体查询 没有CSS3的媒体查询模块,就不能针对设备特性(如视口宽度)设置特定的CSS样式。如果你仔细研读W3C关于CSS3媒体查询模块的规范,就会看到媒体查询的官方解释:
HTML 4和CSS 2目前支持为不同的媒体类型设定专有的样式表。比如,一个页面在屏幕上显示时使用无衬线字体,而在打印时则使用衬线字体。screen和print是两种已定义的媒体类型。媒体查询让样式表有更强的针对性,扩展了媒体类型的功能。
媒体查询由媒体类型和一个或多个检测媒体特性的条件表达式组成。媒体查询中可用于检测的媒体特性有width、height和color(等)。使用媒体查询,可以在不改变页面内容的情况下,为特定的一些输出设备定制显示效果。 2.2.1 媒体查询语法 CSS媒体查询到底长什么样,更重要的是,它是怎么起作用的?
将下面这段代码插入到任意某个CSS文件的最后,然后预览与之关联的网页:
在现代浏览器(如果是IE,至少要IE9)中浏览该网页并不断调整浏览器窗口宽度。页面的背景颜色就会根据当前的视口尺寸而发生变化。为了清晰起见,我在这里使用了颜色名称,但实际上最好使用十六进制颜色值,如#ffffff。
接下来,让我们继续分析媒体查询,学习如何对其进行充分利用。
如果经常使用CSS 2样式表,你就知道可以通过<link>标签的media属性为样式表指定设备类型(如显示屏或打印机)。具体说来,就是在HTML页面的<head>标签中插入一个如下面代码片段所示的link标签:
媒体查询则能使我们根据设备的各种功能特性来设定相应的样式,而不仅仅只针对设备类型。可以将媒体查询想象成对浏览器的提问。如果浏览器回答“是”,则应用样式;如果回答是“否”,则不应用样式。相对于在CSS 2中能且只能问浏览器“你是一块显示屏吗?”,媒体查询能问的问题要多一点。例如,媒体查询可以问:“你是一块纵向放置的显示屏吗?”我们看看对应的实际代码:
首先,媒体查询表达式询问了媒体类型(你是一块显示屏吗?),然后询问了媒体特性(显示屏是纵向放置的吗?)。任何纵向放置的显示屏设备都会加载portrait-screen.css样式表,其他设备则会忽略该文件。在媒体查询的开头追加not则会颠倒该查询的逻辑。例如,下面的代码就会颠倒前例中的效果,会使非纵向放置的显示屏设备加载样式文件:
也可以将多个表达式组合在一起。如,我们扩展一下前面的例子,限制只有视口宽度大于800像素的显示屏设备才能加载文件。
更进一步,还可以写一个媒体查询列表。查询列表中的任意一个查询为真,则加载文件。全部查询都不为真,则不加载。例子如下:
这里有两点需要注意。第一,媒体查询之间使用逗号分隔。第二,你会注意到在projection之后,没有and,也没有任何特性/值的组合。没有后续表达式,意味着只要是projection就满足条件。本例中,样式会应用于所有的投影仪。
和以前编写CSS规则一样,基于媒体查询也可以按条件加载样式。在上面的例子中,我们在向页面的<head></head>标签中链接CSS文件时使用了媒体查询。除此之外,我们还可以在CSS样式表中使用媒体查询。例如,将下面的代码插入样式表,在屏幕宽度小于等于400像素的设备上,h1元素的文字颜色就会变成绿色。
还可以使用CSS的@import指令在当前样式表中按条件引入其他样式表。例如下面的代码会给视口最大宽度为360像素的显示屏设备加载一个名为phone.css的样式表。
切记,使用CSS的@import方式会增加HTTP请求(这会影响加载速度),所以请谨慎使用该方法。 2.2.2 媒体查询能检测那些特性 创建媒体查询时,最常用的是设备的视口宽度(width)和屏幕宽度(device-width)。依我的经验,很少需要检测其他特性。但是,为方便查阅,下面列出了所有可供媒体查询检测的特性,希望其中有能让你心动的特性。
width:视口宽度。
height:视口高度。
device-width:渲染表面的宽度(对我们来说,就是设备屏幕的宽度)。
device-height:渲染表面的高度(对我们来说,就是设备屏幕的高度)。
orientation:检查设备处于横向还是纵向。
aspect-ratio:基于视口宽度和高度的宽高比。一个16∶9比例的显示屏可以这样定义aspect-ratio: 16/9。
device-aspect-ratio:和aspect-ratio类似,基于设备渲染平面宽度和高度的宽高比。
color:每种颜色的位数。例如min-color: 16会检测设备是否拥有16位颜色。
color-index:设备的颜色索引表中的颜色数。值必须是非负整数。
monochrome:检测单色帧缓冲区中每像素所使用的位数。值必须是非负整数,如monochrome: 2。
resolution:用来检测屏幕或打印机的分辨率,如min-resolution: 300dpi。还可以接受每厘米像素点数的度量值,如min-resolution: 118dpcm。
scan:电视机的扫描方式,值可设为progressive(逐行扫描)或interlace(隔行扫描)。如720p HD电视(720p的p即表明是逐行扫描)匹配scan: progressive,而1080i HD 电视(1080i中的i表明是隔行扫描)匹配scan: interlace。
grid:用来检测输出设备是网格设备还是位图设备。
在上述所有特性中,除scan和grid之外,都可使用min和max前缀来创建一个查询范围。例如,分析如下所示的代码片段:
这里对width应用了min和max来设定查询范围。这样phone.css文件只会引入视口宽度介于200像素至360像素的显示屏设备。 2.2.3 用媒体查询改造我们的设计 毫无疑问,你肯定知道CSS代表层叠样式表。所谓层叠,就是指样式表中后面的样式会覆盖前面相同的样式(除非前面的样式具有更高的针对性)。因此我们可以在样式表的开头设置基本样式,以便适应所有设计,然后使用媒体查询来进一步重写相应的部分。例如,先针对大视口设计(用户大多数情况下使用鼠标),将导航链接设计成简单的文字链接;然后再针对较小的视口,使用媒体查询重写这部分样式,为拇指一族提供更大的点击区域。 2.2.4 加载媒体查询的最佳方法 现代浏览器虽然可以智能地忽略与自身不匹配的样式文件,但它却不一定不下载这些文件。因此,将不同媒体查询的样式保存到独立的文件中没有太大好处(个人喜好或为便于组织代码除外)。使用多个独立的文件会增加用于页面渲染的HTTP请求数量,从而导致页面加载变慢。
Respond.js(https://github.com/scottjehl/Respond)是为Internet Explorer 8及更低版本增加媒体查询支持的最快的JavaScript工具,但它目前无法解析CSS的@import命令。因此,建议在已有的样式表中追加媒体查询样式。使用如下语法即可在已有样式表中加入媒体查询:
2.3 我们的第一个响应式设计
不知道你此刻怎么想,但我热切地渴望开始设计一个响应式网页!我们已经理解了媒体查询的原理,那就让我们试着驾驭它们,看看它们在实际工作中如何发挥作用。而且我正好有用来测试的项目。下面请允许我说点题外话……
我喜欢电影。但是,我常常发现自己和别人的观点不同(或许这就是我日复一日独居一室写代码的原因之一),尤其是关于什么是好电影什么是烂电影。每当奥斯卡奖提名宣布的时候,我经常会有一种强烈的恶心反胃感。我总觉得各种类型的电影都应获得嘉奖。
我打算自己弄一个名叫And the winner isn’t的小网站,网址是http://www.andthewinnerisnt.com/。这里会褒奖那些本应获奖的电影,批评那些(不该获奖却)获奖的电影,这里还有视频剪辑、经典语录、电影海报,以及能证明我没错的在线调查(我知道没必要,但我喜欢)。 2.3.1 我们的设计是固定宽度的,不要惊讶 和我以前骂过的那些从不考虑不同视口的平面设计师一样,我开始也基于960像素的固定宽度网格制作一个界面原型。虽然理论上最好是从移动(小屏幕)设备的体验出发开始设计,然后渐进增强,但实际上要让每个人都理解这种思路的好处恐怕还需几年时间。在此之前,更多的可能是要将现有的桌面版网页设计改造成响应式的。既然未来几年我们可能都会这样做,那我们的设计还要从固定宽度着手。下面的截图展示了一个粗略的固定宽度的界面原型:
将这个设计分解,可以看到它有几个简单通用的结构:头部、导航、边栏、内容区和页脚。恐怕你每周都会构建一遍类似的结构吧。
第4章会告诉你为什么应该使用HTML5标签。不过此时我将略过这点,因为我们想急切地检验刚讲的媒体查询技巧。所以我们将使用旧的 HTML 4标签来进行我们的第一次媒体查询试验。使用HTML 4标签编写的没有实际内容的基本页面结构如下所示:
用Photoshop打开设计文档,我们可以看到头部和页脚都是940像素宽(两边各有10像素外边距),而侧边栏和内容区分别占据了220像素和700像素,相应地两边也各自有10像素的外边距。
首先,我们要在CSS中给页面主要的结构模块(头部、导航、侧边栏、内容区和页脚)设置一下样式。在插入重置样式之后,页面的主体CSS代码应该如下所示:
为了说明页面的结构,我在给各个结构模块追加额外内容的同时,还为各个结构模块设置了不同的背景颜色。
重置样式就是一组CSS声明,用来覆盖不同浏览器渲染HTML元素时的各种默认样式。重置样式一般会被加入到主样式文件的开头,用来将各个浏览器的自有默认样式重置成统一表现,确保样式表中后续追加的样式在不同浏览器中有相同的显示效果。世界上没有完美的重置样式,许多开发者都有自己的一套。我在HTML 4页面中使用的重置样式,是在Eric Meyer的原版(http://meyerweb.com/eric/tools/css/reset/)基础上加上了我的一些个人偏好及技巧,这些技巧是我从另外一些天才如Dan Cederholm (http://simplebits.com)的代码里学来的。如果你现在还没用过重置样式,那么强烈建议在你的HTML 4文档开头插入Eric的重置样式。我觉得针对HTML5文档有更好的选择,如normalize.css(http://necolas.github.com/normalize.css/),第4章会详细介绍。
在一个视口大于960像素的浏览器中,页面的基本结构应如下所示:
还有好几种使用CSS制作同样效果的固定宽度左右分栏结构的方法,你肯定有自己偏爱的方法。不过这些方法都普遍存在一个问题,当视口缩减到小于960像素的时候,右侧的内容区就开始被截断。 2.3.2 响应式设计中要保证图片尽可能精简 为了说明上述代码结构的问题,我已经提前参照设计图在CSS中增加了一些装饰性样式。既然要做响应式设计,那我肯定会用最精简的办法来切取背景图片。例如,对于设计图顶部和底部的小彩旗,我不会制作一个长条图片,而只是切取其中的两面小旗。这个切片作为背景图在视口中水平重复,从而给人一种长条旗帜图片的错觉(不论视口有多宽都行)。这种方法为两处背景分别节省了16KB(960像素宽的.png长条旗帜图一张20KB,而精简的切片图只有4KB)。这样你就为通过手机上网的用户节省了一部分流量!下图展示了这个切片图输出前的样子(放大到了600%):
在背景图片和文字大小都设置停当之后,And the winner isn’t…网站在浏览器中的效果如下所示:
要让样式完美,还有很多工作要做。例如,导航按钮颜色应该交替使用红色和黑色,缺少内容区的THESE SHOULD HAVE WON按钮和侧边栏的full info按钮,字体与设计图效果相去甚远。不过,所有这些问题都可以使用HTML5和CSS3来解决。使用HTML5和CSS3,而不再是简单插入图片(如我们以前做过的那样),会使网站与我们的响应式目标步调一致。时刻谨记,我们要保证代码和数据都尽可能精简,以便为带宽有限的用户提供愉悦的体验。 2.3.3 小视口下的内容剪切 现在,我们把美学问题放在一边,重点关注一下当视口缩减至小于960像素的情况,此时正在制作的首页中会有一部分内容被切掉。
这还只是将视口缩减到673像素宽,想象一下网站在iPhone 3GS等设备上将会有多难看?iPhone 3GS的显示屏大小只有320×480像素。看看下面的效果图:
咦,等等,这效果看着挺好,或者说还行……刚才你还说会难看的!这是因为iOS上的Safari浏览器默认是在980像素宽的画布上渲染页面,然后将画布缩小到与视口大小匹配。虽然得放大页面才能看清楚,但页面内容没有被切掉。怎么阻止Safari或其他移动浏览器做这样的默认处理呢? 2.4 阻止移动浏览器自动调整页面大小 iOS和Android浏览器都基于WebKit(http://www.webkit.org/)核心。这两种浏览器以及很多其他浏览器(如Opera Mobile),都支持用viewport meta元素覆盖默认的画布缩放设置。只需要在HTML的<head>标签中插入一个<meta>标签。<meta>标签中可以设置具体的宽度(如像素值)或者缩放比例如2.0(设备实际尺寸的两倍)。下面是一个将页面放大到设备实际尺寸两倍显示的meta标签的示例:
将这个''标签插入到我们的HTML中,如下面的代码所示:
然后,在Android中加载该页面,显示效果如下所示:
如你所见,这并不是我们最终想要的效果,但这个夸张的效果说明了我们讨论的问题。
安装iOS模拟器和Android模拟器
虽然真机测试无可替代,但还是可以使用Android和iOS模拟器。Windows、Linux和Mac版的Android模拟器都可以免费下载,Android软件开发工具包(SDK)也可以免费安装。下载地址是http://developer.android.com/sdk/。不过得使用命令行安装,需要你有一颗勇敢的心(1)。iOS模拟器是Xcode开发包(在Mac App Store中免费下载)的一部分,只能在Mac OS X上使用。一旦安装了Xcode,你就可以在这个路径下找到模拟器:~/Developer/Platforms/iPhoneSimulator.platform/Developer/Applications iOS Simulator.app
我们来分析一下上面所示的<meta>标签,以理解它的工作原理。name="viewport"属性不言而喻。content="initial-scale=2.0的意思是将页面放大两倍(同理,0.5表示缩小一半,3.0表示放大3倍),同时width=device-width告诉浏览器页面的宽度应该等于设备宽度。
<meta>标签还可以控制页面可缩放的范围。下面的代码允许用户将页面最多放大至设备宽度的3倍,最小压缩至设备宽度的一半。
你还可以禁止用户缩放,不过缩放是一个重要的辅助功能,所以在实践中很少禁用。
user-scalable=no即是禁止缩放。
现在,我们将缩放比例设置为1.0,这表示浏览器将按照其视口的实际大小来渲染页面。将宽度设置为设备宽度,意味着支持该特性的浏览器都将会按照设备宽度的实际大小来渲染页面。下面是我们最终将要使用的<meta>标签:
在竖直的iPad上浏览页面,可以看到还是有一部分内容被剪切掉了,但已经不再是缩小版的页面了!这就是本节想要达到的目的。相信我,这是个进步!
鉴于viewport meta标签的使用越来越普遍,W3C正尝试通过CSS引入同样的功能。可以访问http://dev.w3.org/csswg/css-device-adapt/,了解新的@viewport声明。基本思路是不用在<head>标签中添加<meta>标签了,而是可以在CSS中编写@viewport { width: 320px; }声明,同样可以将浏览器视口宽度设置为320像素。有些浏览器已经支持这种语法(如Opera Mobile),不过要使用私有前缀,如@-o-viewport{ width: 320px; }。 2.5 针对不同视口宽度修正设计 设置viewport meta标签后,任何浏览器都不再缩放页面了,现在我们可以针对不同视口来修正设计效果。首先在CSS中为平板设备(如iPad)增加媒体查询,竖直iPad的视口宽度是768像素(横向视口宽度是1024像素,此时页面渲染效果很理想)。
媒体查询针对视口宽度不大于768像素的情况,重新调整了外壳、头部、页脚以及导航等页面元素的宽度。下图展示了此时页面在iPad上的效果:
上图的效果让我很受鼓舞。页面内容没再被剪切,而是与iPad屏幕(或者其他视口不超过768像素的设备)恰好匹配。但还得解决导航区链接超出背景图和主内容区浮动在侧边栏之下(因为内容区太宽,在有限的空间内放不下)这两个问题。我们再修改一下CSS中的媒体查询,代码片段如下所示:
现在,侧边栏和内容区填满了页面,且两边各留有适当的内边距。但这样的效果并不引人注目。我想把内容放在前面,把侧边栏放在后面(侧边栏的重要程度一般不高)。如果说我正在尝试制作一个真正的响应式设计,那在此处我又犯了一个低级错误。 2.6 响应式设计中内容始终优先 我们想让设计在多平台多视口的情况下保留尽可能多的内容(而不是使用display:none或类似方法来隐藏部分内容),但也要意识到内容模块显示顺序的重要性。目前,页面中侧边栏和主内容区标签的顺序决定了侧边栏会显示在主内容区前面。显然,窄视口设备的用户应该先看到主内容,而后再看到侧边栏。
我们还可以(或许应该)将内容区移到导航区域之上。这样那些使用小视口设备的用户就可以先看到主内容。这样无疑是坚决贯彻“内容优先”原则的合理做法。但是,多数情况下每个页面顶部还是应该有导航区,所以我乐得只将HTML代码中的侧边栏和内容区位置互换一下,让内容区出现在侧边栏之前。当前代码结构如下:
互换位置后的代码如下:
虽然我们互换了标签位置,但页面在大视口中的显示效果没有变化,因为侧边栏和内容区分别使用了float:left和float:right属性。但是在iPad上,则变成了首先显示内容区,下面才是侧边栏。
在调整好标签结构的顺序之后,我还着手为768像素宽的视口追加并替换了一些样式。现在最新的媒体查询代码如下所示:
此处添加的代码只会对视口等于或小于768像素的显示屏设备起作用。视口更宽的设备会忽略这些代码。另外,因为这些样式放在文件的末尾,所以会覆盖之前的重名样式。结果就是视口大的设备上没有什么变化,而视口宽度为768像素的设备上最终的效果如下所示:
不消说,我们没准备拿什么设计大奖。但仅通过几行CSS媒体查询代码,就为不同的视口做出一个完全不同的布局来,这还是挺牛的。这些代码都是什么意思呢?
首先,使用媒体查询将所有内容区宽度置为全屏:
然后是一些美化页面元素及布局的样式。例如,如下代码片段改变了导航区大小、布局以及背景,这样平板用户可以更方便地选择导航条目:
现在,同样的内容可以根据视口大小以不同的布局来显示了。媒体查询很赞,不是吗?太值得庆祝一下了。当你开香槟的时候,我用iPhone看了看页面的效果……如下图所示:
2.7 媒体查询只是必要条件之一
我们回顾一下的前面所讲的内容。很显然我们的工作还远未结束,网站在iPhone的320像素宽的小视口中显示得很糟糕。媒体查询尽其所能,根据设备特性应用了对应的样式。但问题是,现有的媒体查询只覆盖了小范围的视口。视口宽度小于768像素的设备都将看到内容被剪切,而视口介于768像素到960像素之间的设备,则会使用未受媒体查询样式影响的原有样式,结果我们已经知道了,一旦视口宽度小于960像素,页面就无法匹配(作者抱头长叹)。
我们需要流动布局
如果针对已知的特定访问设备,可以单独使用媒体查询来制作理想的设计效果,我们已经见过专门适配iPad有多简单。但是这种策略有严重的缺陷,换句话说,它不能适应未来的变化。目前的情形是,页面捕捉到媒体查询设置的断点,然后布局发生变化。但在捕捉到下一个视口断点之前,页面静止不变。我们需要比这更好的策略。针对各种视口的排列组合编写对应的CSS样式,无法兼容未来可能出现的设备;而一个完美的设计,往往能在一定程度上适应未来的发展。在这点上我们目前的解决方案尚不完备。目前的效果更像是一个自适应设计,而不是我们想要的真正的响应式设计。我们的设计应该在突变之前保持灵动。要做到这点,需要将呆板的固定布局修改成灵活的流动布局。 2.8 小结 本章我们学习了什么是CSS3媒体查询,如何在CSS文件中引入媒体查询,以及如何使用媒体查询来制作响应式网页。讨论了如何让移动浏览器像桌面版浏览器一样渲染我们的页面,另外还谈到了在编写页面标签时要遵守“内容优先”的原则。此外还学习了在设计中使用图片时如何保证尽可能精简。
然而,我们也认识到媒体查询只能为我们提供自适应设计效果,不能真正实现响应式设计。对于响应式设计来说,媒体查询是必需的,而能让我们的设计在媒体查询设置的断点之间灵动显示的流动布局技术同样必需。创建基本的流动布局,保证页面在媒体查询断点之间的显示效果平滑流畅,是我们下一章将要讲述的内容。
【注释】
(1)最新Windows版能够可视化安装。