移动端适配方案总结

397 阅读10分钟

背景

由于自己太久没开发移动端的页面,个人觉得移动端开发相比较PC端开发最大的不同,在于需要去适配各种尺寸的手机屏幕,尤其还有1px的问题。

因此需要再次收集一下,除了自己认知(rem适配)以外,是否还有移动端适配的方案吗?

问题

移动端存在几类问题,如下所列:

  • 1px显示过粗问题
  • 手机屏幕出现刘海屏、滴水屏等,如何适配
  • 如何在不同屏幕,显示正确高清图片
  • meta 的 viewport 值 能否用来适配
  • rem,vw,vh计算单位的区别
  • ...

概念

像素

像素是计算机屏幕上所能显示的最小单位。用来表示图像的单位。

按照我个人理解,把屏幕比做一张白纸,然后像素就是一个点,接着同一水平线上的点形成一条线,水平线和垂直线同时就形成一个画面。

然后对于我们前端开发而言,像素又需要分成几类:

  • 设备独立像素, 俗称DIP,你可以理解成我们平时用css像素 又等于 逻辑像素,简单说就是同一个尺寸的屏幕,设备独立像素是一样的,从而减少误解
  • 物理像素,其实就是我们真实肉眼可见的像素,物理像素 = 分辨率
  • 设备像素比DPR devicePixelRatio = 设备像素 / 设备独立像素,当物理像素和设备独立像素不一样的时候,这个时候就需要一个标准来做适配,利用DPR我们能将同样的画面适应不同的屏幕
  • 每英寸像素 ppi (pixel per inch),表示每英寸所包含的像素点数目,更确切的说法应该是像素密度。数值越高,说明屏幕能以更高密度显示图像

分辨率

分辨率指屏幕上像素的数目,一般用水平垂直,比如:屏幕分辨率为 800 600, 水平有800个像素点,垂直有600个像素

所以平时我们将2k、4k屏幕,通常指的是水平方向的存放像素超过2000或4000个。

视口(viewport)

视口代表当前可见的计算机图形区域。在 Web 浏览器术语中,通常与浏览器窗口相同,但不包括浏览器的 UI,菜单栏等——即指你正在浏览的文档的那一部分。 视口一般是指用户访问页面时,当前的可视区域范围。通过滚动条滑动,视口可以显示页面的其他部分。 通过 document.documentElement.clientWidth 或 window.innerWidth 可以获取视口宽度。

简单的说,视口就是浏览器肉眼可见的区域,是随时可变化,视口是一个概念,它又可以根据不同情况分为以下几种:

  • 布局视口,对于开发来说的一种视口概念,在移动端可以通过 <meta name="viewport" content="width=980, initial-scale=1.0"></meta>调整从而将打屏显示的内容完整缩小适配到移动端小屏
  • 视觉视口,是一种针对移动端屏幕提出来的概念,具体是指的屏幕的可见区域,当键盘弹起、浏览器工具栏隐藏等,视觉视口都会随之变化,而布局视口不会

布局视口(layout viewport)

innerHeight 和 innerWidth 所组成的区域通常被认为是布局视口(layout viewport)。浏览器的框架不被认为是视口的一部分。

在PC端的时候, 视口=布局视口=视觉视口。

在移动端的时候,布局视口 = 内容宽度,可以通过<meta name="viewport" content="width=980, initial-scale=1.0"></meta>调整,下面举几个例子(移动端为375*667):

  • <meta name="viewport" content="width=980, initial-scale=1.0"></meta>, 布局视口=window.innerWidth=980
  • <meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>, 布局视口=window.innerWidth=375

视觉视口(visual viewport)

视觉视口指当前浏览器中可见的部分,并且可以变化。当使用双指缩放,或键盘在手机上弹出的时候,或者之前隐藏的地址栏变得可见的时候,视觉视口缩小了,但是布局视口却保持不变。

相比较布局视口概念,视觉视口是由苹果 乔布斯提出,为了更好的在移动端展示web网页,视觉视口=屏幕的可见区域,下面通过几个例子去认知(移动端为375*667):

  • <meta name="viewport" content="width=980, initial-scale=1.0"></meta>,视觉视口=window.screen.width=375
  • <meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>,视觉视口=window.screen.width=375

