响应式布局(Responsive Layout)是指能够针对不同屏幕尺寸、分辨率做出合理布局和样式调整的一整套解决方案。经常会看到一些网站,在 PC 端放了一大堆功能,切换到移动端后整体样式大变样,功能也被阉割不全。又或者一个移动端的网站,在 PC 端点开,字体无比巨大,按钮遮住大部分页面,严重的甚至无法正常浏览。笔者之前写过一篇《基于媒体查询和 rem 的响应式布局实践》 ,现在看来有一些细节没有覆盖到,于是便有了本文。本文将通过几个典型的实战页面,将笔者在响应式布局上的一些经验分享给大家。
效果展示 先来直观感受下实现了响应式布局的两个页面,分别是商城页 🔗 和购物车页 🔗 ,大家可以点开链接直接体验一下。
响应式布局使用了媒体查询 @media 为不同的尺寸设备应用不同的样式。贴一段样式代码来说明一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 button { width : 100% ; height : 30px ; } @media (min-width : 576px ) { button { width : 200px ; height : 40px ; } }
上段样式代码的含义是 Button 的默认宽度为 100%,在浏览器窗口宽度 >= 576px 时,将 Button 宽度设为 200px,高度设为 40px。这种样式写法通常叫做手机优先,由于手机端浏览器只需应用前一段样式,对优化渲染速度,提高用户体验有很大作用。 其中的 576px 叫做断点(breakpoint),以响应式著称的 Bootstrap 框架推荐了几个常用的断点,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ...; @media (min-width : 576px ) { ...; } @media (min-width : 768px ) { ...; } @media (min-width : 992px ) { ...; } @media (min-width : 1200px ) { ...; } @media (min-width : 1400px ) { ...; }
当然,你也可以反过来写,先把 PC 端的样式写出来,不过这样写的话手机端需要覆盖一次样式,所以理论上性能会差一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 button { width : 200px ; height : 40px ; } @media (max-width : 575.99px ) { button { width : 100% ; height : 30px ; } }
商城页主体区域实现原理 对应到刚刚的商城页,我们选择将主体区域分成左右两侧,并且在手机端将左侧目录栏隐藏掉,右侧商品区部分宽度设置为 100%。当屏幕尺寸变为横置平板,也就是 >=992px 时,显示左侧目录栏,并固定宽度为 200px,右侧商品区宽度设置为 calc(100% - 200px)
。
1 2 3 4 <div class ="container" > <div class ="left" > </div > <div class ="right" > </div > </div >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 .container { height : 200px ; font-size : 24px ; font-weight : bold; display : flex; color : #fff ; } .left { display : none; height : 100% ; background-color : #bae637 ; } .right { width : 100% ; height : 100% ; background-color : #40a9ff ; } @media (min-width : 992px ) { .left { display : block; width : 300px ; } .right { width : calc (100% - 300px ); } }
在 codesandbox 打开 demo1🔗
顶部导航条实现原理 再来看下顶部导航条的实现,顶部分成左、中、右三部分,默认样式(即手机端)下,只显示三个 icon 和中间的文字,当屏幕尺寸变为横置平板,也就是 >=992px 时,只显示隐藏 Logo 和 右侧文字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <div class ="container" > <div class ="header__left" > <img class ="toggle icons" src ="./src/toggle.png" /> <img class ="logo" src ="./src/logo.png" /> </div > <div class ="header__center" > HelloWorld</div > <div class ="header__right" > <a class ="cart__wrapper" href ="cart.html" > <span > CART</span > <img class ="cart icons" src ="./src/cart.png" /> </a > <a class ="mail__wrapper" href ="contact.html" > <span > CONTACT</span > <img class ="mail icons" src ="./src/mail.png" /> </a > </div > </div >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 .container { height : 44px ; display : flex; align-items : center; justify-content : space-between; color : #333 ; font-weight : bold; padding : 0 16px ; border-bottom : 1px solid rgba (165 , 165 , 165 , 0.3 ); } .logo { display : none; } .icons { width : 20px ; height : 20px ; } .cart__wrapper ,.mail__wrapper { text-decoration : none; color : #333 ; } .cart__wrapper { margin-right : 20px ; } .cart__wrapper span ,.mail__wrapper span { display : none; } @media (min-width : 992px ) { .cart__wrapper span , .mail__wrapper span { display : inline; } .icons { display : none; } .logo { display : block; width : 30px ; } .header__center { display : none; } }
在 Codesandbox 打开 demo2🔗
加点细节 一个合理的响应式页面应该在所有设备上保留主体业务功能,在我们刚刚的 demo1 和 demo2 中,demo1 因为在手机端隐藏了左侧目录区域,造成了功能的缺失,那么我们就在 demo3 中,配合 demo2,将目录的功能保留下来。
具体实现细节为,顶部导航条的最左侧的图标(三条横的那个图标)添加 onclick 事件,当被点击时,将主体区域的目录栏侧滑出来。目录栏不再设置为 display:none
,而是 固定位置,并且偏移到视窗之外 position: fixed; transform: translateX(-300px);
。这部分代码稍微多了点,就不贴在这里了,大家直接打开 codesandbox 查看。
在 Codesandbox 打开 demo3🔗
Rem 实现等比例缩放 通过上面 3 个 demo,笔者向大家展示了 @media
在适配不同设备的强大能力,但是我们还有一个缩放的问题没有解决。同样都是手机端,如何让大小尺寸不一的手机都能按比例的缩放呢?如果我们按固定像素去写,那么就可能出现 iPhone5 不够放,iPhone12ProMax 又显得太小的情况。
这里需要引入 rem 单位,rem 是一个根据 html 根元素 font-size 计算的相对单位。元素实际尺寸 = 元素 rem 值 x html 的 font-size 值。也就是说当 html 的 font-size 设置为 50px 时,2rem 的元素实际尺寸就是 100px。
举个例子,设计师给我们提供了以 iPhone7@2x 为原型的设计稿,设计稿的宽度为 750px,对应实际尺寸 350px。为了方便计算,我们将 html 的 font-size 设为 50px,那么设计稿 200px = 实际尺寸 100px = 2.00rem,rem 的数值正好是设计稿数值小数点左移 2 位。
接下来我们希望不管是 iPhone5 还是 iPhone12ProMax,都按照 iPhone7 等比例缩放,只需要根据设备宽度让根元素的 font-size 基于 iPhone7 的宽度等比例缩放即可。计算公式为:目标设备根元素的 font-size = 目标设备的宽度 / 350 * 50px。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 function responsive ( ) { const clientWidth = document .documentElement.clientWidth; if (clientWidth < 576 ) { document .documentElement.style.fontSize = (clientWidth / 350 ) * 50 + "px" ; } else { document .documentElement.style.fontSize = "50px" ; } } responsive(); window .onresize = function ( ) { responsive(); };
这里分享一个在工程实践上的技巧。如果大家脚手架用的是 webpack,引入 postcss-pxtorem
插件并配置 rootValue: 50
,即可实现自动将 px 转换成 rem。如果有部分样式就是不想缩放,例如按钮的 border-width:1PX
,可将单位变成大写 PX
或 Px
,插件就会跳过这个属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 { loader : 'postcss-loader' , options : { plugins : [ require ('postcss-import' ), require ('autoprefixer' ), require ('postcss-pxtorem' )({ rootValue : 50 , propList : ['*' ], }), ], }, }
Flex Order 属性的应用 在设计视觉上,PC 端的排布往往是从左到右,再从上到下的,而手机端受限于屏幕宽度,大区域的排布是从上到下的。这就导致了视觉核心区在 PC 端是在屏幕正中间,而在手机端是在最上面。PC 端 left/center/right
的布局,在手机端就需要 top/middle/bottom
,并且 PC 端的 center
要放置在手机端的 top
,如图所示。
如何实现呢?
这里引入 flex 布局中的 order 属性,order 属性规定了 flex 容器中的子元素在布局时的顺序。元素按照 order 属性的值的增序进行布局。拥有相同 order 属性值的元素按照它们在源代码中出现的顺序进行布局。order 属性的默认值为 0,可为负数。
配合媒体查询 @media,在不同的区间设置不同的 order 值即可实现上述需求。
1 2 3 4 5 <div class ="container" > <div class ="left" > Left</div > <div class ="center" > Center</div > <div class ="right" > Right</div > </div >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 .container { height : 200px ; font-size : 24px ; font-weight : bold; display : flex; color : #fff ; } .left { flex : 1 ; background-color : #73d13d ; } .center { flex : 1 ; background-color : #ff4d4f ; } .right { flex : 1 ; background-color : #40a9ff ; } @media (max-width : 576px ) { .container { flex-direction : column; } .center { order : -1 ; } }
在 Codesandbox 打开 demo4🔗
工程实践小技巧 我们在写项目的时候为了方便管理,往往会分很多个 less 文件,如果在写每一个 less 文件的时候都要记着 576、992、min-width、max-width 会很麻烦。这里参考 bootstrap 的项目实践,分享一个小技巧。
首先我们单独维护一个名为 media.less
的 mixins 文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @media -up-xs : ~"(min-width:576px)" ;@media -up-sm : ~"(min-width:992px)" ;@media -up-md : ~"(min-width:1200px)" ;@media -xs : ~"(max-width:575.98px)" ;@media -sm : ~"(min-width:576px) and (max-width:1023.98px)" ;@media -md : ~"(min-width:1024px) and (max-width:1279.98px)" ;@media -lg : ~"(min-width:1280px)" ;
当我们需要写 Home 页的样式的时候,创建两个文件 home.less
和 home-resp.less
。在home-resp.less
的头部引入 media.less
,使用断点的时候就可以直接调用语义化更明确的变量。最后在 home.less
文件的末尾引入 home-resp.less
。这样在对照样式的时候,打开双栏编辑窗口,可以提高一定的编码效率。
1 2 3 4 5 6 ...; ...; ...; @import "home-resp.less" ;
1 2 3 4 5 6 7 8 9 10 @import "./mixins/media.less" ;@media @media-up-xs { ...; } @media @media-up-sm { ...; }
总结 以上就是笔者在响应式布局实战上用到的全部技巧了,更深入的实现细节,商城源码地址也附在下方了,希望大家可以学会并试着应用,一点点改变一下当前互联网的网页体验。
最后,附上开头的响应式商城静态页的全部源码 https://github.com/daweilv/responsive-layout-real-world-mall ,觉得不错可以点的 star 🌟 哦!