移动端适配⽅案指南

知识点

像素单位有设备像素、逻辑像素、CSS 像素 3 种

  • 设备像素(device pixels)也叫物理像素,显示器上的真实像素,每个像素的⼤⼩是屏幕的固有属性, 出⼚后不会再改变。
  • 设备分辨率描述的是显示器的宽和⾼分别是多少个设备像素,例如 1920 * 1080。
  • 设备像素和设备分辨率是由操作系统来管理,浏览器不必知道设备分辨率,根据逻辑分别率来计算。
  • 设备独⽴像素(device independent pixels)是操作系统定义的⼀种像素单位,应⽤程序将设备独⽴像素告诉操作系统,再将设备独⽴像素转化为设备像素,从⽽控制屏幕上真正的物理像素点。

为什么需在应⽤程序与设备像素之间定义这么⼀种单位呢?为什么不应该直接使⽤设备像素?

  • 例如原先在 1280×720 设备分辨率显示⾼度为 12 个设备像素的字体,现在放到设备分辨率为 2560 ×1440的显示屏中,要想得到原先的⼤⼩则需要 24 个设备像素
  • 因此操作系统定义了⼀个单位:设备独⽴像素,对于那些像素密度⾼的屏幕,将多个设备像素划分为⼀个逻辑像素。
  • 对于上⾯的例⼦:操作系统会将⼀个逻辑像素定义为 2*2 个 真实像素,从⽽设备独⽴像素尺⼨不需要改变,不管在新、旧设备上,显示的尺⼨⼤致相同。

设备独⽴像素与设备像素之间的⽐例是多少,显示器⼚商和操作系统⼚商会通过调查研究来得出最利于观看的⽐例。普遍规律是屏幕的像素密度越⾼,就需要更多的设备像素来显示⼀个设备独⽴像素。


CSS 像素

在 CSS 中使⽤的 px 都是指 css 像素: 当缩放⻚⾯时,元素的 css 像素数量不会改变,改变的只是每个 css 像素的⼤⼩。

  • 比如缩放 200% 以后,宽度依然是 128 个 css 像素,只不过每个 css 像素的宽度和⾼度变为原来的两倍。如果原本元素宽度为 128 个设备独⽴像素,那么缩放 200% 以后元素宽度为 256 个设备独⽴像素。

css 像素与设备独⽴像素的关系

  • 缩放⽐例就是 css 像素边⻓ / 设备独⽴像素边⻓;
  • 在缩放⽐例为 100% 的情况下,1 个 css 像素⼤⼩等于 1 个设备独⽴像素;
  • 在缩放⽐例为 200% 的情况下,1 个 css 像素⼤⼩等于 (2 * 2) 个设备独⽴像素;

css 像素与设备像素的关系

  • window.devicePixelRatio 设备像素⽐ = 设备像素的数量 /CSS 像素的数量,等价于 CSS 像素边⻓ / 设备像素边⻓。
  • 如 devicePixelRatio = 2,相同⻓度的直线上,设备像素的数量是 CSS 像素数量的 2 倍,因此 CSS像素的边⻓是设备像素的 2 倍。缩放会导致 CSS 像素边⻓的改变,从⽽导致 window.devicePixelRatio 的改变!

响应式设计 - viewport

viewport 表示浏览器的可视区域,也就是浏览器中⽤来显示⽹⻚部分区域。

layout viewport

  • ⽹⻚布局的区域,html 元素⽗容器,只要不在 css 中修改 元素的宽度, 元素的宽度就会撑满 layout viewport
    的宽度。
  • 很多时候浏览器窗⼝没有办法显示出 layout viewport 的全貌,这个时候滚动条就出现了,需要通过滚动条来浏览 layout viewport 其他的部分。
  • layout viewport ⽤ css 像素来衡量尺⼨,在缩放、调整浏览器窗⼝的时候不会改变。缩放、调整浏览器窗⼝改变的只是 visual viewport。
  • 在桌⾯浏览器中,缩放 100% 的时候,Layout Viewport 宽度等于内容窗⼝的宽度。(你⼏乎不会在电脑上⻅
    过横向滚动条,除⾮你调整缩放)
  • 在移动端,缩放为 100% 的时候,Layout Viewport 不⼀定等于内容窗⼝的⼤⼩。

在这里插入图片描述

