基于媒体查询和 rem 的响应式布局实践PRO

916 阅读7分钟

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

本文首发于 www.lvdawei.com/post/respon…

效果展示

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

商城页展示效果

购物车页展示效果

@media 原理解析

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

/* 默认手机端样式 */
button {
  width: 100%;
  height: 30px;
}

/* PC端样式,覆盖掉手机端样式 */
@media (min-width: 576px) {
  button {
    width: 200px;
    height: 40px;
  }
}

上段样式代码的含义是 Button 的默认宽度为 100%,在浏览器窗口宽度 >= 576px 时,将 Button 宽度设为 200px,高度设为 40px。这种样式写法通常叫做手机优先,由于手机端浏览器只需应用前一段样式,对优化渲染速度,提高用户体验有很大作用。 其中的 576px 叫做断点(breakpoint),以响应式著称的 Bootstrap 框架推荐了几个常用的断点,如下:

/* 默认样式为竖屏手机 */
...;

/* 横置手机 */
@media (min-width: 576px) {
  ...;
}

/* 平板设备 */
@media (min-width: 768px) {
  ...;
}

/* 横置平板 */
@media (min-width: 992px) {
  ...;
}

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

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

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

/* 默认PC端样式 */
button {
  width: 200px;
  height: 40px;
}

/* 检测到宽度低于576px,应用上手机端样式 */
@media (max-width: 575.99px) {
  button {
    width: 100%;
    height: 30px;
  }
}

商城页主体区域实现原理

对应到刚刚的商城页,我们选择将主体区域分成左右两侧,并且在手机端将左侧目录栏隐藏掉,右侧商品区部分宽度设置为 100%。当屏幕尺寸变为横置平板,也就是 >=992px 时,显示左侧目录栏,并固定宽度为 200px,右侧商品区宽度设置为 calc(100% - 200px)

商城结构

<div class="container">
  <div class="left"></div>
  <div class="right"></div>
</div>
.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端结构

<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>
.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.gif 在 Codesandbox 打开 demo2🔗

加点细节

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

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

demo3.gif 在 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。

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,插件就会跳过这个属性。

{
    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 值即可实现上述需求。

<div class="container">
  <div class="left">Left</div>
  <div class="center">Center</div>
  <div class="right">Right</div>
</div>
/* 默认为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 文件:

// ./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。这样在对照样式的时候,打开双栏编辑窗口,可以提高一定的编码效率。

// home.less
...;
...;
...;

@import "home-resp.less";
// home-resp.less
@import "./mixins/media.less";

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

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

总结

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

最后,附上开头的响应式商城静态页的全部源码 github.com/daweilv/res…, 觉得不错可以点的 star 🌟 哦!