关于前端:第一次写移动端自适应布局那就对了

7次阅读

共计 10363 个字符,预计需要花费 26 分钟才能阅读完成。

一、前言

随着挪动互联网的倒退, 做进去的网页可能显示在不同屏幕大小的设施上, 为了针对不同大小的屏幕都能够有良好的款式体验特地是一套代码适配多端的状况, 天然有不同布局方案设计进去, 我次要把他们分为三类

  • 基于宽度百分比和浮动配合的栅格布局或者叫流式布局,flex 弹性布局, grid 多维网格布局等。因为 flex 在这类布局中是支流, 为了不便就对立称为弹性布局。

  • 基于 @media 的媒体查问, 能够依据查问条件抉择执行特定的款式代码, 从而实现不同的布局, 因为次要是针对设施个性进行条件查问, 所以能够称为媒体布局。(媒体查问条件一览 这里提一点常常用的 device-aspect-ratio 这个查问设施像素比的条件曾经被废除了由 resolution 来取代)

  • 基于 rem 或者是 vm 等单位实现的一套布局, 这类布局的特点是严格依照设计图的比例来出现页面, 能够称为等比例布局或者叫自适应布局。

对于布局的名称, 其实是没有对立的约定, 次要是不便下文的叙述, 这三类布局在理论应用时也是能够混合应用的, 次要由理论场景决定。弹性布局和媒体布局在理解了根本的 css 常识后都比拟容易了解(很久前写的一个 flex 布局测试页面, 不便学习用的), 而对于自适应布局, 可能有点不好了解, 当然我会以起码的概念来说, 一开始太多的抽象概念反而妨碍对本质的了解。

由下面的自适应布局的特点, 即严格依照设计图比例来出现网页, 重点是依照比例, 不是尺寸, 这个很重要。那是什么比例呢, 对于网页设计来说个别能够简略地概括为设计元素的尺寸比上设计图的宽度尺寸, 这个有点像地图的比例尺, 只有依照比例尺的比例画图就能够把实在的地形画在一张无限尺寸的纸上。对于咱们网站开发来说, 其实也是一样, 咱们只须要找来一张图纸, 而后依照比例把图从新画在图纸上就是了, 其余能够不必管, 只有保障从新画上的元素的尺寸比上图纸宽度等于设计元素的尺寸比上设计图的宽度就能够了, 而这里的图纸宽度在浏览器上就叫做布局宽度, 能够通过 document.documentElement.clientWidth 来获取。到目前为止只须要记住这一个概念就能够了, 上面我会以一个素人的角度来开展, 通过素人的经验, 置信你会对自适应布局有更好的了解

二、viewport 初识

小明是一个街口职业技术学院的毕业生, 毕业后据说前端妹子丑陋, 而后就去应聘了一个叫前端的职位, 还应聘上了, 然而上岗后发现前端没有妹子, 迫于待业压力, 也只好迎难而上了。很快小明就相熟了公司的业务, 而且理解到和他对接的 UI 是一个妹子, 名字叫小红, 他就变得更加好学了, 心想肯定能完满实现小红的设计成果。很快就有一项工作交给了小明, 而且小红也给了设计图, 其中 PM 啊强嘱咐小明要在不同大小的手机屏幕都要依照设计图比例来显示, 小明示意这齐全没有问题。

小红的设计图

小明认真钻研了一番设计图, 发现就只有两个方块和一条分割线, 要比例统一立刻就想到了宽高百分比, 但很快就发现了问题, 高度的百分比不是绝对宽度的, 而且要让高度百分比起作用, 下面的 body html 都要设置高度, 问题是设计图竟然没有标记高度, 看来这种办法不行。否定这个办法后, 小明没有止步不前, 立刻就写下了上面的实现计划, 说不定浏览器自身就自已主动适配呢

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title> 浏览器请主动适配 </title>
  <style>
    body {margin: 0}
    .top {
      width: 750px;
      height: 100px;
      background-color: green;
      border-bottom: 1px solid black;
    }
    .left {
      width: 200px;
      height: 500px;
      background-color: red;
    }
  </style>
</head>
<body>
  <div class="top"></div>    
  <div class="left"></div>    
</body>
</html>