visual viewport

  • 视觉视⼝,显示在屏幕上的⽹⻚区域,它往往只显示 layout viewport 的⼀部分。
  • visual viewport 如⼀台摄像机,layout viewport 就像⼀张纸,摄像机对准纸的哪个部分,你就能看⻅哪个,你可以改变摄像机的拍摄区域⼤⼩(调整浏览器窗⼝⼤⼩),也可以调整摄像机的距离(调整缩放⽐例),这些⽅法都可以改变 visual viewport,但是 layout viewport 始终不变。

在这里插入图片描述
ideal viewport

  • ideal viewport 为理想视⼝,不同的设备有⾃⼰不同的 ideal viewport
  • ideal viewport 的宽度等于移动设备的屏幕宽度,最适合移动设备的 viewport。只要在 css 中把某⼀元素的宽度设为 ideal viewport 的宽度 (单位⽤ px),那么这个元素的宽度就是设备屏幕的宽度了,也就是宽度为 100% 的效果。 ideal viewport
    的意义在于,⽆论在何种分辨率的屏幕下,那些针对 ideal viewport ⽽设计的⽹站,不需要⽤户⼿动缩放,也
    不需要出现横向滚动条,都可以完美的呈现给⽤户。

利⽤ meta 标签对 viewport 进⾏控制

  • 移动设备默认的 viewport 是 layout viewport,也就是那个⽐屏幕要宽的 viewport。
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">

该 meta 标签的作⽤是让当前 viewport 的宽度等于设备的宽度,同时不允许⽤户⼿动缩放。如果你不这样
设定,会使⽤那个⽐屏幕宽的默认 viewport(layout viewport),也就是说会出现横向滚动条。

在这里插入图片描述


⽅案一、@media

1.1、使⽤ css 的媒体查询 @media

  • 基于 css 的媒体查询属性 @media 分别为不同屏幕尺⼨的移动设备编写不同尺⼨的 css 属性,虽然此⽅法能在⼀定程度上解决移动设备适配的问题,但我们也可以看出其存在以下问题,所以其已⼏乎 被历史潮流淘汰。
  • ⻚⾯上所有的元素都得在不同的 @media 中定义⼀遍不同的尺⼨,这个代价有点⾼;
  • 如果再多⼀种屏幕尺⼨,就得多写⼀个 @media 查询块;

1.2、使⽤ rem 单位

  • rem(font size of the root element)是指相对于根元素的字体⼤⼩的单位
  • 如果我们设置 html 的 font-size为 16px,则如果需要设置元素字体⼤⼩为 16px,则写为 1rem。
  • 但其必须借助 @media 属性来为不同⼤⼩的设备设置不同的 font-size,相对上⼀种⽅案减少重复编写相同属性的代价
  • 所有涉及到使⽤ rem 的地⽅,全部都需要调⽤⽅法 calc()
@media only screen and (min-width: 375px) {
	 html {
	 	font-size : 375px;
	 }
}
@media only screen and (min-width: 360px) {
	 html {
	 	font-size : 360px;
	 }
}
@media only screen and (min-width: 320px) {
	 html {
	 	font-size : 320px;
	 }
}
@function calc($val){
	@return $val / 1080;
}
.logo{
	width : calc(180rem);
}

方案二、flexible

2.1、flexible 适配⽅案

在 rem ⽅案上进⾏改进,我们可以使⽤ js 动态来设置根字体

2.2、 使⽤ rem 模拟 vw 特性适配多种屏幕尺⼨

// set 1rem = viewWidth / 10
function setRemUnit () {
	 var rem = docEl.clientWidth / 10
	 docEl.style.fontSize = rem + 'px'
}
setRemUnit();

上⾯的代码中,将 html 节点的 font-size 设置为⻚⾯ clientWidth(布局视⼝) 的 1/10,即 1rem 就等于⻚⾯布局视⼝的 1/10,这就意味着我们后⾯使⽤的 rem 都是按照⻚⾯⽐例来计算的。

2.3、控制 viewport 的 width 和 scale 值适配⾼倍屏显示

  • 设置 viewport 的 width 为 device-width,改变浏览器viewport(布局视⼝和视觉视⼝)的默认宽度为理想视⼝宽度,从⽽使得⽤户可以在理想视⼝内看到完整的布局视⼝的内容。
  • 等⽐设置 viewport 的 initial-scale、maximum-scale、minimum-scale 的值,从⽽实现 1 物理像素 = 1 css像素,以适配⾼倍屏的显示效果(就是在这个地⽅规避了⼤家熟知的 “1px 问题”)
