1

前言

这篇文章主要讨论了移动端页面渲染与布局的一些基本概念例如 viewportcss 像素, 这些内容是了解移动端页面适配的基本前提.

在这篇文章中记录了这些基本的概念, 因为我并不从事移动端开发, 文章有误, 欢迎指正.

移动端渲染表现

为了简单起见, 我们不从概念讲起, 我们先来看看一个固定宽度的元素被放置到移动端浏览器中会发生什么.

有如下代码:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    .example{
      width: 900px;
      background-color: aqua;
    }
  </style>
</head>
<body>
  <div class="example">
    我的宽度是 900px
  </div>
</body>
</html>

当然桌面浏览器渲染效果肯定在我们意料之中啦✨:

图片: 1920px下chrome渲染的效果:
批注 2019-07-18 165112.png

而下面展示了图片展示了几个移动端浏览器渲染的结果:

图片: 移动端浏览器渲染效果:
1563439703821.jpg

这里我们简单的介绍一下图片中浏览器的版本, 这些浏览器在后文的截图中版本都是不会发生变化的.

浏览器 版本 平台
chrome 75 windows10
chrome 75 android9
edge 42 android9
Samsung 9.3 android9

仔细观察我们发现:900px 宽的 div 没有占满屏幕, 也就是说屏幕的宽度比 900px 还要大.
那么屏幕宽度到底是多大呢, 我们插入一行代码来获取HTML元素的宽度:

<script>
  document.write(`html元素的宽度是${document.documentElement.clientWidth}`);
</script>

不同浏览器的显示结果为:
1563441464510.jpg

他们都输出了同样的数据那就是 980 在某种程度上我们知道了页面的宽度, 原来页面是 980px 的宽度所以 900px 的div无法铺满一行.

对于移动浏览器它感知到自己的视口的宽度是 980px, 此时它渲染的比例就和一个 横向分辨率为 980px 的桌面显示器渲染的一样. 只不过移动终端的屏幕是一个小号的显示器而已.
在没有更改页面的设置的情况下或者说默认的情况下, 移动终端的浏览器会按照 980px 的宽度来渲染页面, 然后再将页面缩放到适合屏幕的大小.

不理解没关系, 我们使用一张图来理解这一行为:
1563443177628.jpg

为什么会有这种行为?

原因很简单, 在移动设备刚刚兴起的时候大多数的页面都是为桌面设计的在当时一个页面的内容区域的宽度一般在 980px 以内.

但是移动设备屏幕的分辨率很小, 例如某款手机的屏幕的横向分辨率是 320px 完全不足以容纳为桌面设计的页面.

和现在的情况相反, 手机厂商得设计一种方式来兼容现有的页面确保页面在移动端布局正常, 所以手机浏览器的默认内容区域的宽度被设置为桌面页面内容区域的典型值 980px(不同设备不同,后面有统一方法), 为了浏览效果和桌面一致将内容区域平铺到屏幕上.

这样确保了布局正常且手机屏幕上浏览的整体效果和在桌面上的一致, 不过说到底移动端的横向分辨率只有 320px 自然有些内容看不清楚这个时候怎么办呢 -> 双击缩放呗🤣.

默认渲染方式导致的问题

回想一下移动端浏览器的两个默认的渲染行为:

  1. 页面内容区域大小为 980px
  2. 将页面区域按照比例缩放到屏幕的大小

对于响应式设计来说, 这种默认的行为有些问题. 例如第一种行为会导致媒体查询没无法使用, 因为移动端页面宽度固定 980px.

第二种自动缩放的行为会导致页面模糊(高分辨率的页面缩放到低分辨率的终端上), 或者难以辨别内容(高分辨率的终端导致文字太小).

在无额外的配置影响下, 下面的例子中的媒体查询不会起作用, 因为页面宽度为 980px 不会变化:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style>
    .example {
      width: 900px;
      background-color: aqua;
    }

    @media screen and (max-width:960px) {
      .example {
        background-color: red;
      }
    }
  </style>
</head>

<body>
  <div class="example">
    我的宽度是 900px
  </div>
  <script>
    document.write(`html元素的宽度是${document.documentElement.clientWidth}`);
  </script>
</body>

</html>

所以一个自然的想法就产生了页面的大小应该和屏幕大小一致才对啊, 这样我们就可以根据不同的屏幕尺寸来定制页面展示的效果.

初识 viewport 属性

需要借助于 meta 标签来完成, 通过设置 <meta name="viewport" content=""> 来控制页面的渲染.

在大部分的IDE和文本编辑器中这行代码被放到了创建HTML文件的模板里, 所以很多人会觉得眼熟:

<meta name="viewport" content="width=device-width, initial-scale=1.0">

这里你可以基本将这行代码理解为 "调整视口宽度和设备宽度一致", 更多的讨论我们在随后的中会提到.

图片:适应设备宽度后的渲染效果:
1563458168665.jpg

注意:上面图片中的三个浏览器实际上都是有横向滚动条的, 因为 900px 的 div 宽度超过了视口宽度.

