15.响应式布局和 Bootstrap 实现

2021-06-08  本文已影响0人  ikonan

响应式这个概念曾经非常流行,但从发展来看,似乎「响应式」布局不再是一个必不可少的话题。究其原因我认为主要有以下几点:

但是我们仍然不能对这个概念「掉以轻心」,因为响应式布局仍然有其存在的价值:移动端碎片化的现象将会无限期存在;前端也必然进入物联网,任何设备界面的响应布局将会成为关键挑战。除此之外,响应式布局也体现了 CSS 的灵活和发展。

本讲我们就来深入这个话题,相关知识点如下:


图片

上帝视角——响应式布局适配方案

我们首先来梳理一下响应式布局的几种典型方案:

其中「传统 float 浮动布局」已经在「第 3-1 课:前端面试离不开的「面子工程」」中有所体现(多栏自适应),这种实现方式比较传统,且能力较弱。

相对单位布局比较容易理解,梳理 CSS 中的相对单位有:

重点是理解这些相对单位的使用规范,「到底是相对于谁」(注意,这也是一个很重要的面试考点),比如:

这两个单位在响应式布局中非常重要,我们后续在真实线上适配案例中就能发现,以 rem 为核心,诞生了淘宝的 flexible 响应式布局的方案。

width: calc(100vw - 80px)

除了相对单位以外,媒体查询(Media Query)以及 flex、grid 布局也都比较好理解。相关内容都容易找到,这里插播一下借助 JavaScript 实现响应式布局的案例,结合上一讲「进击的 HTML 和 CSS」中的 CSS 变量,往往也能简化很多问题:

p {
   height: var(--test-height);
}

