移动端适配

469 阅读9分钟

移动端适配

移动端适配主要指两个方面

  • 自适应:根据不同的设备屏幕大小来适配元素大小
  • 响应式:当屏幕尺寸发生变化时可以重新计算元素大小

viewport

我们先看看viewport的一些设置,带着疑问我们往下分析

<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover">

在了解viewport的时候我们首先要理解三个概念

  • 布局视口
  • 视觉视口
  • 理想视口
布局视口

image.png

image.png

这两个盒子都是100px*100px的,但是在移动端尺寸就显得更小,这是为什么呢

image.png

我们在viewport没有设置参数的时候会发现,移动端会按照默认宽度980px来布局,为了完整的展示整个页面,会适当的等比例进行缩放,所以移动端的显示尺寸就和实际尺寸不一致

我们相对于980px布局的这个视口就称为布局视口

视觉视口

image.png

视觉视口实际上就是我们屏幕的可视区域,比如上面的设备尺寸,如果按照980px布局,势必造成展示不全的情况

理想视口

如果我们开发的时候都按照980px开发,然后进行缩放来适应设备,实际上是很不太利于我们开发的,所以就引申出了理想视口这个说法,实际上就是 视觉视口宽度=布局视口

也就是

<!-- width: 设置布局视口的宽度  device-width是获取用户设备的宽度 -->
<meta name="viewport" content="width=device-width">
可能的附加值描述
width一个正整数,或者字符串device-width定义 viewport 的宽度。
height一个正整数,或者字符串 device-height定义 viewport 的高度。未被任何浏览器使用
initial-scale一个0.0和10.0之间的正数定义设备宽度与 viewport 大小之间的缩放比例
maximum-scale一个0.0和10.0之间的正数定义缩放的最大值,必须大于等于minimum-scale.否则表现将不可预测。
minimum-scale一个0.0和10.0之间的正数定义缩放的最小值,必须小于等于 maximum-scale.否则表现将不可预测。
user-scalableyes 或者 no默认为 yes,如果设置为 no,将无法缩放当前页面。浏览器可以忽略此规则

现在我们实现了第一步,就是防止浏览器的自动缩放,导致元素变化的不可控,下一步就是实现响应式

响应式

移动端的屏幕尺寸非常多,我们需要在不同的尺寸下,元素的大小,margin,padding等也要相应的发生变化,比如我们在375px基础下写的100px盒子

  • 在375下为100*100
  • 在414下为110.4*110.4
  • 在320下为85.3*85.3

适配方案有很多,比如百分比,rem+动态html font-size设置,vw+vh等等

我们今天主要讨论两种方案

rem+动态html font-size设置

rem单位是相对于html元素的font-size来设置的,那么如果我们需要在不同的屏幕下有不同的尺寸,可以动态的修改html的font-size尺寸。在开发中,我们只需要考虑两个问题:

  • 针对不同的屏幕,设置html不同的font-size;
  • 将原来要设置的尺寸,转化成rem单位;

第一种:媒体查询

