轻松掌握移动端web开发【尺寸适配】常用解决方案

14,649 阅读8分钟

keycode

本文主要针对初学移动端web开发的读者,笔者也是初学者,文中有众多用词不当之处望读者指正。

前言

从开始做web app开发到现在,一直对移动端的尺寸适配有一种模糊的概念。能说得上来‘媒体查询’,‘栅格化布局’,‘流式布局’等若干技术名词和实现方式,但是每次自己做web app开发的时候,做出来的产物总是不尽人意,比如在iPhone5上出现文字溢出,调整好的布局位置在一些小尺寸手机上发现位置非常不对,或是遮盖了其他元素,或是换行了。

如果是之前,我是这样的做法:

不断写媒体查询做兼容,直到PM或者QA满意为止。

这样的方法,存在以下几个问题:

  1. 难以适应所有的手机屏幕尺寸,总是会有不兼容的尺寸出现,问题仍然存在,只是尚未被发现。
  2. 太累了,非常折磨人。特别是这些问题一般会集中涌现在上线前被一并提出来,而那个时候正好是压力最大的时候。

我想了想,为什么会出现治标不治本的情况:

  1. 在项目开始的时候没和UI协调好规范。
  2. 身边没有太多的测试机,没法测试得太全面。
  3. 没有意识到移动端适配是一个棘手的问题。

那么,有没有那样一种一劳永逸全尺寸支持不用动脑子算的移动端尺寸适配方案呢?

**答案当然是有的。**笔者结合了自己所看的几篇热门的博客,总结了其中比较有用的几个知识点,希望能让读者更快的掌握并使用这个'一劳永逸的方法'。能偷懒的事情绝对要偷懒。🐷(热门博客题目如图,含flexible的github repo)

屏幕快照 2018-04-12 下午6.15.30

屏幕快照 2018-04-12 下午6.15.44

1523528305234

我们要达到的效果

  • 直接根据UI的标注视觉稿上面的尺寸进行开发。如标注的是230px, 通过函数将其转为rem而不用人工计算。
  • 在大部分的手机机型上看起来的页面视觉效果都一致。

什么是rem

一句话概括: 假如<html>标签上设置了样式font-size:16px,那么 1rem = 16px。 所以:

re

与UI的配合

首先,需要和UI小姐姐说一句话:

"标注元素的时候请按照750px * 1334px为准。"

那么,你将会拿到一张如下的标注图:

【核心】动态计算+rem

到这一步,我们仍然没有解决核心问题:

  1. 要自己去将px换算成rem。(可能旁边会放一个计算器)
  2. 全尺寸适配。

接下来,就是最为核心的环节了,笔者通过步骤图向大家还原计算的过程。

第一步:假设有三款不同长宽的手机。

第二步:把手机的宽分为10份,那么上述三款手机的每份宽度是35px/36px/37px。并且将<html>标签添加不同的font-size设置。

即:一份分别为35px/36px/37px

第三步:根据UI的px标注图计算出相应的rem:

第四步:rem将转化成不同的px尺寸在不同的手机上呈现:(ps:图中的除法结果算错了)

通过这样的方式,即可以在不同尺寸的手机上有相同的展示效果。而最cool的地方,是上述整个过程时自动适配的。开发者只需根据UI标注图无脑写就行了,再也不用挤眉弄眼地对着Chrome Devtools 疯狂调试了。

代码实现

把手机的宽分为10份,那么上述三款手机的每份宽度是35px/36px/37px。并且将<html>标签添加不同的font-size设置。

通过JavaScript动态计算出当前的屏幕宽度,切割为10份并将<html>fontSize设置为1份单位宽度

key-code

document.addEventListener('DOMContentLoaded', function(e) {
    document.getElementsByTagName('html')[0].style.fontSize = window.innerWidth / 10 + 'px';
}, false);

当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。另一个不同的事件 load 应该仅用于检测一个完全加载的页面。 MDN::DOMContentLoaded

根据UI的px标注图计算出相应的rem

这一步需要使用Sass来定义一个px2rem的工具函数:

scssFunction

// utils.scss
@function px2rem($px){
    $rem : 75px; // '750/10':分成10份
    @return ($px/$rem) + rem;
}

// foo.scss

.box1 {
 	width: px2rem(320px); // '(320/750) * 10 = 4.266rem'
 }

这样,我们在styleSheet中实际生效的是height: 4.266rem,而1rem对应多少px是上述JavaScript代码根据不同的window.innerWidth提前计算好的。这样就实现了自动适配。