viewport设置项

viewport的设置主要是在移动端配置视口大小,从width宽度,initial-scale缩放等设置属性,具体如下:

  • width:控制 viewport 的大小,可以给它指定一个值(正整数),或者是一个特殊的值(如:device-width 设备独立像素宽度,单位缩放为 1 时);
  • initial-scale:初始缩放比例,即当页面第一次加载时的缩放比例,为一个数字(可以带小数);
  • maximum-scale:允许用户缩放到的最大比例,为一个数字(可以带小数);
  • minimum-scale:允许用户缩放到的最小比例,为一个数字(可以带小数);
  • user-scalable:是否允许用户手动缩放,值为 "no"(不允许) 或 "yes"(允许);
  • height:与 width 相对应(很少使用)。

注意项

  • iframe的视口等于是其内部高度和宽度的大小。
  • SVG的视口即 SVG 图片的可视区域。

方案

这些兼容方案都是基于视口不缩放配置才能生效: <meta name="viewport" content="width=device-width, initial-scale=1.0"></meta>

目前市面主流的几种适配方案如下:

  • rem ,通过一个宽度尺寸作为统一的单位值,然后通过js计算出不同尺寸对比值,得到适配效果
  • vw ,一个浏览器支持的单位,利用 CSS 视窗的特性,总宽度为 100vw,每一份为一个单位 1vw,设置 1rem 单位为 10vw
  • px + calc + clamp,大漠在2021年提出,根据 CSS 的新特性:css变量、calc()函数、clamp()、@container函数实现

rem方案

rem是指的html元素的font-size的大小,如:html{font-size:50px}; 1rem=50px

实现原理

通过一个具体的例子,更好理解它的原理。比如你拿到一张设计稿为750px宽度的,里面有个长方形为100*200,这个时候需要在不同屏幕去做适配。

rem的解决方案思路为:

  • 将标准尺寸宽度 750/10= 75px,设置:html{font-size:75px} 1rem=75px
  • 将长方形的 100*200,100/75 * 200/75,设置为:.rectangle{width: 1.33rem;height:2.66rem}
  • 那么当屏幕的尺寸发生变话的时候, 变成从750减少为375,那么这个时候,设置: html{font-size:37.5px} 1rem=37.5px
  • 长方形的css设置无需变化,从而达到适配的效果

所以rem的解决方案就是在web应用在加载的时候,提前计算好rem单位,所以就可以完成适配。

目前主流的方案有:

源码实现:

h5-shipei.js

PS: 里面涉及到pageshow事件,基本上是因为移动端缓存了web页面,当浏览器历史记录前进或后退的是会触发,可以通过e.persisted判断是否从缓存获取

缺点:

  • 需要前置js才能实现,根据设备的视窗宽度进行计算,影响性能
  • 在 PC 端浏览破相,一般设置一个最大宽度

vw方案

是什么

vw是css的一种计算单位,定义:

1vw 等于1/100的视口宽度 (Viewport Width)

同理vh也是:

1vh 等于1/100的视口高度 (Viewport Height)

那么vmax,vmin,我在网上找到比较好解释如下:

vmin — vmin的值是当前vw和vh中较小的值。 vmax — vw和vh中较大的值。 在横竖屏的切换中,十分有用。

原理

了解到vw的概念,那么如何利用vw去解决移动端的适配呢?其实和rem方案是一样,具体代码可以如下:

/* rem 方案 */
html { font-size: width / 100}
div { width: 26.67rem }

/* vw 方案 */
div { width: 26.67vw }

已实现的框架方案如下:

postcss-px-to-viewport

利用打包编译过程,将设置好的px单位转换为vw或vh,具体使用可以看使用文档。

缺点:

  • 和rem一样,容易在pc端适配错误,可以用@media媒体查询去做样式兼容

后续添加

如果利用flex/grid布局,加上vw、vh单位,是否可以做到自适应布局呢?

答案是肯定,而且相对而言会比其他方案,会更加容易开发些,实现步骤如下:

  • 利用flex弹性布局,加上vw设置flex弹性盒子的宽度,从而做到不同宽度屏幕,是否换行或者垂直布局

