使用一项叫媒体查询的CSS 功能,很容易检测出用户设备的屏幕大小。然后,据以提供替代或额外的CSS,可针对相应屏幕实现更加优化的体验。使用这种方式创建对设备有感知力的网站,被称为响应式设计。
响应式设计的要素
响应式设计包含三个重要的方面。
- 媒体查询:是一种CSS 语法,可以根据浏览器的特性,一般是屏幕或浏览器容器宽度提供CSS 规则;
- 流式布局:是使用em 或百分比等相对单位设定页面总体宽度,让布局能够随屏幕大小而缩放;
- 弹性图片:是使用相对单位确保图片再大也不会超过其容器。
媒体查询
媒体查询是CSS 代码的容器,其中的CSS 只在某些条件(比如,当前页面要被打印或者要显示在某种类型或尺寸的屏幕上)具备时才会应用。媒体查询可以用两种方式来写:@media 规则和<link>标签的media 属性。
@media规则
第一种方式是@media 规则,可以在样式表或<style>标签的CSS 中包含媒体查询,比如:
@media print {
nav {
display:none;
}
}
//这条规则声明:如果当前页面要打印,那么就不显示nav 元素。
大家注意,这里是把CSS 规则嵌套在了一个@media 规则中,乍一看似乎有点不太习惯。尽管可以把CSS 规则嵌套在媒体查询里,但媒体查询本身却不能互相嵌套。下面再看一个假设的示例,其中涉及最大屏幕宽度。
/*只在屏幕宽度不大于568 像素时应用*/
@media screen and (max-width:568px) {
.column {float:none; width:96%; margin:0 auto;}
}
媒体类型
最常用的媒体类型如下所示:
- all:匹配所有设备;
- handled:匹配手持设备(小屏幕、单色、带宽有限);
- print:匹配分页媒体或打印预览模式下的屏幕;
- screen:匹配彩色计算机屏幕;
- 其他媒体类型还有braille(盲文点字触觉反馈设备)、embossed(盲文分页打印机)、projection(投影仪)、speech(语音合成器)、tty(电话机屏幕等固定宽度字符栅格设备)和tv(电视机)。
当然,任意时刻浏览器窗口中只能使用一种媒体类型。媒体类型从IE6 开始就得到支持了,但媒体特性到IE9 以上才得到支持。一般来说这并不是问题,因为我们使用媒体特性多数情况下都是为了检测平板电脑或智能手机等现代设备。
媒体特性
媒体特性也就是媒体某一方面的特征,一般带有min-或max-前缀。最常用的媒体
特性如下:
min-device-width 和max-device-width:匹配设备屏幕的尺寸;
min-width 和max-width:匹配视口的宽度,例如浏览器窗口宽度;
orientation(值为portrait 和landscape):匹配设备是横屏还是竖屏。
如果想通过媒体查询来根据用户对浏览器窗口的缩放重新调整布局,应该使用min-width 和max-width。
可以使用逻辑运算符and、not、or 及关键字all、only 组合媒体类型和媒体特性。其中,only 关键字可以用来对不支持媒体查询的浏览器隐藏样式表。
<link>标签的media属性
如果要通过媒体查询应用的CSS 规则非常多,那么就可以考虑使用<link>标签的media 属性设定条件,有选择地加载独立的样式表。
<link type="text/css" media="print" href="css/print_styles.css" />
<link type="text/css" media="screen and (max-width:568px)"
href="css/iphone_styles.css" />
断点
断点(breakpoint)在这里指的是媒体查询起作用的屏幕宽度,其写法类似如下形式。
@media screen and (max-width:640px) { /*CSS 规则*/ }
//在这里,断点是640 像素宽。如果有设备的屏幕宽度等于或小于断点设定的宽度,
//那么后面的CSS 就会起作用。
用<meta>标签设定视口
iPad 和iPhone 会把适合大屏幕的网页缩小,以便在它们较小的屏幕上能看到网页的全貌。这是一个通用技巧,但对于手机——特别是iPhone来说,由于文字实在太小了,为了看清楚网页内容,肯定得用扩展手势放大页面,然后再来来回回地扫屏。如果你想让自己的页面布局适合这些小屏幕,首先就要覆盖这种自动缩小的设定。方法是在页面的<head>标签里添加一个<meta>标签:
<meta name="viewport" content="width=device-width; maximumscale=1.0" />
这个<meta>标签告诉浏览器按照屏幕宽度来显示网页,不要缩小网页。虽然这样可以让布局以实际宽度显示,但在iOS 设备(如iPad 和iPhone)中却会引发一个已知的bug。关于这个bug 及如何解决,后面再交待。
针对平板优化布局
@media only screen and (max-width:1000px) { /*1000 像素的断点*/
body {
margin:0 8px 20px; /*添加右外边距,以防滚动条碍事儿*/
}
#wrapper {width:98%;} /*布局由固定变成流动*/
header {
height:100px; /*增加页眉高度,为重新定位菜单留出空间*/
padding:1px 0 0 0; /*防止导航菜单的上外边距叠加*/
}
header nav.menu {
margin-top:65px; /*把菜单移动到页面标题和搜索框下方 */
}
section#feature_area {padding-bottom:0;} /*用不着了*/
section#feature_area article { /*让博文摘要部分与布局同宽*/
float:none; /*不需要浮动了*/
width:auto;/*自动填满布局*/
}
section#feature_area aside { /*原来的右栏同样与布局同宽*/
float:none;
width:auto;
}
section#feature_area aside form {
float:left; /*浮动到左侧*/
margin:15px 0 0 0; /*与nav 的上外边距一致*/
}
section#feature_area aside nav {
width:17em; /*缩小博文链接区的宽度*/
}
} /*1000 像素断点结束*/
针对智能手机优化布局
@media only screen and (max-width:640px) {
header {height:100px;}
header nav.menu {width:94%; font-size:.65em; } /*水平间距更多了*/
section#book_area article { /*博文摘要*/
width:auto;
float:none;
margin:0; padding:0; /*每本书的容器都与布局同宽*/
}
section#feature_area aside form,
section#feature_area aside nav { /*博文链接*/
margin:10px auto; /*添加上、下外边距*/
float:none;
}
/*图片容器与布局同宽*/
section#book_area article .inner {width:98%; margin:0 0 0 5px; }
#book_area .inner h3 { /*取消文本旋转效果*/
-webkit-transform:none;
-moz-transform:none;
-moz-transform-origin:none;
-ms-transform-origin:none;
transform:none;
position:static;
}
#book_area article img { /*相对设备宽度确定图片宽度 */
width:40%;
}
section#book_area {background:#fff; padding: 0 10px 10px; margin:0 0 10px;}
#book_area article aside { /*在图片旁边显示弹出层*/
display:block;
position:static;
float:right;
margin:0; padding:0 0 20px 0;
font-size:.8em;
border:none;
width:55%;
box-shadow:none;
}
section#book_area article aside::before, /*隐藏弹出层的三角*/
section#book_area article aside::after {
display:none;
}
}
针对竖屏进一步优化
@media only screen and (max-width:320px) { /*iPhone 竖屏*/
header {height:90px;} /*缩小页眉高度*/
header section#title h1 {font-size:1.25em;} /*文本再小一点*/
header section#title h2 {font-size:.75em;} /*文本再小一点*/
header form.search {top:6px; right:2px;} /*搜索框上移*/
/*按比例缩小,并上移菜单*/
header nav.menu {font-size:.55em; margin-top:55px;}
nav.menu ul li a {
padding:5px 4px; /*增大链接,方便点击*/
margin:0;
}
}
最后两个问题
移动Safari中的缩放bug
Safari Mobile(iPhone 浏览器)中有一个bug,在设备从竖屏旋转到横屏时会导致缩放和重绘问题。有一个JavaScript 脚本可以解决这个重绘问题,请参考这里:
http://webdesignerwall.com/tutorials/iphone-safari-viewport-scaling-bug
让下拉菜单支持触摸
最后,还有一个问题要解决,那就是让下拉菜单支持触摸操作。
问题在于,支持触摸的设备会跳过:hover 规则中对visibility 属性的过渡。似乎也不算太意外,毕竟这个属性只是简单地切换了一个布尔值的状态,从而实现显示和隐藏。但这也说明visibility 属性不是过渡动画的理想对象。除非我去掉visibility属性,否则菜单在触摸屏上没法用。可是如果真这样做,在非触摸屏上只要鼠标经过菜单下方,就会导致下拉菜单显示出来。因为下拉菜单虽然处于完全透明状态,但它对鼠标还是“可见的”。解决方案是使用Modernizr 检测设备是否支持触摸,如果支持再去掉对visibility 属性的过渡。如果设备支持触摸,Modernizr 会给根元素html 添加一个touch 类,我们就可以针对触摸设备单写一条规则:
/*Modernizr 检测到触屏,再去掉妨碍菜单过渡的visibility 属性*/
.touch nav.menu li ul {
-webkit-transition:1s opacity;
-moz-transition:1s opacity;
transition:1s opacity;
}
这条规则对(不支持触摸的)非移动设备是不适用的。在触摸设备上,有了它用户在触摸相关菜单项时,下拉菜单就会显示出来。不过,当用户再触摸别的地方时,菜单会立即消失,而不是淡出屏幕。实际上,淡出效果的唯一好处,就是当用户鼠标意外离开时,再移回去还能把菜单“召回”。因此,在不使用鼠标的触摸设备上,这种“突然消失”的效果还是可以接受的。
请大家注意,这个菜单只有在触摸屏幕其他地方的时候才会消失。因为触摸其他地方会触发收缩bug 的代码(上一节介绍过),强迫JavaScript 运行,这样就足以让菜单意识到当前已经不再是悬停状态而关闭了。另一个实现这种“触摸其他地方关闭”效果的方法是执行某个JavaScript 函数。比如,下面就是执行jQuery 的noop 函数(英文noop 的意思是“无操作”)的例子。虽然这个函数什么也不做,但只要通过触摸屏幕其他地方来调用它,就可以关闭菜单了。
(function(){ $(window).on('touchstart',$.noop); })();
假如你不需要修复缩放bug,那么只要加上这行代码就可以关闭菜单了。