移动端适配你需要了解的那点事

2,684 阅读9分钟

写在前面

伴随着移动设备的发展和普及,随之而来的是蹑影追风的网速,这些为越来越丰富的视觉体验和人性化的交互提供了必要的条件。资深产品、设计也不会放弃展示自己独特匠心的机会,所以奋战在一线的前端开发们需要解决的第一个问题----页面适配。

一千个用户看到一千个哈姆雷特的情况是绝对不允许出现的(最多500个, 哈哈哈)。优雅的方式应该是:展现给用户的页面不会因手机品牌、型号、屏幕不同,呈现出巨大的差异。最终呈现给用户的视觉效果应该是相近的。适配可能是应对pc和移动端的,也可能是针对不用分辨率的还可能是。。。(省略1w字)。

作为一名前端开发者,页面适配这个问题曾在一段时间内困扰着我。对于适配我们需要做些什么,哪些做法是优雅的,合理的,高效的?

下面说一下个人的理解

预备知识

viewport视口

你有没有想过,<html>标签的width属性 width:20%是指相对那个容器宽度的20%?在调节浏览器的宽度的时候html的宽度是怎么变化的?

其实,viewport就是承载页面内容的容器,里面装载着html,<html>的宽度是相对viewport的 , viewport不是html的概念 不能通过CSS控制。

在PC浏览器中viewport严格等于浏览器的窗口宽高。注意缩放操作不会影响viewport,因为浏览器的窗口没有改变,改变的只是网页的宽高。

但是在移动设备上有点特殊。因为在设计时,移动设备相对桌面浏览器来说宽度太窄了,为了方便CSS布局就提出了两种viewport----visualviewportlayoutviewport,关于这两种viewport的理解可以看Stack Overflow上的这篇文章,看完真的有种豁然开朗的感觉, 如果坚持不看 接下来要说的可能不是能很好理解。 如果坚持不看 接下来要说的可能不是能很好理解。 如果坚持不看 接下来要说的可能不是能很好理解。

需要知道的是这两种viewport的区别和联系。对于移动设备来说,visualviewport就是你通过小窗看到的内容,也就是当前屏幕上显示的部分,用户滚动缩放浏览器可以改变visualviewport,但是并不能改变小窗后面的大图layoutviewport。不幸的是 CSS基本是相对layoutviewport来定义的。所以适配中要做的就是中和visualviewport和layoutviewport带来影响

举一个默认情况下的栗子说明:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <!--<meta name="viewport"-->
          <!--content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">-->
    <title>Viewport</title>
</head>
<body>
<p>this is hasn't viewport</p>
</body>
</html>

可以在移动设备上查看 是否设置viewport对显示效果的影响, 加深对viewport的理解

物理像素

就是设备屏幕上最小的物理部件,每个物理像素可以设置自己的颜色和亮度。

CSS像素

这是一个抽象的单位,用来在浏览器中精准表示内容大小的属性,它还有一个更有逼格的名字----设备无关的像素(device-independent pixel)简称DIPs, 这个就是.css中所用到的像素

这里需要知道的是两个概念的联系:开发中接触的1倍、2倍、3倍屏指的就是一个css像素由多少个物理像素构成,多倍屏是以更多物理像素来展现1个css像素的,更加精细化,显示效果更棒。

设备独立像素(density-independent pixel)

表示设备可供程序使用的逻辑像素,最后由系统转化成对应的物理像素。设置CSS像素时使用的就是这种逻辑像素

设备像素比dpr(device pixel ratio)

这是一个很重要的概念,它定义了设备物理像素和设备独立像素的对应关系。

公式:

设备像素比 = 物理像素 / 设备独立像素

这个概念就是移动设备提出的。iPhone4开始,Apple将iPhone4的分辨率提高了一倍,但是物理尺寸没有发生变化,如果还是按照已有的方式布局,将会留出大量空白,显示效果可想而知。设备像素与设备独立像素的比例是 1 : 1的时候没有人关心这个概念,但是现在不同了,有了Apple的加入,dpr要多奇葩有多奇葩,这个概念也就显得十分重要了。

屏幕像素密度PPI(pixel per inch)

可以理解为每英寸有多少像素。屏幕像素密度与屏幕尺寸和屏幕分辨率有关,在单一变化条件下,屏幕尺寸越小、分辨率越高,像素密度越大,反之越小。

rem,em

rem: Relative to font-size of the root element

em: Relative to the font-size of the element

一个是相对根节点一个是相对父节点的没啥好说的

在真正的理解了这些概念以后,适配就显得不那么难了

好了概念介绍完了,我们偷盗一张图来轻松一下

以最常见的设计稿iPhone6为栗子,设备独立像素为375 * 667, dpr为2,物理像素为750 * 1334。 元素CSS样式:

width: 2px;
height: 2px;

如图所示在不同的屏幕上,CSS像素所呈现的物理尺寸是一致的,而不同的是CSS像素所对应的物理像素数不一致。在普通屏幕下1个CSS像素对应1个物理像素,而在Retina屏幕下,1个CSS像素对应的是2个物理像素。

实操

随着“杂屏”时代的到来,如果你还只是用@media的方式 简单粗暴的解决问题,我佩服你是个汉子。

设计稿

首先要提到的是设计稿,这个是程序员需要参考的一手资料,也是最终实现效果的参考,在开发中扮演着十分重要的角色,请重视请重视请重视