但很显著小明想多了, 而且他很快发现了一个奇怪的问题, 他关上 chrome 浏览器的控制台的挪动端模式, 把尺寸设置为 750px, 还是这样, 他想那必定是 dpr 问题, 如是他把 dpr 调节为 1 2 3 还是一样, 需然这个时候还不晓得什么是 dpr(在线查看该例子)

宽度 750px DPR 1 2 3 下的样子

小明被头顶那块绿震惊了, 为什么缩水了? 小明通过审查元素, 发现其宽高还是设置的宽高, 作为一个学过 js 的人, 小明感觉不能被 css 所蛊惑, 而后在控制台输出了上面的代码

    document.querySelector('.top').style.width

很快浏览器就输入了 "", 很显著宽度款式是设置在 css style 标签上的, 不是设置元素的 style 内联属性上, 天然会返回空字符串, 小明很快就看穿了这个问题, 接着写下了

  getComputedStyle(document.querySelector('.top')).width

浏览器返回了 "750px", 看来控制台的 css 没有坑骗我啊, 小明感叹道, 然而小明还是不死心

  document.querySelector('.top').getBoundingClientRect().width

浏览器仍然输入 750
这就奇怪了, 调试的尺寸为 750px, 元素的宽度也是 750px, 怎么就不能占满呢? 是不是浏览器有 bug
小明是个聪明人, 他很快就关上搜索引擎输出了 第一次写挪动端布局遇到了 bug 怎么办 不出意外他找到了这篇文章, 并很快看到了前言, 很显著了他记住了布局宽度和 document.documentElement.clientWidth, 他也是一个求实不会摸鱼的人并没有浪费时间在持续浏览这篇文章上, 他显然不晓得这篇文章配角是他。

事件很快就给了小明一个答案

  document.documentElement.clientWidth

浏览器输入了 980, 对就是 980,这个就是浏览器的默认布局宽度, 我写的元素就是放在这个大小上的, 小明胸有成竹地说道。小明也是一个富裕想象力的人, 他先把自已化身为一个浏览器, 而后用浏览器的身份把自已化身为一个镜头, 当初镜头呈现了一个物体, 物体的大小超过了镜头的视线, 那么镜头应该怎么能够记录下整个物体呢?
其实有两个办法, 第一个办法是让镜头挪动或者是让物体挪动, 通过镜头和物体的绝对挪动就能够记录到物体整个大小。
第二个办法是, 把镜头和物体的间隔拉远, 视线宽阔了, 天然就能够看到整个物体, 相对来说物体就看似变小了。
这两种办法中, 镜头和物体的大小其实始终都没有变, 变的只是间隔和绝对挪动的坐标。而这里的镜头就是浏览器的可视区域, 很多时候就是屏幕区域, 而物体显然就是布局区域, 那么浏览器是采纳何种办法来显示布局区域呢, 如果是采纳第一种办法, 那么显然 980 的布局宽度是大于屏幕宽度的, 如果要浏览整个网页, 天然就须要向左滑动, 相似于绝对挪动, 很显著事实不是这样, 整个页面都能看到, 那必定是浏览器通过拉大间隔来把页面塞进去, 失去的成果就像是把整个布局区域都放大塞进了屏幕区域内, 很显然这种缩放也是等比例缩放的。
小明毕竟是一个求实的人, 他很快从工程部借来一把游标卡尺, 对着屏幕量起了元素的尺寸来(px 和理论间隔的换算是基于特定设施的, 有趣味能够看这里), 并查问了设施的 dpi(每英寸有多少像素) 值, 最初计算出顶部的绿色块的大小应该是 575px, 这个大小就是缩放后元素的尺寸值, 前面只须要计算对应比例是否统一就能够得出浏览器是否采纳了主动缩放来实现这个成果

  575 / 750 = 0.7666666666666667
  750 / 980 = 0.7653061224489796

毕竟是应用游标卡尺测量的, 这点误差其实是能够承受的, 小明持续测量了其余的尺寸, 也失去相似答案, 最初小明总结了一条要使网页符合要求的公式,

  这个是要求: 最初缩放后的理论大小 / 屏幕的宽度 = 设计图的尺寸 / 设计图的宽度
  因为: 最初缩放后的理论大小 / 屏幕的宽度 = css 尺寸的大小 / 布局宽度
  所以保障: css 尺寸的大小 / 布局宽度 = 设计图的尺寸 / 设计图的宽度
  就能够实现须要的要求