function changePHeight (height)
   document.documentElement.style.setProperty('--test-height', `${height}px);
}

其实总结下来,这也是一道非常常见的面试题:「你如何实现自适应?如何做到响应式?」,想必大家已经有所了解了。

事实上,所有的响应式布局手段都不是单一的,上述方法搭配使用,效果更明显,也更加简单可行。

下面我们通过分析线上案例(淘宝 + 网易),来了解真实环境下的解决方案

真实线上适配案例分析

在进入分析前,我们先罗列一下其他关于响应式布局的概念:

不同设备的物理像素尺寸等信息可以参考:Device Metrics
这些内容都可以在社区上了解到,这里重点分析移动端页面的处理方案。

首先,淘宝通过设置:


禁用了用户缩放功能,使页面宽度和设备宽度对齐,一般这种操作也是移动端的响应式适配的标配。

我们观察在页面根节点 HTML 元素上,显式设置了 font-size:


并且进行试验,当改变浏览器大小时,html 的 font-size 会动态变化。这样不难理解, 采用 rem 作为相对单位的长宽数值,都会随着 resize 事件进行变化(因为 html 的 font-size 动态变化)。我们在其页面当中,不难找到这样的代码:

我将其复制并美化出来,得到:

!function(e, t) {
   var n = t.documentElement,
       d = e.devicePixelRatio || 1;

   function i() {
       var e = n.clientWidth / 3.75;
       n.style.fontSize = e + "px"
   }
   if (function e() {
       t.body ? t.body.style.fontSize = "16px" : t.addEventListener("DOMContentLoaded", e)
   }(), i(), e.addEventListener("resize", i), e.addEventListener("pageshow", function(e) {
       e.persisted && i()
   }), 2 <= d) {
       var o = t.createElement("body"),
           a = t.createElement("div");
       a.style.border = ".5px solid transparent", o.appendChild(a), n.appendChild(o), 1 === a.offsetHeight && n.classList.add("hairlines"), n.removeChild(o)
   }
}(window, document)

核心逻辑不难理解,这是一个 IIFE,在 DOMContentLoaded、resize、pageshow 事件触发时,进行对 html 的 font-size 值设定,计算方式:

font-size = document.documentElement.clientWidth / 3.75

为什么这么计算呢?我可以肯定的是:淘宝的工程师是按照设计 375px 的视觉稿完成的。在 375px 视觉稿下,html 的 font-size 为 100,那么如果宽度是 75px 的元素,就可以设置为 0.75rem(100 0.75 = 75px);当设备宽度为 414px(iPhone8 plus)时,我们想让上述元素的宽度等比例自适应到 82.8px(75 414 / 375),那么在 CSS 样式为 0.74rem 不变的前提下,想计算得到 82.8px,只需 HTML font-size 变为:110.4px 即可(110.4 * 0.75 = 82.8)。那么反向过来,这个 110.4 的计算公式就是:

document.documentElement.clientWidth / 3.75

当然淘宝实现响应式布局除了依靠 rem 以外,还大量运用了 flex 布局,比如页面中最复杂的布局区块:



实现较为简单。

整套解决方案淘宝开源出来,叫做 flexible 布局。其实读到这里,你已经理解了这个解决方案的核心原理。

我们再来看看网易的做法,大体类似:


同样采用了 rem 布局,但区别是网易并没有 JavaScript 介入计算 html 的 font-size,而是通过媒体查询和 calc 手段,「枚举」了不同设备下不同的 HTML font-size 值。

在其页面中,较为复杂的头部 slider 组件中:



slider 宽度明显是 JavaScript 获取设备宽度后动态赋值的(图中为 414px),而高度采用了 rem 布局: 3.7 rem = 55.3px(calc(13.33333333vw) * 3.7)

总结一下,响应式布局并没有那么困难,我们需要掌握最基本的处理手段,在实际场景中综合运用多种套路即可实现最大限度的灵活。

Bootstrap 栅格实现思路

Bootrap 栅格化是一个非常「伟大」的实现,我们在使用 Bootrap 布局时,可以通过添加类的方法,轻松实现栅格化,流式布局。

我们选取代表性的 BS4 官网范例,可以在线参考,或者参看以下截图,在宽屏幕下,我们看到:

当屏幕宽度小于 576px 时候,我们有:


对应代码:

<div class="col-6 col-sm-3">...</div>
<div class="col-6 col-sm-3">...</div>
<div class="col-6 col-sm-3">...</div>

.col-6 class 样式在源码里面可以简单归纳(不完全)为:

.col-6 {
   -webkit-box-flex: 0;
   -webkit-flex: 0 0 50%;
   -ms-flex: 0 0 50%;
   flex: 0 0 50%;
   max-width: 50%;
}

.col-sm-3 class 在源码里面可以归纳为:

.col-sm-3 {
   -webkit-box-flex: 0;
   -webkit-flex: 0 0 25%;
       -ms-flex: 0 0 25%;
           flex: 0 0 25%;
      max-width: 25%;
}

我们看到,代码里设置了两个 class:col-6 col-sm-3 进行样式声明。

从上面样式代码里看到类似 flex: 0 0 25% 的声明,为了理解它,我们从 flex 属性入手:flex 属性是 flex-grow、flex-shrink 和 flex-basis 的简写(类似 backgroud 是很多背景属性的简写一样),它的默认值为 0 1 auto,后两个属性可选。语法格式如下:

.item {
   flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

浏览器根据这个属性,计算主轴是否有多余空间,它可以设为跟 width 或 height 属性一样的值(比如 350px),则项目将占据固定空间

Bootstrap 这里对 flex 设置为比例值,这也是响应式自然而然实现的基础。

但是我们想,很明显 col-6 col-sm-3 的样式属性是有冲突的,那么他们是如何做到「和平共处」交替发挥作用的呢?

事实上:

在屏幕宽度大于 576px 时候,会发现 .col-sm-3 并没有起作用,这时候起作用的是 .col-6。
我们在源码里发现 .col-sm-* 的样式声明全部在

 @media (min-width: 576px) {...}

的媒体查询中,这就保证了在 576px 宽度以上的屏幕,只有在媒体查询之外的 .col-* 样式声明发挥了作用。

在屏幕宽度小于 576px 时候,命中媒体查询,命中 .col-sm-3 的样式声明。它的优先级一定大于 .col-6(媒体查询优先级高),这时候就保证了移动端的样式「占上风」。

再结合 col-6 col-sm-3 的样式声明,我们可以简单总结一下:Bootstrap 主要是通过百分比宽度(max-width: 50%; max-width: 25%;),以及 flex 属性,再加上媒体查询,「三管齐下」实现了栅格化布局的主体。

当然整个过程实现还有很多其他细节,我也一直认为 Bootstrap 的源码是管理大型样式项目的优秀典范,有兴趣的读者可以参阅源码进行了解。

横屏适配以及其他细节问题

很多 H5 页面中,我们要区分横屏和竖屏,在不同屏幕下要显示不同的布局,所以我们需要检测在不同的场景下给定不同的样式。通常使用 JavaScript 检查:

window.addEventListener("resize", () => {
   if (window.orientation === 180 || window.orientation === 0) {
       console.log('竖屏')
   };
   if (window.orientation === 90 || window.orientation === -90 ){
       console.log('横屏')
   }  
})

我们同样可以使用纯 CSS 来实现不同场景下的布局:

@media screen and (orientation: portrait) {
 /*竖屏样式代码*/
}
@media screen and (orientation: landscape) {
 /*横屏样式代码.*/
}

同时这里我们在总结一下其他常见的响应式布局话题:

这些问题都可以轻松找到解决思路,我们不再详细给出。

面试题:% 相对于谁

面试题:% 相对于谁
在之前的课程《前端面试必不可少的「面子工程」》中我们讲解了实现水平垂直居中的几种方式。其中 absolute + transform 方案:

.wp {
   position: relative;
}
.box {
   position: absolute;
   top: 50%;
   left: 50%;
   transform: translate(-50%, -50%);
}

我们用到了不止一处 % 单位。事实上,上述代码中的 % 还真代表着不一样的计算规则。第一处 50% 是指 .wrap 相对定位元素宽度和高度的 50%,而 transform 中的 50% 是指自身元素的宽高的一半。

那么在 CSS 中,这个常见的 % 单位有着什么样的规则呢?这也是一道很好的面试题目,我们在这一部分进行梳理。

border-radius: 50%

得到一个圆形,因此不难发现这里的 % 也是相对于自身宽高的。

这些就是我们常见的使用 % 的情况,还是很灵活多变的,具体细节都可以在 CSS 规范中找到。要求开发者的是了解常见的以及特殊的 % 场景。

总结

这一讲我们分析了实现响应式布局的常用手段,并结合实际案例加以剖析;同时讨论了布局方案对于页面性能的影响。到此为止,HTML 和 CSS 相关的内容终于告一段落了,读者应该能有一个清晰的认识:

HTML 和 CSS 很重要
HTML 和 CSS 如果不花心思,也不好学
但是关于 HTML 和 CSS 我们更应该注重实战,也许可以暂时不用「系统化」地去了解,但是遇见一个案例,就去攻克一个案例,慢慢地,你也能成为 HTML 和 CSS 专家!

阅读原文

上一篇下一篇

猜你喜欢

热点阅读