后续可以专门写个文章用来描述flex布局的原理。

px + calc + clamp

是什么

calc() 此 CSS 函数允许在声明 CSS 属性值时执行一些计算。 支持 + , - , / ,* 等运算。

/* property: calc(expression) */
width: calc(100% - 80px);

clamp() 函数的作用是把一个值限制在一个上限和下限之间,当这个值超过最小值和最大值的范围时,在最小值和最大值之间选择一个值使用。它接收三个参数:最小值、首选值、最大值。

font-size: clamp(1rem, 2.5vw, 2rem); 
  • 2.5vw大于2rem,使用2rem
  • 2.5vw小于1rem,使用1rem
  • 其他使用2.5vw单位

原理

了解到上面css两个函数,如果通过只用px单位去做适配呢?原理步骤如下:

  • 假设我们拿到的设计稿750px宽度,那么这个时候有个长方体是200*100
  • 利用css变量,设置一个首先值,然后计算出去其他尺寸所需的最大值、最小值,设置clamp()
  • 同时利用calc()去计算偏差值

缺点:

  • calc 和clamp 函数在浏览器支持度还不够
  • 需要了解这套方案还需要较深的技术方案

1px高清显示问题

1像素问题: 1像素指在 Retina 屏显示 1单位物理像素

  • DPR = 1,此时 1 物理像素 等于 1 CSS 像素
  • DPR = 2,此时 1 物理像素等于 0.5 CSS 像素
  • border-width: 1px,这里的 1px 其实是 1 CSS 像素宽度,等于 2 物理像素,设计师其实想要的是 border-width: 0.5px DPR = 3,此时 1 物理像素等于 0.33 CSS 像素。设计师想要的是 border-width: 0.33px

解决方案:

  • 渐变实现 : background-image: linear-gradient(to top, ,,,)
  • 使用缩放实现:transform: scaleY(0.333)
  • 使用图片实现:base64
  • 使用 SVG 实现:嵌入 background url
  • border-image,低端机下支持度不好

以上方案都是基于媒体查询解决的

@media only screen and (-webkit-min-device-pixel-ratio: 2),
    only screen and (min-device-pixel-ratio: 2) {}
@media only screen and (-webkit-min-device-pixel-ratio: 3),
    only screen and (min-device-pixel-ratio: 3) {
        
}

媒体查询

媒体查询(Media queries)非常实用,尤其是当你想要根据设备的大致类型(如打印设备与带屏幕的设备)或者特定的特征和设备参数(例如屏幕分辨率和浏览器视窗宽度)来修改网站或应用程序时。

使用

一般有几种用法,如下所示:

  • 通过 @media@import at-rules 用CSS 装饰样式
  • media= 属性为<style>, <link>, <source>和其他HTML元素指定特定的媒体类型
  • 使用Window.matchMedia() MediaQueryList.addListener() 方法来测试和监控媒体状态

CSS装饰样式用法

@media screen, print { ... }

HTML元素引入

<link rel="stylesheet" src="styles.css" media="screen" />
<link rel="stylesheet" src="styles.css" media="print" />

js用法

let mql = window.matchMedia('(max-width: 600px)');
document.querySelector(".mq-value").innerText = mql.matches;

var mql = window.matchMedia('(max-width: 600px)');

function screenTest(e) {
  if (e.matches) {
    /* the viewport is 600 pixels wide or less */
    para.textContent = 'This is a narrow screen — less than 600px wide.';
    document.body.style.backgroundColor = 'red';
  } else {
    /* the viewport is more than than 600 pixels wide */
    para.textContent = 'This is a wide screen — more than 600px wide.';
    document.body.style.backgroundColor = 'blue';
  }
}

mql.addListener(screenTest);

图片高清问题

图片高清问题,一般是指的:不同 DPR 下图片的高清解决方案。

这种问题解决方案,通常是利用媒体查询+提供不同尺寸的图片去显示。

总结

移动端适配方案,结合开发中,大家都是直接写px,然后利用编译过程的进行转换,比如: px2rempx2vw

再就是排版问题,不同屏幕问题需要做自动排版优化方案,这个需要后面去研究一下《如何正确的使用 CSS Clamp 进行响应式排版》