如果你嫌写 px2rem()也麻烦,那么可以把函数名定义简单一些。

绕不开的viewport和dpr

在写这篇博客的开始,我曾试图绕开阐述viewportdpr这个抽象的概念,因为上述的内容已经可以从一个维度解决大多数问题了。但是,如果想做得更完美,就必须从另一个维度出发,而这个维度,就是dpr

首先,要区分两个概念:

  1. 设备的pixels
  2. css的pixels

有这样一个场景

一位前端工程师敲出了

.box {
    width: 100px;
    height:100px;
}

那么此时,他的意思是box在我们的屏幕中占的实际长宽是100px,在他脑中是这样的画面:

项目上线之后,有一个用户'不怀好意'地使用了放大镜功能将长宽放大了两倍,现在就变成了:

你会发现,设备花了200px的长宽来渲染CSS里面定义的100px的长宽,而设备pixels和样式pixels的比值,就是dpr,即Device Pixel Ratio,如果对这个概念仍有问题,请查看viewport剖析

dp

我们大家都知道Retina屏(视网膜屏),之所以看起来这么高清,就是因为苹果设备花两个像素来渲染一个像素的物体,那么看起来肯定更为精致。

所以,如果我们针对dpr=1的书写了rem2px(100px),那么在dpr=2的设备看起来将会是被放大了2倍的元素。

那么,如果我们能够查询出当前设备的dpr,并且做相应的缩放就可以解决这个问题。

举个例子:某些安卓机的dpr=1,但是UI做标注图的时候是根据dpr=2来做的,就像我们上文的750px * 1334px。直接按照750px * 1334px写出来的元素将会被放大两倍,那么我们就使页面缩小两倍,如何控制呢?

用viewport

简言之,在这里我们使用viewport是为了控制屏幕的缩放。

viewport

var dpr = window.devicePixelRatio;
meta.setAttribute('content', 'initial-scale=' + 1/dpr + ', maximum-scale=' + 1/dpr + ', minimum-scale=' + 1/dpr + ', user-scalable=no'); 
// 帮助理解 如果dpr=2,说明写的100px渲染成了200px,所以需要缩小至1/2,即1/dpr

另外值得一提的是,UI一般会以750px * 1334px的标准进行设计,因为这样使得设计稿更加精细。 比如我们写了rem2px(375px),那么会经过下列的过程换算到设备pixels宽度为390px且dpr=3的手机。

  1. rem2px(375px) ----> 5rem
  2. 5rem -----> 195px (样式pixels)
  3. 样式195px ------> 此时看起来(指的就是设备pixels)有195*3 = 585px的长度
  4. 设置dpr=1/3------->此时看起来只有195px

这样,我们完成了从dpr维度的适配。

show me the code:

sourceCode

<script>
  var dpr = window.devicePixelRatio;
  var meta = document.createElement('meta');

  // dpr
  meta.setAttribute('content', 'initial-scale=' + 1/dpr + ', maximum-scale=' + 1/dpr + ', minimum-scale=' + 1/dpr + ', user-scalable=no'); 
  document.getElementsByTagName('head')[0].appendChild(meta);

  // rem
  document.addEventListener('DOMContentLoaded', function (e) {
    document.getElementsByTagName('html')[0].style.fontSize = window.innerWidth / 10 + 'px';
  }, false);
</script>

为了防止全局变量污染或者覆盖他人的变量,请封装成模块再使用。

One More Thing

在写这篇博客的过程中,曾纠结过这样的问题:rem布局和百分比布局感觉差距不大啊,因为在写rem的时候是基于把宽度切为10份后再写的,就像是1rem = 10% = 10vw一样。这让我一度觉得可以用百分比布局。后来发现,如果出现盒子嵌套(这种场景太多了),那么百分比布局就出现问题了,因为其百分比的参考系选择的是父元素,所以我们如果在子盒子里面定义10%的宽度,指的是针对父盒子的而不是我们想要的针对整个window.innerWidth10%。而vw的代码可维护性不如上述的这套方案,且兼容性也没有rem好(这一点差距不是太大)。

如果想了解更多关于PC端or移动端布局,请看参考资源&鸣谢板块。

参考资源&鸣谢

移动端页面适配方案

使用Flexible实现手淘H5页面的终端适配

六种布局+rem布局的简介

DOMContentLoaded与load的区别

rem是如何实现的

AlloyTeam 移动web适配利器

viewport剖析

lib-flexible