设计图和设计图的宽度还有布局宽度都是已知的, 而后通过换算就能够计算每一个元素的尺寸大小了, 很快小明就写了上面的版本

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title> 浏览器布局 980</title>
  <style>
    body {
      margin: 0;
      background-color: blue;
    }
    .top {
      width: 980px;
      height: 130.67px;
      background-color: green;
      border-bottom: 1.37px solid black;
    }
    .left {
      width: 261.33px;
      height: 653.33px;
      background-color: red;
    }
  </style>
</head>
<body>
  <div class="top"></div>    
  <div class="left"></div>    
</body>
</html>

小明关上了控制台并调节了不同的宽度和 dpr 成果都很好, 当然从下面的公式来看其实和 dpr 也是无关的。(在线查看该例子)

宽度 474x DPR 3 下的样子

然而很快小明就找到问题, 如果调节的宽度超过 980, 布局宽度就不再是 980, 而是变为调节的宽度, 这个时候尺寸的比值就不再被维持, 而当尺寸的宽度是少于 250 时, 浏览器不再等比缩放了, 而是采取下面的说到的第一种办法, 这个时候下面公式也不再被维持。当然小明心田是毫无波澜的, 毕竟谁的手机是这种尺寸的, 如果是那就不是手机了

不过这显著是有问题的, 那就是必定不是全副的手机浏览器的默认布局是 980, 如果遇到不是 980, 那必定就不行了, 那有什么方法可能设置浏览器布局宽度呢? 小明毕竟是一个聪明人, 既然能够从 document.documentElement.clientWidth 从获取值, 那应该也能设置值吧,

document.documentElement.clientWidth = 500
500
document.documentElement.clientWidth
980

很显著小明想太多了, 不能应用这个办法来设置布局宽度。后面提到小明只看到前言, 然而 viewport 初识 这个题目, 他应该是看到的, 很快他就找到了设置浏览器布局宽度的办法

  <meta name="viewport" content="width=">

那么 width 应该设置多少呢? 小明翻了翻公式

css 尺寸的大小 / 布局宽度 = 设计图的尺寸 / 设计图的宽度

如果把布局宽度设置为设计图宽度, 那 css 尺寸的大小不就能够设置为设计图的尺寸, 而且还不必执行额定的计算, 那切实是太好了, 小明显著为了自已发现而兴奋不已, 如是写下了上面的版本

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title> 浏览器布局设计图尺寸 </title>
  <meta name="viewport" content="width=750">
  <style>
    body {
      margin: 0;
      background-color: blue;
    }
    .top {
      width: 750px;
      height: 100px;
      background-color: green;
      border-bottom: 1px solid black;
    }
    .left {
      width: 200px;
      height: 500px;
      background-color: red;
    }
  </style>
</head>
<body>
  <div class="top"></div>    
  <div class="left"></div>
</body>
</html>

同样在不同的宽度和不同的 dpr 有很好的成果 (在线查看该例子)

宽度 520px DPR 2 下的样子

而且调节屏幕宽度大小, 当超过布局宽度时, 也不会扭转变布局宽度, 然而仍然在少于 250 时遇到下面的状况, 不过这显然也不会有影响, 于是小明就欢快地提交了工作。

三、rem hack

提交了工作不久, 小明就接管到小红的信息, 你的 1px 太粗了, 我心愿细一点的, 小明纳闷了, 我的 1px 不是依照你的 1px 设置的吗? 怎么太粗了呢。接着小红发过来了一张截图, 一条黑线在红绿之间, 并看到黑线是横跨了两个像素, 小红反复道, 我心愿这个是 1px 的。
难度是下面的公式有问题, 怎么会呈现 1px 变粗的问题? 很快小明就找到问题的起因了, 是 dpr 导致的, 原来设计图上标注的 1px 和设计图其余中央标注的 px 单位是不同的, 或者说 ui 冀望的是实在的 1px, 而不是逻辑上的 1px。例如如果一部手机的分辨率是 1920 * 1080, 如果设置 css 640px 就能够笼罩这个宽度, 那么该设施的 dpr = 1920 / 640 = 3,dpr 是一个设施的个性, 不同的设施的 dpr 都有不同, 示意设施像素比, 下面的布局公式不是谬误的, 只是误认为设计图的 1px 就是逻辑像素 1px。如果下面的计划放在一部 dpr 是 3, 屏幕宽度是 750px 的手机上显示时, 失去的后果就是分隔线将横跨 3 个设施像素。