页面中的内容显得更加大了一些, 除此以外看起来也没太大变化嘛. 不过这个手机看起来分辨率挺低的啊 html元素宽度居然是 412px , 这个年头的老人机的分辨率都比这个高了吧!

是的 HTML元素宽度确实是 412px , 但是我的手机屏幕的横向分辨率却是 1440px, 为什么?

看来我们又有了一个新的问题, 不过好在下一章中我们就可以解开这个谜团了.

css像素与物理像素

css 中的 px 单位也就是我们常说的 "像素" 和显示器屏幕这个物理设备上提到的 "像素" 不是同一个东西, 换句话说 "css 中的像素" 不等于 "物理像素", 物理像素是屏幕上实际的像素点, 而 css 像素 是一个虚拟的单位.

这有点违反直觉, 因为在桌面浏览器上我们设置的一个 1px 大小的元素, 他所占的实际大小也是一个像素.

这因为大部分情况下桌面操作系统中设定的屏幕分辨率和显示器的物理分辨率都是一致的, 所以就会造成 "css 中的像素" 和 屏幕上的像素是同一个概念的误会.

不过证明 "css 中的像素" 不等于 "屏幕像素" 也不难, 例如在 windows 10 中你可以在 显示设置->缩放与布局 中设置系统缩放比例为 125%. 此时一个横向分辨率为 1920 的显示器去输出 html 元素的宽度的时候是 1536px.

图片: 缩放选项:
批注 2019-07-18 222819.png

图片:chrome和firefox的 HTML 元素的宽度:
1563459939233.png

另外你也可以简单的通过 ctrl + 滚轮上/下 来调整浏览器的缩放来模拟这种情况. 不过你始终要记住这无非是使用了多个物理像素来显示一个 css 像素 而已.

注意: 修改系统缩放比例会导致 window.screen 读取到的数据也是缩放后的而不是屏幕的原始分辨率.

css 像素与设备像素比

在早期的移动设备中常见的横向物理分辨率是 320px. 但是在现代的移动设备中的屏幕物理分辨率大部分都超过了 320px, 实际上现如今桌面显示器还在 1080p 的阶段, 移动设备的分辨率大部分都接近 2k 了, 所以这里会出现一个问题, 移动设备的屏幕太小如果一个css像素等于一个物理像素那么显示的东西会挤在一团导致无法浏览. 所以终端厂商会在设备里面内置了一个 "最佳显示分辨率" 用于优化显示.

毫无疑问大部分的终端中这个 "最佳显示分辨率" 都小于设备的 "物理分辨率" , 因为它们使用多个 "物理像素" 来显示一个 "css像素".

例如:我们有一个设备的横向物理像素数量是 1920, 某个终端制造商设置的内置的最佳显示值是 960, 那么我们就是用一个css像素来对应两个物理像素, 效果就是把显示的内容放大了一倍, 那么这个比值就是 1920 / 960 = 2 ,而这个值就是常说的设备像素比, 在浏览器中这个值我们可以使用 windows.devicePixelRatio 来获取.

我在 windows 10 系统缩放 125% 情况下和移动端中执行如下代码:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .example {
      width: 900px;
      background-color: aqua;
    }

  </style>
</head>

<body>
  <div class="example">
    我的宽度是 900px
  </div>
  <script>
    document.write(`html元素的宽度是${document.documentElement.clientWidth}px`);
    document.write(`<br>`); 
    document.write(`设备像素比是${window.devicePixelRatio}`);
    document.write(`<br>`); 
    document.write(`实际的物理分辨率是 ${screen.width} * ${window.devicePixelRatio} = ${screen.width * window.devicePixelRatio}px`);
  </script>
</body>

</html>

windows 10 输出结果如下:
1570633377630.png

一个高分辨率的手机会有一个更大的设备像素比, 这样文字才能够大才能看的清楚:
1570634979264.jpg

了解 viewport

viewport(视口) 的概念非常简单, 相当于浏览器整块渲染区域, 或者说它等于浏览器窗口, viewport 拥有它的宽高.

在一个空白的页面中存在一个 div 这个 div 被设置了样式:

div {
    width:30%;
    height:500px;
}

他的宽度被设定了一个相对值, 当窗口宽度发生改变的时候, 他会随着改变宽度. 那么这个百分比值相对的是谁呢? 根据规范是相对于父元素, body 的父元素是 htmlhtml 的父元素就是 viewport.

移动端 viewport 模型

移动端的情况比较复杂一些, 这里有两个基本概念 visual viewportlayout viewport.

现在的移动端页面都是经过适配的, 所以要想找到一个早先的移动页面来举例比较困难, 不过请请你试想以前你用手机浏览那些没有做移动端适配的页面所发生的情况.

这种情况就是, "页面上的内容非常小, 你需要通过双指放大才可以看清页面上的细节, 此时的页面往往会有横向滚动条", 例如下方这张中国银行电脑版在移动端的截图:
1570962455438.png

这种行为就好像透过望远镜观察星空一样, 星空的大小是固定的, 但是我们可以透过望远镜选择放大来观察局部的细节还是缩小来观察整体.