var metaEL= doc.querySelector('meta[name="viewport"]');
var dpr = window.devicePixelRatio;
var scale = 1 / dpr
metaEl.setAttribute('content', 'width=device-width, initial-scale=' + scale + ', maximum-scale=' + scale)

2.4、flexible 的缺陷

  • 不可否认 flexible 在兼容性不友好,但是该⽅案⾃身是存在问题。
  • 由于其缩放的缘故,video 标签的视频频播放器的样式在不同 dpr 的设备上展示差异很⼤
  • 即:⼀律按 dpr = 1 处理;
if (isIPhone) {
    // iOS下,对于2和3的屏,⽤2倍的⽅案,其余的⽤1倍⽅案
    if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
        dpr = 3
    } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)) {
        dpr = 2
    } else {
        dpr = 1
    }
} else {
    // 其他设备下,仍旧使⽤1倍的⽅案
    dpr = 1
}

不再兼容 @media 的响应式布局,因为 @media 语法中涉及到的尺⼨查询语句,查询的尺⼨依据是当前设备的物理像素,和 flexible 的布局理论(即针对不同 dpr 设备等⽐缩放视⼝的 scale 值,从⽽同时改变布局视⼝和视觉视⼝⼤⼩)相悖,因此响应式布局在 “等⽐缩放视⼝⼤⼩” 的情境下是⽆法正常
⼯作的;

由于 viewport 单位得到众多浏览器的兼容,lib-flexible 这个过渡⽅案已经可以放弃使⽤,不管是现在的版本还是以前的版本,都存有⼀定的问题

方案三、viewport

由于 viewport 单位得到众多浏览器的兼容,所以⽬前基于 viewport 的移动端适配⽅案被各⼤⼚团队所采⽤。
vw 作为布局单位,从底层根本上解决了不同尺⼨屏幕的适配问题,因为每个屏幕的百分⽐是固定的、可预测、可控制的。

3.1、viewport 相关概念如下:

  • vw:是 viewport’s width 的简写,1vw 等于 window.innerWidth 的 1%;
  • vh:和 vw 类似,是 viewport’s height 的简写,1vh 等于 window.innerHeihgt 的 1%;
  • vmin:vmin 的值是当前 vw 和 vh 中较⼩的值;
  • vmax:vmax 的值是当前 vw 和 vh 中较⼤的值;

假设我们拿到的视觉稿宽度为 750px,视觉稿中某个字体⼤⼩为 75px,则我们的 css 属性只要如下这么写,
不需要额外的去⽤ js 进⾏设置,也不需要去缩放屏幕等;

.logo {
	font-size: 10vw; // 1vw = 750px * 1% = 7.5px
}

3.2、设置 meta 标签

  • 在 html 头部设置 mata 标签如下所示,让当前 viewport 的宽度等于设备的宽度,同时不允许⽤户⼿动缩放。
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">

3.3、px ⾃动转换为 vw

  • 设计师⼀般给宽度⼤⼩为 375px 或 750px 的视觉稿,需要将对应的元素⼤⼩单位 px转换为 vw 单位
  • 社区提供了 postcss-px-to-viewport 插件,来将 px ⾃动转换为 vw,相关配置步骤如下:
npm install postcss-px-to-viewport --save-dev

webpack 相关配置如下
其中 propList 属性配置了 font-size 不进⾏转换 vw,也就是说在不同⼿机屏幕尺⼨下的字体⼤⼩是⼀样的。
其中 font-size 是否需要根据屏幕⼤⼩做适配,或者怎么做,⼀直是个争论不休的话题;

module.exports = {
    plugins: {
        // ...
        'postcss-px-to-viewport': {
            // options
            unitToConvert: 'px', // 需要转换的单位,默认为"px"
            viewportWidth: 750, // 设计稿的视窗宽度
            unitPrecision: 5, // 单位转换后保留的精度
            propList: ['*', '!font-size'], // 能转化为 vw 的属性列表
            viewportUnit: 'vw', // 希望使⽤的视窗单位
            fontViewportUnit: 'vw', // 字体使⽤的视窗单位
            selectorBlackList: [], // 需要忽略的 CSS 选择器,不会转为视窗单位,使⽤原有的 px 等单位
            minPixelValue: 1, // 设置最⼩的转换数值,如果为 1 的话,只有⼤于 1 的值会被转换
            mediaQuery: false, // 媒体查询⾥的单位是否需要转换单位
            replace: true, // 是否直接更换属性值,⽽不添加备⽤属性
            exclude: undefined, // 忽略某些⽂件夹下的⽂件或特定⽂件,例如 'node_modules' 下的⽂件
            include: /\/src\//, // 如果设置了include,那将只有匹配到的⽂件才会被转换
            landscape: false, // 是否添加根据 landscapeWidth ⽣成的媒体查询条件
            landscapeUnit: 'vw', // 横屏时使⽤的单位
            landscapeWidth: 1125 // 横屏时使⽤的视窗宽度
        }
    }
}