理解了下面的起因后, 正当小明着手解决时, 啊强也找到了小明, 啊强说你的计划挺好的, 就是当手机横着看时, 红色和绿色都感觉太粗了, 这样吧你给限度个最大宽度 500px, 如果手机的屏幕宽度大于 500px 时就让整个网页居中, 两边留白。小明也感觉这个需要正当, 如果对于宽度较大的屏幕, 还这样等比例缩放, 天然看到的货色就变少了, 人们换一个大屏幕可不想看到的货色反而变少了。

啊强的需要看似更容易实现, 应该比拟容易解决, 如是小明在后面的计划中减少了上面的代码

   html {background-color: white;}
    body {
      margin: 0 auto;
      background-color: blue;
      max-width: 500px;
    }


宽度 586px DPR 3 下的样子

在线查看例子

小明的用意很显著, 通过最大宽度尺寸限度 body 的大小, 而后通过 margin auto 让 body 居中。然而小明疏忽了前提, 就是为什么设置 px 单位也可能做到自适应, 或者是上述的公式可能成立起因是因为浏览器可能主动等比例缩放布局区域到可视区域外部, 这个有点像把一张图片放在 img 元素一样, 会对整体进行缩放,body 尽管设置了 max-width: 500px, 这个 500px 是放在布局宽度 750px 区域上, 并随整体进行缩放, 因而就看到这种成果。要想这个 500px 不受缩放影响, 那么就须要要求这个尺寸不受等比例缩放影响, 但这个计划把布局宽度定死了, 如果要适配不同的屏幕宽度, 天然就须要进行等比例缩放。
而且小明在捣鼓的过程中, 还发现了这个计划的一个问题, 就是媒体布局的视口宽度查问条件都生效了如 min-width 和 max-width 等条件不会按预期工作, 起因是这些查问都是针对视口宽度的, 当初的视口宽度被定死为设计图大小, 天然就无奈按预期工作。
因为等比例缩放布局区域到可视区域外部是浏览器的行为, 内部是无法控制的, 基于这个起因要使最大宽度设置起作用天然就心愿浏览器不要等比例缩放, 要实现这个其实也很简略

<meta name="viewport" content="width=device-width">

布局的宽度设置为屏幕宽度, 这个设置还有一个益处, 就是媒体查问会以冀望的形式执行。

当初的问题是, 当初自适应布局不再依赖浏览器的等比例缩放了, 然而上面的公式仍然是起作用的, 因为不再等比例缩放, 也可看做是缩放比例是 1, 公式上的推导都是一样的

css 尺寸的大小 / 布局宽度 = 设计图的尺寸 / 设计图的宽度

左边设计图的尺寸和设计图的宽度是固定的, 而右边的布局宽度变为不再是固定了, 因为咱们要适配不同的屏幕尺寸, 那么怎么能够算出 css 尺寸的大小呢。咱们须要一个单位, 一个非凡的单位, 咱们就叫它布局单位, 这个单位应该绝对布局宽度, 这样在计算时就会约去高低不定的布局宽度, 这样才可能适配不同的尺寸。