上面的例子中 "星空" 就是我们的页面, 而望远镜就是我们的手机屏幕, 星空代表的就是 layout viewport 也就是我们的页面, 而望远镜代表的就是 visual viewport 也就是我们的页面的可视区域.

星空有固定的大小, 那么 layout viewport 固定的大小是多少, 这个取决于浏览器, 不过现在常见的大小是 980px, 这个值你可以在本文稍早的移动端截图中找到.

你在页面上设置的 css 像素 都会作用在 layout viewport 上, 而不是 visual viewport 上.

度量 layout viewport

layout viewport 可以通过 document.body.clientWidth/Height 来进行获取, 本质上我们无法直接获取 layout viewport 的大小, 但是在默认情况下 HTML 元素的大小就是 layout viewport 的大小, 因为块级元素继承了 viewport 的 100% 的宽度.

就像之前提到的一样 layout viewport 的值在未经设置的情况下是固定的. 无论设备是竖屏还是横屏这个宽度都是 980px(默认情况下).

图片: 竖屏效果:
1570963629053.png

图片: 横屏效果:
1570963659633.png

可以看到 document.body.clientWidth/Height 的值(图片标红的位置) 并未发生变化.

度量 visual viewport

visual viewport 可以通过 window.innerWidth/Height 来进行衡量. 这个属性会以 css 像素 作为单位返回值, 告诉你页面中还有多少空间可以用于布局, 很显然当页面放大这个值会减少, 页面缩小这个值会变大.

一个更加明显的例子就是, 当移动端键盘弹起的时候 visual viewport 会发生改变, 而 layout viewport 是不会变化的, 通过这个差异我们可以判断键盘是否弹出.

注意: window.innerWidth/Height 这个值会包含滚动条的大小.

VisualViewport 对象

注意: 这里介绍的 VisualViewport 还是一项实验中功能.

Visual Viewport APIVisualViewport 接口代表了给定窗口的 visual viewport.

这个对象接口是一个全局属性, 它提供了两个事件:

  • resizevisual viewport 大小改变的时候触发
  • scrollvisual viewport 滚动的时候触发

使用示例:

window.visualViewport.addEventListener('resize', resizeHandler);

另外他还提供了一些有用的属性, 这些属性都是只读的:

使用 <meta name="viewport">

<meta name="viewport"> 属性在意义在于控制 layout viewport 的宽度(因为 web 开发不关心高度). 你可以为其指定 css 像素 来获取一个固定的宽度.

但是在大部分情况下我们希望 layout viewport 是跟随设备的大小而改变的, 下面我们通过修改其 width 属性来控制它的大小:

示例1, 设置为 500px:

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

示例2, 等于屏幕的物理像素:

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

注意: 就像我之前在 css 像素与物理像素 这节提到的, 这个属性最终得到的值并不会是实际的物理像素, 而是由设备厂商提供的一个建议值.

除了可以指定 width 我们还可以指定其他的属性, 这些属性通过逗号隔开, 例如使用 user-scalable=no 来禁止用户缩放页面, 使页面看起来像原生应用:

<meta name="viewport"  content="width=device-width, user-scalable=no">

下方的表格中提供了 viewport 的几个可选属性:

属性 描述
width 设置layout viewport 的宽度,为一个正整数,或字符串"width-device"
initial-scale 设置页面的初始缩放值,为一个数字,可以带小数
minimum-scale 允许用户的最小缩放值,为一个数字,可以带小数
maximum-scale 允许用户的最大缩放值,为一个数字,可以带小数
height 设置layout viewport 的高度,这个属性对我们并不重要,很少使用
user-scalable 是否允许用户进行缩放,值为"no"或"yes", no 代表不允许,yes代表允许

width=device-width 功能一样的还有 initial-scale=1, 有时我们会常常看到这两个属性是被同时设置的:

<meta name="viewport" content="width=device-width, initial-scale=1">

通过设置 initial-scale=1 是告诉浏览器相对于 ideal viewport (这个概念来自于 Meta viewport 一文, 我把它理解设备厂商内置的最佳显示分辨率也就是 device-witdh的大小) 进行缩放且缩放一倍, 所以他会和 width=device-width 效果一致.

同样根据文中所述, 这种写法是用来解决兼容问题, 因为 IE 不支持 initial-scale=1 而 iphone 和 ipad 不支持 width=device-width.

当这两个属性同时使用的时候:

<meta name="viewport" content="width=400, initial-scale=1">

浏览器会取它们两个中较大的那个值, 如果 ideal viewport 是 500px 那么浏览器就会使用 500px, 反之使用 400px.

next step

在后续我会介绍几种常见的移动端适配方案, 和它们的工作原理, 由于工作关系可能需要一些时间才会写完.

参考

十分建议读者阅读下列的几篇文章, 它们同样也是本文章的重要参考来源之一, 原文是英文的, 不过已经被他人翻译完成, 但是由于原链接已经丢失, 这里提供转载的地址, 原文一共有三篇, 这里按顺序给出:

https://www.jianshu.com/p/738...

https://www.jianshu.com/p/692...

https://www.cnblogs.com/yelon...


ASCll
527 声望13 粉丝