原文连接javascript
自从进入移动互联网时代,响应式布局这个词常常出如今 Web 设计和开发领域,它让 Web 页面在不一样尺寸的设备上都具备良好的浏览体验。css
在讲解响应式布局以前,须要先了解一下基础知识,只有对它们都有必定的了解,才能在作响应式布局时选取合适的技术方案。html
像素这个单位很常见,指的是图像中最小的单位,一个不可再分割的点,在计算机屏幕上通常指屏幕上的一个光点。例如常见的描述中 iPhone X 的分辨率是 1125x2436,通常指的是在长和宽上像素点的个数。可是在 Web 开发中,咱们知道 iPhone X 的像素是 375x812,那么这又是怎么回事呢?这里须要讲到设备像素(Device Pixels)和虚拟像素,也能够叫 CSS 像素(CSS Pixels)或者逻辑像素,后面咱们统一使用 CSS 像素这个称呼,在 Android 开发中能够叫设备无关像素(Device Independent Pixel,简写 dip)。设备像素很好理解,对应屏幕上光点的数量。java
在科技发展到今天,屏幕分辨率已经达到人眼没法区分单个像素的程度,人眼没法在 iPhone X 宽不到 7cm 的屏幕上数出 1125 个像素点。Web 开发人员眼中的 1px 可能对应多个设备像素,Peter-Paul Koch 在他的博文中有详细的讲解《A pixel is not a pixel is not a pixel》。git
好比在 iPhone X 上,设备像素是 1125x2436,而 CSS 像素是 375x812,那么一个 CSS 像素对应的是长和宽各 3 个设备像素,9个设备像素点。github
1 css pixel = 3 x 3 device pixels
那个这个比值 3 就是咱们平时所说的设备像素比(Device Pixel Ratio),简称为 DPR。DPR 它并非一个单位,而是一个比值,这个比值能够在浏览器中经过 JavaScript 代码获取。web
// 设备像素比,在 iPhone X 中等于 3,在 iPhone 6 中等于 2 window.devicePixelRatio
EM 是相对单位,相对于元素自身的 font-size
,它不像像素是固定的单位,所以很适合用来作响应式布局。npm
<style> h1 { font-size: 20px; margin: 1em; /* 1em = 20px */ } p { font-size: 14px; padding: 1em; /* 1em = 14px */ } .outer { font-size: 12px; } .inner { font-size: 2em; padding: 1em; /* 1em = 24px*/ } </style> <div class="outer"> <div class="inner"></div> </div>
若是当前元素没有设置 font-size
,那么 1em 实际大小是多少?canvas
p { padding: 1em; /* 1em 等于多少像素*/ }
在上面的代码中没有设置 <p>
的 font-size
,它会从继承父元素的字体大小,若是父元素也没有设置字体大小,会一直找到根元素 <html>
,而 <html>
元素的默认 font-size
通常是 16px。有的元素有默认的字体大小,好比 <h1>
的 font-size
默认等于 2em,最终计算仍是会追溯到最外层。浏览器
<html> <head></head> <body> <p>1em = 16px</p> </body> </html>
REM = Root EM,顾名思义就是相对于根元素的 EM,是根据根元素来计算出CSS 像素点的大小。根元素就是 <html>
,而它的默认字体大小是 16px。
h1 { font-size: 20px; margin: 1rem; /* 1rem = 16px */ } p { font-size: 1rem; /* 1rem = 16px */ }
因此,若是咱们改变根元素的字体大小,页面上全部使用 rem 的元素都会被从新计算元素属性并重绘。
EM 和 REM 都是相对单位,二者均可以用来作响应式布局的单位。根据它们的特性,EM 和 REM 互有优劣。
<html>
的字体大小便可计算当前的实际像素大小。有的开发者所有都用 REM,有些开发者所有用 EM,这其实都是不合理的用法。开发者应该视状况不一样采用不一样的单位,但在如今的环境下,REM 使用的更普遍一些。
开发者根据设计师提供的 UE 图进行开发时,测量出来的大小单位通常是像素,若是须要转换为 REM,能够采用 PostCSS 的插件 postcss-px2rem
自动转换为 rem 单位。
vw
, vh
,百分比vw
和 vh
vw 和 vh 如今还不常见,但也逐渐开始被开发者使用,特别是在布局上。
vw 和 vh 的逻辑比较简单,100vw = 100% 视口宽度,视口(viewport)会在后面详细讲解。下面的代码演示如何在 iPhone X 上计算 vw 的实际 CSS 像素大小,vh 的计算方法和 vw 同样。
<html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> p { width: 50vw; /* 1vw = 1 / 100 * 375px = 3.75px */ } </style> </head> <body> <p>50vw = 50% viewport width = 50% * 375px = 187.5px</p> </body> </html>
浏览器对 vw 和 vh 支持相对较晚,目前在 Android 4.4 如下的 Android Browser 上还不支持,可是国内主流应用的 WebView 内核都是本身定制的,内核版本都高于系统自带的,所以在国内 vw 和 vh 的支持度比 Can I Use 统计的要高不少,并且随着版本的推移,vw 和 vh 会更流行。
vw
, vh
vs 百分比如今咱们知道了,1vw = 1% 视口宽度,那么它们是否是等价呢?咱们先来看一下下面的代码,一样仍是以 iPhone X 为例。
<html> <head> <meta name="viewport" content="width=device-width, initial-scale=1"> <style> .p1 { width: 50vw; /* 1vw = 1 / 100 * 375px = 3.75px */ } .outer { width: 80vw; } .p2 { width: 50%; } </style> </head> <body> <p class="p1">50vw = 187.5px</p> <section class="outer"> <p class="p2">50% = 150px</p> </section> </body> </html>
将上面的代码在浏览器中运行,发现第二个 <p>
标签的实际宽度为 150px,不是 187.5px。其实原理很是简单,就和 EM 和 REM 同样,百分比相对于父元素的宽度来计算,而 vw
根据视口的宽度来计算。
因此再回顾上面的例子中的第二个 <p>
标签,.outer
元素的宽度为 80vw = 300px,那么其子 <p>
标签的宽度为 300px * 50% = 150px。
除 vw 和 vh 外,还有 vmin 和 vmax 元素,这里就不继续展开,感兴趣的开发者能够访问 《Fun with Viewport Units》了解更多,这篇文章中对 vw 和 vh 讲的很详细,还有很多示例。
Web 开发中还有不少其余的单位,如 in(英尺),mm(毫米),cm(厘米)等,但由于使用的很少,开发者仅做了解就能够。
上面咱们讲到了百分比和 vw/vh,它们均可以用来实现响应式的布局,可是不如咱们接下来要讲的弹性框灵活,它不是单位,而是一种布局方式。
区别于传统的布局方式,如标准文档流、浮动布局和定位布局,弹性框(flexbox)布局更加灵活,弹性框中的元素能够弹性伸缩,能够定义排版方向,还能够指定 flex 元素的顺序。下面是一个简单的例子。
<style> .container { display: flex; /* 设置容器为弹性布局 */ } .box { width: 100px; height: 30vh; } .b1 { background: #009; } .b2 { background: #06c; } .b3 { background: #39f; } .b4 { background: #6cf; width: 50px; } </style> <div class="container"> <div class="box b1"></div> <div class="box b2"></div> <div class="box b3"></div> <div class="box b4"></div> </div>
上面的例子在浏览器中的表现以下图所示,咱们能看到,每一个 <div>
元素都是横排,这是由于弹性布局默认排列为横向排列,咱们能够经过 flex-direction
属性决定排列方向,同时在小于 350px 宽的浏览器里,会按比例自动缩小每一个 <div>
的宽度。
从上面的例子中,能发现,有两个重要的角色须要开发者关注,一个是容器,一个是其子元素。
容器指的是 display: flex
的元素,它能够定义其余的属性,决定子元素的排列,以下。
flex-direction
- 定义主轴方向,即子元素的排列方向,取值为 row
, row-reverse
, column
和 column-reverse
,默认为 row
,即水平从左到右flex-wrap
- 默认状况下,弹性布局会将全部元素都压缩到一行,能够经过设置 flex-wrap
告诉浏览器在适当时候换行,取值为 nowrap
, wrap
和 wrap-reverse
,默认为 nowrap
flex-flow
- 这个属性值是 flex-direction
和 flex-wrap
的简写,如 flex-flow: row nowrap
,等价于 flex-direction: row; flex-wrap: nowrap
justify-content
- 定义子元素在主轴上对齐方式,取值为 flex-start
, flex-end
, center
, space-between
, space-around
,默认为 flex-start
align-items
- 定义子元素在垂直于主轴的交叉轴的排列方式,取值为 stretch
, flex-start
, flex-end
, center
, baseline
,默认为 stretch
,即若是没设置高度,将填满交叉轴方向align-content
- 定义了子元素在多条轴线上的对齐方式,若是只使用了一条轴线,那该属性不起做用,取值为 flex-start
, flex-end
, center
, space-between
, space-between
, space-around
和 stretch
,默认为 stretch
在弹性布局以前,开发者若是要实现子元素水平和垂直居中会比较麻烦,在弹性布局中,很是容易实现,只须要在容器上设置轴线对齐方式,以下代码所示。
.container { display: flex; /* 设置容器为弹性布局 */ justify-content: center; /* 设置在主轴上居中对齐 */ align-items: center; /* 设置在交叉轴上居中对齐 */ }
一样,子元素也有不少新增的样式属性,以下:
order
- 设置子元素在主轴方向上的顺序,取值为数字,从小到大排列,默认为 0flex-grow
- 定义子元素的放大比例,取值为数字,默认为 0flex-shrink
- 定义子元素的缩小比例,取值为数字,默认为 1flex-basis
- 定义在分配多余空间以前,子元素的默认大小,默认为 auto
flex
- 是 flex-grow
, flex-shrink
和 flex-basis
的简写,默认值为 0 1 auto
align-self
- 覆盖父元素的 align-items
属性,可让子元素自身采用不一样的对齐方式,默认为 auto
,继承父元素的 align-items
弹性布局很是灵活,属性值也足够应对大部分复杂的场景。能够阅读这篇文章查看详细的介绍《A Complete Guide to Flexbox》。
那么,开始以前须要了解的内容就到这了,接下来看如何实现响应式布局。
支持响应式第一步,须要作的是设置页面的 viewport
。移动端网页会在头部书写 viewport 的元标签,它告诉浏览器页面多大尺寸,是否须要缩放。
<meta name="viewport" content="width=device-width, initial-scale=1">
想要理解 viewport 能够阅读 Peter-Paul Koch 写的三篇文章,《A tale of two viewports — part one》,《A tale of two viewports — part two》,《Meta viewport》。
在早期,移动设备常常须要打开 PC 端的网页,早期的移动设备设备像素比较低,多为 320px,而 PC 端的网页宽度通常都很大,因此,若是将 PC 端的网页在移动设备上打开,会由于页面太窄而致使布局错乱。为了解决这个问题,浏览器会将页面默认 viewport 设置为一个较大的值(Safari 默认是 980px),因此 PC 端的网页在移动设备浏览器上都能正常打开,只是元素看上去比较小。
在上面的例子中,viewport 的值 width=device-width
,告诉浏览器用 屏幕宽度(单位为 CSS 像素)来做为页面宽度渲染,在 iPhone X 下是 375px,不一样的设备宽度可能不同。这个视口被 Peter-Paul Koch 称为理想视口(ideal viewport),也是体验最好的视口大小。
viewport 元标签的取值有 6 种,以下表所示
字段名 | 取值 | 说明 |
---|---|---|
width |
正整数,device-width | 定义视口的宽度,单位是 CSS 像素,若是等于 device-width,则为理想视口宽度 |
height |
正整数,device-height | 定义视口的高度,单位是 CSS 像素,若是等于 device-height,则为理想视口高度 |
initial-scale |
0 - 10 | 初始缩放比例,容许小数点 |
minimum-scale |
0 - 10 | 最小缩放比例,必须小于等于 maximum-scale |
maximum-scale |
0 - 10 | 最大缩放比例,必须大于等于 minimum-scale |
user-scalable |
yes/no | 是否容许用户缩放页面,默认是 yes |
设置了 viewport 为理想视口,若是在 iPhone X 上,有元素的宽度超出了 375px,那么就会溢出到视口外面,致使出现横向滚动条。不管是在 PC 端,仍是移动端,用户的都习惯上下滚动,而不是左右滚动,强迫用户横向滚动或者缩小页面来浏览所有的内容,体验很很差。
所以,不能指望设置 viewport 宽度能解决适配问题,还须要开发者记住如下原则。
对于图片或者视频等嵌入式的元素,能够在站点 CSS 中添加下面的代码。
img, embed, object, video { max-width: 100%; /* 设置 img 等元素最大宽度威 100% */ }
媒体查询(media query)让开发者能够有选择性的应用不一样 CSS,媒体查询提供了简单的判断方法,能够根据不一样的设备特征应用不一样样式,好比设备的宽度、类型、方向等,能够参考 MDN 上的文档《CSS 媒体查询》。
<!-- 在 viewport 宽度大于 600px 时,应用 example.css 中的样式 --> <link ref="stylesheet" href="example.css" media="min-width: 600px"> <style> /* 若是设备类型为屏幕而且 viewport 小于 800px 宽,设置 body 背景颜色为灰色 */ @media screen and (max-width: 800px) { body { background: #ccc; } } </style>
若是须要使用媒体查询应用的样式比较多,能够独立为一个文件,经过在 <link>
标签中设置媒体查询条件。
媒体查询支持不少设备特征,经常使用的主要是 viewport 的宽高和设备方向,以下表所示。
设备特征 | 取值 | 说明 |
---|---|---|
min-width |
数值,如 600px | 视口宽度大于 min-width 时应用样式 |
max-width |
数值,如 800px | 视口宽度小于 max-width 时应用样式 |
orientation |
portrait|landscape | 当前设备方向,portrait 垂直,landscape 水平 |
如何选择 min-width
和 max-width
的取值,咱们称为选择断点,主要取决于产品设计自己,没有万能媒体查询的代码。但通过实践,咱们也总结了一套比较具备表明性的设备断点,代码以下。
/* 很小的设备(手机等,小于 600px) */ @media only screen and (max-width: 600px) { } /* 比较小的设备(竖屏的平板,屏幕较大的手机等, 大于 600px) */ @media only screen and (min-width: 600px) { } /* 中型大小设备(横屏的平板, 大于 768px) */ @media only screen and (min-width: 768px) { } /* 大型设备(电脑, 大于 992px) */ @media only screen and (min-width: 992px) { } /* 超大型设备(大尺寸电脑屏幕, 大于 1200px) */ @media only screen and (min-width: 1200px) { }
若是要对细分屏幕大小进行适配,能够查看文章,列出了详细的常见设备的媒体查询条件,《media queries for common device breakpoints》。
大多数用户阅读都是从左到右,若是一行文字太长,用户阅读下一行时容易出错,或者用户只会读一行文字的前半部分,而略读后半部分。在上世纪就有研究代表,一行 45 ~ 90 个英文字符是最好的,固然这要看是什么字体,一个中文汉字通常对应两个英文字符,因此,对于中国用户来讲,一行文字合理的数量应该是 22 ~ 45 个字符。
字体大小对阅读体验一样重要,基本字体通常不小于 16px,行高大于 1.2em。
p { font-size: 16px; line-height: 1.2em; /* 1.2em = 19.2px */ }
而设备的尺寸多种多样,若是设计师但愿在平板上将字体设置为 18px,开发者可使用前面讲到的 REM 和媒体查询,代码以下。
/* 在屏幕宽度大于 600px 的设备上使用下面的样式 */ @media only screen and (min-width: 600px) { p { font-size: 1.125rem; /* 1.125rem = 16px * 1.125 = 18px */ } }
一图胜千言,图片占网页流量消耗的 60%,可见其在 Web 的重要性。在上文提到图片不要超出视口的宽度,给图片设置 max-width: 100%
,这确实很是有做用,那还有没有其余须要咱们注意的呢。
现代设备的 DPR (设备像素比)都很高,iPhone X 的 DPR 是 3,所以若是咱们用 375px 宽的图片在 iPhone X 上显示,实际只能利用它三分之一的设备像素点,会让图片看起来很模糊,视觉体验较差。若是咱们都用 3 倍分辨率的图片来显示,实际屏幕较小的设备没法彻底显示如此高清晰度的图片,就会在显示时进行压缩,这对于实际屏幕比较小的设备来讲会浪费较多带宽。
为此,图片质量也须要能响应式。
<!-- 响应式图片 --> <img srcset="example-320w.jpg 320w, example-480w.jpg 480w, example-800w.jpg 800w" sizes="(max-width: 320px) 280px, (max-width: 480px) 440px, 800px" src="example-800w.jpg" alt="An example image">
这里 sizes
和 srcset
不少开发者比较陌生。在兼容性很差的浏览器里,会继续使用默认 src
属性中的图片。
srcset
定义了几组图片和对应的尺寸,格式比较简单,主要的两个部分是图片地址和图片固有宽度,单位为像素,可是这里使用 w
代替 px
。
sizes
定义了一组媒体查询条件,而且指名了若是知足媒体查询条件以后,使用特定尺寸的图片。
若是开发者书写了上面代码中的图片,浏览器会根据下面的顺序加载图片。
srcset
中最接近这个尺寸的图片并显示因此,若是咱们在视口宽度为 375px 的设备上,会采用最接近 440px 的图片,example-480w.jpg
。
若是对 srcset
和 sizes
还想了解更多,能够访问 MDN 的文档《响应式图片》。
咱们提到将图片的 max-width
设置为 100%,图片就会在手机屏幕上压缩到视口的宽度,若是这张图片实际上很大,图片中的内容就会看不清,特别是若是图片主要内容集中在中间,如人像,浏览效果会比较差。遇到这样的状况,最好的方式是在不一样的屏幕尺寸下采用不一样的图片,让主要内容保持在视口中间,以下图。
HTML 标准中有一个标签 <picture>
,容许咱们在其中设置多个图片来源,就和 <video>
,<audio>
标签同样。
<picture> <source media="(max-width: 799px)" srcset="example-480w-portrait.jpg"> <source media="(min-width: 800px)" srcset="example-800w.jpg"> <img src="example-800w.jpg" alt="An example img"> </picture>
<picture>
标签的做用和上面在 <img>
中设置 sizes
和 srcset
同样,都能在不一样的设备宽度下显示不一样的图片,笔者更建议使用 <picture>
实现此效果。
响应式布局在 PWA 中是很是重要的概念,在实现响应式的同时,还须要关注响应式图片是否会带来性能问题。在开发过程当中,还须要注意下面几个问题。
利用相对单位、flexbox、媒体查询等技术,开发者能应付各类类型的页面和布局,为了方便开发者可以快速上手,下面介绍 5 种常见的响应式布局模式。这些模式最初由 Luke Wroblewski 总结并提出。
为了更好的理解这些响应式布局,笔者准备了 5 个例子。
# 从 GitHub 下载代码到本地 pwa-book-demo 目录 $ git clone https://github.com/lavas-project/pwa-book-demo.git # 进入到 chapter02/responsive-web-design 目录 $ cd chapter02/responsive-web-design # 安装 npm 依赖 $ npm install -g edp # 启动 chapter02 responsive-web-design 示例 $ edp ws start
启动完成以后,访问 http://localhost:8848/
能看到 5 个不一样的目录,如 mostly-fluid,点击目录里的 index.html
就能看到不一样模式的效果,尝试拖动改变浏览器的大小吧。
大致流动布局的主要特色是在大屏幕上,内容区域宽度是固定的,所以在多数设备上,主要布局结构并无很大改变,若是屏幕宽度大于内容区域,就在内容左右留白。而在视窗宽度较窄时,会逐渐掉落呈堆放,以下图所示。
大致流动布局比较简单,每每只须要少许的媒体查询就能够实现,以下代码所示。
<style> .box { width: 100%; height: 150px; } /* 设置各个区块的颜色 */ .b1 { background: #009; } .b2 { background: #06c; } .b3 { background: #39f; } .b4 { background: #6cf; } .b5 { background: #cff; } /* 在大于 450px 时,将 .b2, .b3 宽度设置为 50% */ @media screen and (min-width: 450px) { .b2, .b3 { width: 50%; } } /* 在大于 800px 时,将 .b1, .b2 宽度设置为 50%,让 .b3, .b4, .b5 平分一行 */ @media screen and (min-width: 800px) { .b1, .b2 { width: 50%; } .b3, .b4, .b5 { width: 33.333333%; } } /* 定义最大宽度为 980px */ @media screen and (min-width: 980px) { .container { max-width: 980px; margin: auto; } } </style> ... <div class="container"> <div class="box b1"></div> <div class="box b2"></div> <div class="box b3"></div> <div class="box b4"></div> <div class="box b5"></div> </div>
列掉落,布局中的每列随着宽度变小而逐个掉落,在视口最小的时候,每一个元素都纵向堆放。和大致流动布局不一样点在于,列掉落布局不会设置最大宽度,若是视口足够大,列掉落布局也会填满整个页面。媒体查询的断点选择须要根据网页自己的内容来选择。
列掉落比大致流动布局要简单,代码以下。
<style> /* 在大于 450px 时,将 .b1 和 .b2 放置在同一行,分别占据 30% 和 70% */ @media screen and (min-width: 450px) { .b1 { width: 30%; } .b2 { width: 70%; } } /* 在大于 800px 时,.b1, .b3 各占 20% 区域,.b2 在中间占 60% */ @media screen and (min-width: 800px) { .b1, .b3 { width: 20%; } .b2 { width: 60%; } } </style> ... <div class="container"> <div class="box b1"></div> <div class="box b2"></div> <div class="box b3"></div> <div class="box b4"></div> <div class="box b5"></div> </div>
布局移位是最灵活的布局方式,它不只仅将元素按照从前到后,从上到下排列,有时还会改变元素的位置。
代码以下:
<style> /* 在大于 800px 时,将 .b1 和 .b4 放置在同一行,分别占据 20% 和 80% */ @media screen and (min-width: 800px) { .b1 { width: 20%; } .b4 { width: 80%; } } </style> ... <div class="container"> <div class="box b1"></div> <div class="box b4"> <div class="box b2"></div> <div class="box b3"></div> </div> </div>
微调,意思就是在视口发生变化时,对内容进行一些小的调整,好比调整字体、图片大小或者元素间距等。
<style> .b1 { background: #39f; text-align: center; padding-top: 10%; font-size: 1em; } @media screen and (min-width: 450px) { .b1 { font-size: 2em; } } @media screen and (min-width: 800px) { .b1 { font-size: 4em; } } </style> ... <div class="container"> <div class="box b1">字体大小</div> </div>
在画布溢出布局中,内容不是从上到下的,而是将不经常使用的内容,好比应用菜单和导航栏,折叠起来,留下一个打开的入口,只有当屏幕足够大的时候才显示。画布溢出布局很常见,不只在 Web App 中,在 Native App 中使用更多。这样的布局通常都须要配合 JavaScript 使用。
在示例中,咱们经过 transform: translate(-275px, 0)
将左侧侧边栏隐藏在视口外,点击菜单后,菜单栏会从左侧划出。在页面视口大于 800 时,将布局改成 flexbox
弹性布局,直接显示左侧菜单栏,代码以下。
<style> nav { width: 275px; height: 100%; position: absolute; /* 将菜单栏隐藏起来 */ transform: translate(-275px, 0); transition: transform 0.3s ease-in-out; background: #39f; z-index: 2; } nav.open { transform: translate(0, 0); } /* 在视口大于 800px 时,将菜单栏直接显示出来 */ @media screen and (min-width: 800px) { nav { position: relative; transform: translate(0, 0); } body { display: flex; flex-flow: wrap; } main { width: auto; flex: 1; } } </style> <nav></nav> <main> <a id="menu">...</a> </main> <div id="mask"></div> <script> let mask = document.querySelector('#mask') let nav = document.querySelector('nav') let menu = document.querySelector('a') // 点击菜单,显示或隐藏菜单栏 menu.addEventListener('click', event => { nav.classList.toggle('open') mask.classList.toggle('open') }) // 点击遮罩,隐藏菜单栏 mask.addEventListener('click', event => { nav.classList.remove('open') mask.classList.remove('open') }) </script>
以上就是比较常见的 5 种响应式布局模式,大多数状况下都须要多种模式同时使用。
响应式布局从设计角度出发,借助视口设置、CSS 媒体查询等方法,使开发者能够更易于维护适用于不一样尺寸屏幕的网页。在本节中,咱们介绍了一些文字、图片以及布局方面常见的响应式设计最佳实践,开发者应用这些经验,能够更好的优化 PWA 在不一样尺寸大小设备的用户体验。