小明犯愁了, 在他的意识中 css 可没有布局单位啊, 不过小明是一个聪明人, 没有能够自已写一个啊, 布局单位的特点不就是绝对布局宽度嘛, 在看了一众的单位后, 小明选取了 rem, 这个单位原本是一个绝对单位, 任何设置了该单位的尺寸都会绝对根元素的字体大小尺寸, 只有把该尺寸设置为布局宽度不就变为布局单位了。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width">
  <title> 浏览器布局 rem</title>
  <style>
    html {background-color: white;}
    body {
      margin: 0 auto;
      background-color: blue;
      max-width: 500px;
    }
    .top {
      width: 7.5rem;
      height: 1rem;
      background-color: green;
      border-bottom: 0.01rem solid black;
    }
    .left {
      width: 2rem;
      height: 5rem;
      background-color: red;
    }
  </style>
  <script>
    (function () {function resize() {
      var clientWidth = document.documentElement.clientWidth;
      clientWidth = clientWidth > 500 ? 500 : clientWidth
      document.documentElement.style.fontSize = (clientWidth / 7.5) +'px';
    }
    resize();
    window.onresize = resize;
  })()
  </script>
</head>
<body>
  <div class="top"></div>    
  <div class="left"></div>
</body>
</html>


宽度 426px DPR 3 下的样子


宽度 826px DPR 3 下的样子

在线查看例子

在屏幕宽度少于 500px 时, 设计的要求是

设计要求: 元素理论大小 / 屏幕宽度 = 设计图的尺寸 / 设计图的宽度
等比缩放比例是 1 因而: 元素理论大小 / 屏幕宽度 = css 尺寸大小 / 布局宽度
所以: 设计图的尺寸 / 设计图的宽度 = css 尺寸大小 / 布局宽度

通过查看程序, 咱们晓得 7.5rem 其实就是布局宽度, 在依照比例带入

设计图的尺寸 / 750px = xrem / 7.5rem

这里成心设置为 7.5 就是不便计算, 能够看到左边的 rem 被高低约去

x = 设计图的尺寸 / 100

在屏幕宽度大于 500px 时, 设计的要求是

设计要求: 元素理论大小 / 500 = 设计图的尺寸 / 设计图的宽度
等比缩放比例是 1 因而: 元素理论大小 / 屏幕宽度 = css 尺寸大小 / 布局宽度
所以: 设计图的尺寸 / 设计图的宽度 * 500 / 屏幕宽度 = css 尺寸大小 / 布局宽度
布局宽度是等于屏幕宽度的: 设计图的尺寸 / 设计图的宽度 = css 尺寸大小 / 500

能够看到当屏幕宽度大于 500px 时, 如果心愿 x 的 css 尺寸设置也能利用在这种状况, 咱们须要 500 应该等于 7.5rem, 这个也是程序中三目运算符的逻辑, 这个时候 7.5rem 总是等于 500。

这种计划实现了阿强的需要, 当然也有一点问题就是通过 rem 换算后呈现了像素误差, 而且也没有解决 1px 问题, 上面小明就要解决 1px 像素误差了

通过一番搜寻小明理解到已经有一个 flexible 的计划, 通过布局宽度的缩放来解决了 1px 的问题, 其原理交融了等比例缩放和 rem 布局单位的设置。viewport 标签中有一个 initial-scale 属性, 能够设置布局宽度绝对屏幕宽度的倍数, 当初把这个属性的属性值设置为 initial-scale = 1 / dpr, 这样布局宽度就等于屏幕宽度 dpr 倍, 然而仍然会须要合乎上面的公式

设计图的尺寸 / 设计图的宽度 = css 尺寸大小 / 布局宽度

因而对于布局单位 rem 来说,1rem 的理论尺寸变为了 1rem * dpr 的尺寸, 而这个时候布局宽度是屏幕宽度的 dpr 倍, 因而浏览器会缩放该布局宽度, 刚好放大 dpr 倍, 因而 1rem 的理论尺寸和之前没有设置 initial-scale = 1 / dpr 是一样的。
然而对于非布局单位, 比方 px, 如果设置 1px 在布局宽度增大 dpr 倍时, 仍然是 1px, 然而在浏览器缩放该布局宽度时, 会整体进行缩放,1px 大小的尺寸会被放大 dpr 倍, 而之前举的例子中, 如果 1px 的分隔线横跨了 3 个像素, 刚好经验了这番折腾就能够渲染为 1 个设施像素。
尽管解决了 1px 的问题, 但也会带来另一个问题, 就是任何的非布局单位, 都会依据 dpr 的倍数被放大, 如果不是 1px 像素, 那就须要进行额定的设置, 而且在查阅中小明发现该计划曾经弃用, 应该应用 viewport 单位, 哈哈原来布局单位很早就曾经存在了。