css 编码:

.hello {
 color: #333;
 font-size: 28px;
}

3.3、标注不需要转换的属性

如果要求某⼀场景不做⾃适配需为固定的宽⾼或⼤⼩,需要利⽤插件的 Ignoring 特性,对不需要转换的 css 属性进⾏标注

/* example input: */
.class {
 /* px-to-viewport-ignore-next */ 下⼀⾏不进⾏转换
 width: 10px;
 padding: 10px;
 height: 10px; /* px-to-viewport-ignore */ 当前⾏不进⾏转换
}
/* example output: */
.class {
 width: 10px; 
 padding: 3.125vw;
 height: 10px;
}

3.4、Retina 屏预留坑位

  • 考虑 Retina 屏场景,可能对图⽚的⾼清程度、1px 等场景有需求,所以我们预留判断 Retina 屏坑位
  • 在⼊⼝的 html ⻚⾯进⾏ dpr 判断,以及 data-dpr 的设置
  • 在项⽬的 css ⽂件中就可以根据 data-dpr 的值根据不同的 dpr 写不同的样式类;
const dpr = devicePixelRatio >= 3? 3: devicePixelRatio >= 2? 2: 1;
document.documentElement.setAttribute('data-dpr', dpr);
[data-dpr="1"] .hello {
 background-image: url(image@1x.jpg);
[data-dpr="2"] .hello {
 background-image: url(image@2x.jpg);
}
[data-dpr="3"] .hello {
 background-image: url(image@3x.jpg);
}

特定场景

(1)⾏内样式的场景

  • ⾏内样式的代码, postcss-px-to-viewport 插件⽆法进⾏转换,需要⼿动计算好 vw;
  • 通过添加、修改、删除 className 的⽅式进⾏处理此类场景,不直接操作⾏内样式,这更符合将js 和 css 隔离开的更佳实践。

(2) retina 屏 1px 的问题

  • retina 屏的 1px 线会显得⽐较粗,设计美感⽋缺
  • 在视觉设计师眼⾥的 1px 是指设备像素 1px
  • 直接写 css 的⼤⼩ 1px,那在 dpr = 2 时,则等于 2px,设备像素,dpr = 3 时,等于 3px 设备像素

(2.1)transform: scale(0.5)

  • 进⾏ X、Y 轴的缩放
  • 优点是编写简单,但是如果实现上下左右四条边框会⽐较难,并且如果有嵌套存在的话,会对包含的元素产⽣影响,所以结合 :before 和 :after 来使⽤。
.class1 {
 height: 1px; 
 transform: scaleY(0.5);
}

(2.2)transform: scale(0.5) + :before / :after (推荐)

  • 此种⽅式能解决例如 标签上下左右边框 1px 的场景,以及有嵌套元素存在的场景
.calss1 {
 position: relative;
 &::after {
 content:"";
 position: absolute;
 bottom:0px;
 left:0px;
 right:0px;
 border-top:1px solid #666;
 transform: scaleY(0.5);
 }
}

(2.3)box-shadow

  • 利⽤ css 对阴影处理来模拟边框,示例如下所示,底部⼀条线,缺点是存在阴影
 .class1 {
 box-shadow: 0 1px 1px -1px rgba(0, 0, 0, 0.5);
 }

(3)图⽚⾼清的问题

  • 适⽤普通屏的图⽚在 retina 屏中,图⽚展示就会显得模糊;
  • 适⽤ retina 屏的图⽚在普通屏中,图⽚展示就会缺少⾊差、没有锐利度,并且浪费带宽;
  • 对性能、美观要求很⾼的场景,需要根据 dpr 区分使⽤对应的图⽚

相关 css 写法如下:

[data-dpr="1"] .hello {
 background-image: url(image@1x.jpg);
[data-dpr="2"] .hello {
 background-image: url(image@2x.jpg);
}
 
[data-dpr="3"] .hello {
 background-image: url(image@3x.jpg);
}

iPhoneX 适配⽅案

  • iPhoneX 取消了物理按键,改成底部⼩⿊条,这⼀改动导致⽹⻚出现了⽐较尴尬的屏幕适配问题
  • 需要关注底部与⼩⿊条的适配问题(即常⻅的吸底导航、返回顶部等各种相对底部 fixed 定位的元素)。

(1.1)安全区域

  • 安全区域指的是⼀个可视窗⼝范围,处于安全区域的内容不受圆⻆(corners)、⻬刘海(sensor housing)、⼩⿊条(Home Indicator)影响

在这里插入图片描述
也就是说,我们要做好适配,必须保证⻚⾯可视、可操作区域是在安全区域内

(1.2)viewport-fit

  • iOS11 新增特性,苹果公司为了适配 iPhoneX 对现有 viewport meta 标签的⼀个扩展
  • ⽤于设置⽹⻚在可视窗⼝的布局⽅式,可设置三个值。
    • contain: 可视窗⼝完全包含⽹⻚内容
    • cover:⽹⻚内容完全覆盖可视窗⼝
    • auto:默认值,跟 contain 表现⼀致

需要注意:⽹⻚默认不添加扩展的表现是 viewport-fit=contain,需要适配 iPhoneX 必须设置 viewport-fit=cover

(1.3)、env() 和 constant()

iOS11 新增特性,Webkit 的⼀个 CSS 函数,⽤于设定安全区域与边界的距离,有四个预定义的变量

  • safe-area-inset-left:安全区域距离左边边界距离
  • safe-area-inset-right:安全区域距离右边边界距离
  • safe-area-inset-top:安全区域距离顶部边界距离
  • safe-area-inset-bottom:安全区域距离底部边界距离

这⾥我们只需要关注 safe-area-inset-bottom 这个变量,因为它对应的就是⼩⿊条的⾼度(横竖屏时值不⼀样)

注意:当 viewport-fit=contain 时 env() 是不起作⽤的,必须要配合 viewport-fit=cover 使⽤。对于不⽀持env() 的浏览器,浏览器将会忽略它。

需要注意的是之前使⽤的 constant() 在 iOS11.2 之后就不能使⽤的,但我们还是需要做向后兼容

padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom); /* 兼容 iOS >= 11.2 */

注意:env() 跟 constant() 需要同时存在,⽽且顺序不能换

(2.1)设置⽹⻚在可视窗⼝的布局⽅式

新增 viweport-fit 属性,使得⻚⾯内容完全覆盖整个窗⼝,前⾯也有提到过,只有设置了 viewport-fit=cover,才能使⽤ env()

<meta name="viewport" content="width=device-width, viewport-fit=cover">

(2.2)fixed 完全吸底元素场景的适配

可以通过加内边距 padding 扩展⾼度

{
 padding-bottom: constant(safe-area-inset-bottom);
 padding-bottom: env(safe-area-inset-bottom);
}

或者通过计算函数 calc 覆盖原来⾼度

{
 height: calc(60px(假设值) + constant(safe-area-inset-bottom));
 height: calc(60px(假设值) + env(safe-area-inset-bottom));
}

注意,这个⽅案需要吸底条必须是有背景⾊的,因为扩展的部分背景是跟随外容器的,否则出现镂空情况。

还有⼀种⽅案就是,可以通过新增⼀个新的元素(空的颜⾊块,主要⽤于⼩⿊条⾼度的占位),然后吸底元素
可以不改变⾼度只需要调整位置

{
 margin-bottom: constant(safe-area-inset-bottom);
 margin-bottom: env(safe-area-inset-bottom);
}

空的颜⾊块:

{
 position: fixed;
 bottom: 0;
 width: 100%;
 height: constant(safe-area-inset-bottom);
 height: env(safe-area-inset-bottom);
 background-color: #fff;
}

(2.3)fixed ⾮完全吸底元素场景的适配

位置需要对应向上调整,可以仅通过下外边距 margin-bottom 来处理

{
 margin-bottom: constant(safe-area-inset-bottom);
 margin-bottom: env(safe-area-inset-bottom);
}

你也可以通过计算函数 calc 覆盖原来 bottom 值:

{
 bottom: calc(50px(假设值) + constant(safe-area-inset-bottom));
 bottom: calc(50px(假设值) + env(safe-area-inset-bottom));
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值