响应式布局实战

响应式布局(Responsive Layout)是指能够针对不同屏幕尺寸、分辨率做出合理布局和样式调整的一整套解决方案。经常会看到一些网站,在 PC 端放了一大堆功能,切换到移动端后整体样式大变样,功能也被阉割不全。又或者一个移动端的网站,在 PC 端点开,字体无比巨大,按钮遮住大部分页面,严重的甚至无法正常浏览。笔者之前写过一篇《基于媒体查询和 rem 的响应式布局实践》,现在看来有一些细节没有覆盖到,于是便有了本文。本文将通过几个典型的实战页面,将笔者在响应式布局上的一些经验分享给大家。

效果展示

先来直观感受下实现了响应式布局的两个页面,分别是商城页 🔗购物车页 🔗,大家可以点开链接直接体验一下。

商城页展示效果

购物车页展示效果

@media 原理解析

响应式布局使用了媒体查询 @media 为不同的尺寸设备应用不同的样式。贴一段样式代码来说明一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 默认手机端样式 */
button {
width: 100%;
height: 30px;
}

/* PC端样式,覆盖掉手机端样式 */
@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) {
...;
}

/* PC屏幕 */
@media (min-width: 1200px) {
...;
}

/* PC大屏幕 */
@media (min-width: 1400px) {
...;
}

当然,你也可以反过来写,先把 PC 端的样式写出来,不过这样写的话手机端需要覆盖一次样式,所以理论上性能会差一点。

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 默认PC端样式 */
button {
width: 200px;
height: 40px;
}

/* 检测到宽度低于576px,应用上手机端样式 */
@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);
}
}

demo1
在 codesandbox 打开 demo1🔗

顶部导航条实现原理

再来看下顶部导航条的实现,顶部分成左、中、右三部分,默认样式(即手机端)下,只显示三个 icon 和中间的文字,当屏幕尺寸变为横置平板,也就是 >=992px 时,只显示隐藏 Logo 和 右侧文字。

顶部导航条手机端结构
顶部导航条PC端结构

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;
}
}

demo2
在 Codesandbox 打开 demo2🔗

加点细节

一个合理的响应式页面应该在所有设备上保留主体业务功能,在我们刚刚的 demo1 和 demo2 中,demo1 因为在手机端隐藏了左侧目录区域,造成了功能的缺失,那么我们就在 demo3 中,配合 demo2,将目录的功能保留下来。

具体实现细节为,顶部导航条的最左侧的图标(三条横的那个图标)添加 onclick 事件,当被点击时,将主体区域的目录栏侧滑出来。目录栏不再设置为 display:none ,而是 固定位置,并且偏移到视窗之外 position: fixed; transform: translateX(-300px); 。这部分代码稍微多了点,就不贴在这里了,大家直接打开 codesandbox 查看。

demo3
在 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) {
// 只有在手机端的时候才进行等比缩放,PC端则无需处理
document.documentElement.style.fontSize = (clientWidth / 350) * 50 + "px";
} else {
document.documentElement.style.fontSize = "50px";
}
}

responsive();
// 在浏览器宽度改变时自动换算根元素的 font-size
window.onresize = function () {
responsive();
};

这里分享一个在工程实践上的技巧。如果大家脚手架用的是 webpack,引入 postcss-pxtorem 插件并配置 rootValue: 50,即可实现自动将 px 转换成 rem。如果有部分样式就是不想缩放,例如按钮的 border-width:1PX,可将单位变成大写 PXPx,插件就会跳过这个属性。

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,如图所示。

PC端与手机端的区别

如何实现呢?

这里引入 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
/* 默认为PC端样式 */
.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 排在 left 和 right 的前面 */
/* left 和 right 的 order 默认值为 0 */
.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
// ./mixins/media.less

// 竖版pad, 576px and up
@media-up-xs: ~"(min-width:576px)";

// 横版pad, 992px and up
@media-up-sm: ~"(min-width:992px)";

// PC, 1200px and up
@media-up-md: ~"(min-width:1200px)";

// 手机, only
@media-xs: ~"(max-width:575.98px)";

// 竖版pad, only
@media-sm: ~"(min-width:576px) and (max-width:1023.98px)";

// 横版pad, only
@media-md: ~"(min-width:1024px) and (max-width:1279.98px)";

// PC, 1280px and up
@media-lg: ~"(min-width:1280px)";

当我们需要写 Home 页的样式的时候,创建两个文件 home.lesshome-resp.less。在home-resp.less的头部引入 media.less,使用断点的时候就可以直接调用语义化更明确的变量。最后在 home.less 文件的末尾引入 home-resp.less。这样在对照样式的时候,打开双栏编辑窗口,可以提高一定的编码效率。

1
2
3
4
5
6
// home.less
...;
...;
...;

@import "home-resp.less";
1
2
3
4
5
6
7
8
9
10
// home-resp.less
@import "./mixins/media.less";

@media @media-up-xs {
...;
}

@media @media-up-sm {
...;
}

总结

以上就是笔者在响应式布局实战上用到的全部技巧了,更深入的实现细节,商城源码地址也附在下方了,希望大家可以学会并试着应用,一点点改变一下当前互联网的网页体验。

最后,附上开头的响应式商城静态页的全部源码 https://github.com/daweilv/responsive-layout-real-world-mall,觉得不错可以点的 star 🌟 哦!

Author

David Lv

Posted on

2021-06-15

Updated on

2021-06-20

Licensed under