三、vw 启程

rem 毕竟只是一个绝对单位, 不是布局单位, 既然有了专门的布局单位, 那就尽量应用布局单位吧.100vw 就是布局的宽度, 有了这个信息, 再加上以上的折腾, 小明很快就写出了 vw 的版本

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width">
  <title> 浏览器布局 vw</title>
  <script>
  </script>
  <style>
    html {background-color: white;}
    body {
      margin: 0 auto;
      background-color: blue;
    }
    .top {
      width: 100vw;
      height: 13.33vw;
      background-color: green;
      border-bottom: 0.13vw solid black;
    }
    .left {
      width: 26.67vw;
      height: 66.67vw;
      background-color: red;
    }
  </style>
</head>
<body>
  <div class="top"></div>    
  <div class="left"></div>
</body>
</html>

不过小明很快就陷入了迷茫, 就是不晓得怎么实现啊强的需要, 应用 rem 的话能够在达到最大值时把 7.5rem 设置为一个常数,然而如何把 100vw 设置为一个常数, 如同只能是把布局宽度设置为一个常数, 也就是动静批改 viewport 的值, 然而把布局宽度设置为一个常数后, 浏览器就会主动缩放该布局宽度, 天然也就达不到需要。或者能够引入一个中继的单位让这个单位绝对 vw 单位, 在最大尺寸达到后把这个应用 vw 的单位设置为常数, 但这个和 rem 的计划那有什么区别? 看来先应用 rem 计划应酬啊强了
最初就是解决 1px 了, 依据 flexible 计划的启发, 通过放大后放大的形式就能够解决这个问题了
上面这个能够解决 dpr 2 的问题

.top::after {
      position: absolute;
      box-sizing: border-box;
      content: ' ';
      pointer-events: none;
      top: -50%;
      right: -50%;
      bottom: -50%;
      left: -50%;
      border: 0 solid #ebedf0;
      -webkit-transform: scale(0.5);
      transform: scale(0.5);
      border-bottom-width: 0.13vw;
    }

在线查看例子

最初

本文从布局宽度开始逐渐介绍了自适应布局的不同计划, 由浏览器的默认行为登程, 并由根底公式的推导阐明了间接设置 px 单位为何也能适配不同的屏幕尺寸。但也指出了该计划的毛病, 固定死布局宽度会导致咱们失去对网页的更多管制, 为躲避这些毛病, 布局宽度变成可变的, 绝对屏幕宽度的, 也通过公式的阐明, 要适配不同的屏幕宽度, 引入布局单位是必须的, 这样才能够约去可变的局部, 让一套代码适配不同的屏幕宽度。在布局单位上, 也介绍了 rem vw 单位, 并阐明了这些单位的工作原理,rem 作为 hack 手法, 被革新为布局单位, 而 vw 是正儿八经的布局单位, 也指出了应用这些单位时可能遇到的局部问题。同样也指出了 1px 问题, 设计图的 1px 和 css 设置的 1px 其实是两个概念, 介绍了 flexible 为何能解决 1px 的问题, 从这个计划的启发下, 利用 transform 来解决 1px 问题。当然文中列出问题只是在实践中遇到的问题中很少的局部, 而咱们只须要记住的就是浏览器默认的缩放行为所波及到的尺寸变动, 而后基于设计的需要, 利用公式来推导。
自适应布局只是我前言提到的三种布局中的一种, 能够让咱们回归到写一种传统 pc 页面的感觉, 并严格实现了设计图的比例, 很显著这种设计是 ui 主导的, 前端变得毫无灵魂。而很多时候, 这种设计的初始是动态的, 天然很难去思考多屏幕下的动态变化, 这个时候是不是更应该让前端去施展更多的主动性, 而 ui 设计图只须要列出要害的设计尺寸, 而作为前端, 能够搭配弹性布局和媒体布局作出更好的设计, 让工具人变得没那么工具好不

参考资料

  • https://developer.mozilla.org…
  • https://developer.mozilla.org…
  • https://drafts.csswg.org/css-…
  • https://www.quirksmode.org/mo…
  • https://www.quirksmode.org/mo…
正文完
 0