流程

  • 选择一种尺寸作为设计和开发基准(一般就是iPhone6按物理像素标注的版本)
  • 定义一套适配规则,自动适配剩下尺寸
  • 特殊适配效果给出设计效果

这里要为广大开发说一句,一个页面如果同时用在pc和移动端上,最好最讲究的做法是出两套设计。因为一套设计走天下的背后是开发可能要做很多妥协甚至是黑科技,效果并没有想象的那么好。所以为了真正上节约大家的时间、少点扯皮的时间,请不要再说看着适配或者是“口口相传”式的需求,然后反馈适配不完美。

实现

1.设置理想条件viewport

<meta name="viewport" content="width=width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">

设置viewport(禁止用户缩放),让layoutviewport与visualviewport一样大(html宽度==屏幕宽度),至于1个CSS像素可以转化成多少物理像素由设备决定

2.动态设置viewport缩放

根据dpr不同,设置scale为dpr倒数。 设置scale目的是让多倍屏1个CSS像素由1物理像素显示,提供更加精细化控制。回想一下是否遇到过自己画的1px线比设计稿上的宽,因为你在css文件中设置的1px最后转换为物理像素的时候不一定是1px。

这里可能会有个疑问,既然步骤1已经基本满足适配,为什么还要动态设置viewport缩放?

原因:

还是拿iPhone6说,viewport设置缩放为1的理想viewport,html宽度为375,显示的也正好是屏幕 的宽度,这时候1px的CSS像素占用的2个物理像素,这样怎么画出1物理像素的线?啊?

但是在设置缩放为0.5、html宽度为750,缩放后显示刚好为屏幕宽度375,而总的CSS像素其实是750,与设备像素一致,这样1pxCSS像素占用的物理像素也是1,完美

3.css单位转换及适配

设置layoutviewport.widht ==visualviewportscale.width、scale = 1/dpr后,会直接影响css像素最终显示效果(影响方式点击查看)。

在iPhone6上,缩放为0.5,2 CSS px显示效果为1px(不理解多看几遍步骤1、2的解释),而在scale=1的设备,1 CSS px显示效果为1px,不同倍数屏幕之间显示效果存在差异。为了消除差异、获得一致的显示效果,需要px适配不同dpr的版本。为此rem登场,用来进行单位适配。

rem理论上可以是任何正整数,但是考虑到chrome默认root font-size为16并且最好font-size大于12px,这个rem可以适当大一点。以iPhone6为例,我习惯的做法设置rem=75px(将屏幕宽度等分为100份,同样符合vh,vw的概念,每10份为1rem)。

设计稿中标注的75px可以写为1rem

为了适配不同的屏幕需要动态设置根元素fontSize

下面展示下我平时使用的代码,好久之前写的 见谅见谅

/**
 * Created by gladyu on 18/7/3.
 */
var ua = navigator.userAgent.toLowerCase();
var is_devices = /iphone/.test(ua) || /ipad/.test(ua) || /android/i.test(ua)
$().ready = adapterRem();
function adapterRem() {
	//rem adapter
	var dpr, rem, scale;
	var docEl = document.documentElement;
	var fontEl = document.createElement('style');
	var metaEl = document.querySelector('meta[name="viewport"]');
	dpr = window.devicePixelRatio || 1;
	if (is_devices) {
		rem = parseFloat(docEl.clientWidth * dpr) / 7.5;
	} else {
		rem = 50;
	}
	scale = 1 / dpr;
	metaEl.setAttribute('content', 'width=' + dpr * docEl.clientWidth + ',initial-scale=' + scale +
		',maximum-scale=' + scale + ',minimum-scale=' + scale + ',user-scalable=no');

	docEl.setAttribute('data-dpr', dpr);
	docEl.firstElementChild.appendChild(fontEl);
	fontEl.innerHTML = "html{font-size:" + rem + "px!important;}";
	console.log(rem);
	window.dpr = dpr;
	window.rem = rem;
};

只是给大家提供一下思路,肯定比我写的好。步骤1,2,3需要在你引入css文件之前完成,这样css中的rem才是你指定的呀

4.rem计算

在面对设计稿中众多需要转换px到rem的操作,心算是肯定不行的,为了方便,可以借助sass写一个函数计算px转化为rem。这样在写样式时就不用手动计算,直接调用函数就好了。

@function px2em($px, $base-font-size: 16px) {
    @if (unitless($px)) {
        @warn "Assuming #{$px} to be in pixels, attempting to convert it into pixels for you";
        @return px2em($px + 0px); // That may fail.
    } @else if (unit($px) == em) {
        @return $px;
    }
    @return ($px / $base-font-size) * 1em;
}

font-size: px2rem(18px);

至此,你需要做的就是切页面完成需求了

文本字号不建议rem

绝大多数的字体文件都自带一些点阵尺寸,比较常见的通常是16px和24px,使用rem会出现很多奇葩尺寸。再就是我们希望在不同屏幕上字体大小看起来是相近的,在大屏上可以显示更多文字。

如此一来,rem并不适合用到文字上, 还是使用px作为单位。使用dpr属性来设置不同dpr下的文本字号大小。

自己的做法是根据dpr设置body的字体大小,其他的元素字体单位使用em,这样只需要改变body的fontSize其他的就跟着改变了。

这是我在不引入框架时解决适配问题的方式,希望可以帮到你 如果你还有更好的方案 欢迎交流

转载必须标明出处,谢谢。文章有疏漏浅薄之处,请各位大神斧正