@media screen and (min-width:320px) {html{font-size: 20px;}}
@media screen and (min-width:375px) {html{font-size: 24px;}}
@media screen and (min-width:414px) {html{font-size: 28px;}} 
@media screen and (min-width:480px) {html{font-size: 32px;}} 
.box {width: 5rem; height: 5rem; background: #8ec04c;}

这里有些缺点就是,这也是现在不常见的原因

  • 我们需要针对不同的屏编写大量的媒体查询
  • 如果动态改变尺寸,不会实时的进行更新

第二种:用js动态获取设备宽度

  • 根据html的宽度计算出font-size的大小,并且设置到html上
  • 监听页面的实时改变,并且重新设置font-size的大小到html上
const htmlEl = document.documentElement
function setRemUnit() {
    const htmlWidth = htmlEl.clientWidth
    htmlFontSize = htmlWidth / 37.5
    htmlEl.style.fontSize = htmlFontSize + "px"
}
// 保证第一次进来时, 可以设置一次font-size
setRemUnit()
// 当屏幕尺寸发生变化时, 实时来修改html的font-size
window.addEventListener("resize", setRemUnit)

htmlFontSize = htmlWidth / 37.5这一步很重要,视觉稿一般是375px或则是这个宽度的倍数,比如100px,换算之后我们直接得到10rem就好了,试想一下如果是htmlFontSize = htmlWidth / 100,那么换算可能就是灾难了,100/(375/100)

vw方案

这个是我们现在常用的方案,vw单位是相对于视口的。比如375px的屏幕就是1vw==3.75px。其实仔细想来可以发现,rem实际上也是基于vw思想的,rem是viewport的一个过渡方案,由于现在大部分浏览器对viewport兼容性较好,所以推荐使用vw,vh来做兼容性方案

image.png

vw对比rem的一些优点

  • 具备rem的所有功能
  • 不用去计算html的font-size大小,也不需要给html设置这样一个font-size
  • 因为不依赖font-size的尺寸,所以不用担心某些原因html的font-size尺寸被篡改,页面尺寸混乱
  • vw相比于rem更加语义化,1vw刚好是1/100的viewport的大小

那么现在就有一个问题了,使用vw就面临前面提到的那个换算兼容性问题,拿设计稿375px为例

  • 第一种,手动换算,每个尺寸都需要xx/3.75换算成rem
  • 第二种,lass/sass预处理,借用变量
@vwUnit:3.75;.pxToVw(@px) {result: 1vw * (@px / @vwUnit);}
.box {width: .pxToVw(100)[result]}
  • 第三种,postcss-px-to-viewport,这是工程化中借助webpack工具进行处理
"postcss-px-to-viewport": {
  unitToConvert: "px", // 要转化的单位
  viewportWidth: 375, // UI设计稿的宽度
  unitPrecision: 10, // 转换后的精度,即小数点位数
  viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
}

高清屏dpr

首先我们需要了解几个概念

  • css像素
  • 设备像素
  • 设备独立像素
  • dpr

css像素

适用于web编程,在 CSS 中以 px 为后缀,是一个长度单位,px是一个相对单位,相对的是设备像素,一般情况,页面缩放比为1,1个CSS像素等于1个设备独立像素,假设页面放大一倍,原来的 1px 的东西变成 2px,意味着1px长度就会占据更多的设备像素

设备像素

设备像素(device pixels),又称为物理像素,从屏幕在工厂生产出的那天起,它上面设备像素点就固定不变了

设备独立像素

这其实是一种概念,与设备无关的逻辑像素,代表可以通过程序控制使用的虚拟像素,CSS像素其实就是设备独立像素

为什么会出现这个概念是为了引出设备像素比dpr,比如如果没有这个概念我们应该怎么写样式呢,一个设备像素是300,一个是600,我们按照设备像素来布局的话,显然会出现一些问题,所以就出现了这个虚拟概念,比如这两个设备的独立像素都是300,而设备像素是600的机器,一个虚拟像素就由两个设备像素构成。至于 1 个虚拟像素被换算成几个物理像素,这个数值我们称之为设备像素比

dpr

dpr = 设备像素/设备独立像素,dpr不为1的设备就是高清屏,也就是我们平常说的2倍屏,3倍屏,根据下面的图我们知道,dpr越大的设备显示的内容就会越清晰,越细腻

image.png

1px和图片选择问题

讲上面dpr的原因就是为了了解一下1px和图片选择问题,我们经常会遇到设计稿(750)中有1px的线,图片会提供三个尺寸的情况

这里的1px实际就是设备像素的概念,而750可以理解成css像素为375px,dpr为2,这其实在设计中也是合理的,显然我们认为按照375的设计稿那条细线的css像素应该就是0.5px

遗憾的是在IOS8+,苹果系列都已经支持0.5px了,而IOS7及以下和Android部分系统里,0.5px将会被显示为0px,所以我们就要想一些兼容手段

  • 伪类+transform+媒体查询
.border-top-1px::after{
    ...
    height1px;
}   
@media (-webkit-min-device-pixel-ratio2) {
   .border-top-1px::after {
      transformscaleY(0.5);
   }
}
  • viewport + rem 同时通过设置对应viewportrem基准值,这种方式就可以像以前一样轻松愉快的写1px了。
<meta name="viewport" content="width=device-width,initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
var dpr = window.devicePixelRatio || 1;
var scale = 1 / dpr;
//下面是根据设备dpr设置viewport
viewport.setAttribute(
    "content", +
    "width=device-width," +
    "initial-scale=" +
    scale +
    ", maximum-scale=" +
    scale +
    ", minimum-scale=" +
    scale +
    ", user-scalable=no"
);
  • background-image 和 border-image准备合适的图片,比如一半有颜色,一半透明,修改起来麻烦

图片选择

image.png

一个位图像素是栅格图像(如:png, jpg, gif等)最小的数据单元,理论上,1个位图像素对应于1个物理像素,图片才能得到完美清晰的展示。

在普通屏幕下是没有问题的,但是在retina屏幕下就会出现位图像素点不够,就近取色,从而导致图片模糊的情况。

那如果普通屏幕下,用了两倍图片会怎么样呢,这就会出现一个物理像素点对应4个位图像素点,所以它的取色也只能通过一定的算法,肉眼看上去虽然图片不会模糊,但是会觉得图片缺少一些锐利度,或者是有点色差

image.png

一般的完整写法为了兼容各个多倍屏需要用媒体查询来兼容。一般来说dpr = 2为多,dpr = 1 为普通屏幕,dpr = 3占少数。所以我们至少做2套图片,一套是兼容dpr = 1的小图;一套是兼容dpr = 2的大图;dpr = 3的可以兼容到dpr = 2的图片中,虽然有点失色,但还是可以接受的。

.img {
    background-image: url(image.png)
}
@media (-webkit-min-device-pixel-ratio2) {
   .img {
        background-image: url(image@2x.png)
    }
}