0%

移动端适配

移动端适配基本概念

参考

视口 viewport

一个虚拟的区域, 就是设备上用来显示网页的那一块区域

  • 浏览器承载 viewport , viewport 承载网页, viewport 默认会对网页进行缩放来呈现完整内容
    • 可能造成元素太小看不清

布局适口

用来布局的区域

有自己的默认值,可以通过 width=device-width 或 initial-scale 来重新设置

当发生冲突时,取较大的值

  • PC 端
    • 布局视口默认和视觉视口等大
    • 进行网页放大时,布局视口变小;缩小时,布局视口变大(缩放时的是每个像素点的大小)
  • 移动端
    • 各个设备有自己固定的布局适口大小 (iphone 为 980px)
    • 进行缩放时,布局适口不会变化(物理像素值不会变化)

视觉适口

用户能看到的区域

默认情况下是等于设备宽的,只有设置了 initial-scale 才会改变大小

当布局视口的内容超出视觉视口时,才会出现滚动条

  • PC 端
    • 用户可任意缩放
  • 移动端
    • 通过 initial-scale 来设置缩放(相对于设备宽)

适配方案要求

  1. 网页宽度必须和浏览器保持一致 (防止出现水平滚动条)

  2. 默认显示的缩放比例和 PC 端保持一致

  3. 不允许用户自行缩放网页

1
2
3
4
5
6
7
8
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0,user-scalable=no">

<!--
device-width —— 视觉视口大小
width=device-width —— 改变的是布局视口大小
initial-scale —— 设备独立像素对视觉视口(布局视口)的比例
initial-scale=1.0 —— 同时改变布局视口和视觉视口大小都为设备像素的 1 倍
-->

理想适口

让 layout viewport (布局视口)的宽度等于设备宽度

1
<meta name="viewport" content="width=device-width" />

完美视口

同时不让系统对网页进行缩放

默认视觉视口宽度就是等于设备宽度的

设置 initial-scale 是同时在设置布局视口和视觉视口的宽度

当元素超出视觉视口宽度时,就会出现滚动条

1
<meta name="viewport" content="width=device-width,initial-scale=1.0" />

物理像素(屏幕的分辨率)

设备能控制显示的最小单元,可以把物理像素看成是对应的像素点

物理像素是一个抽象单位,我们不能说 1 个物理像素有多大,这还跟设备分辨率有关

  • eg 屏幕的分辨率 (1366 x 768 ect.) 可以理解为物理像素

设备分辨率 PPI

设备的分辨率指的是每英寸显示屏上像素点个数

计算公式为 PPI = 物理像素/物理尺寸

1
2
// iPhone6 的分辨率为 
326 = √(750^2 + 1334^2) / 4.7

设备独立像素 & css像素

设备独立像素(也叫密度无关像素),可以认为是计算机坐标系统中的一个点,这个点代表一个可以由程序使用并控制的虚拟像素

  • 我们写 CSS 时所用的像素,它是一个抽像的单位
  • css 像素是浏览器特有的概念

设备像素比 dpr

设备像素比 = 物理像素 / css 像素

  • 在普通屏幕下1个 CSS像素对应1个物理像素,而在Retina屏幕下,1个CSS像素对应的却是4个物理像素

dpr & DPI & PPI

dpr —— 设备像素比,物理像素/设备独立像素 = dpr, 一般以 Iphon6 的 dpr 为准 dpr = 2

PPI —— 一英寸显示屏上的像素点个数 (iPhone6 为 326 = √(750^2 + 1334^2) / 4.7)

DPI —— 最早指的是打印机在单位面积上打印的墨点数,墨点越多越清晰

位图像素

1 个位图像素是一个栅格图像上最小的数据单元

移动端适配方案

所谓的移动端适配,就是让页面在不同尺寸的移动设备上展现出理想的视觉效果
参考

viewport 适配

原理

视口默认会对网页内容进行缩放来呈现内容,利用 initial-scale 来设置布局视口和视觉视口的大小后,网页内容再进行缩放;同一个元素在不同设备上的 css 像素是一样的,但是缩放比是不一样的

  1. 根据设计稿标准(750px 宽度)开发页面,写完后页面及元素自动缩放,适配不同的设备
  • 在 initial-scale 上做文章,initial-scale = 设备的宽度 / 设计稿的宽度
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <head>
    <meta name="viewport" content="initial-scale=1.0">
    <script>
    // 设计稿尺寸
    const WIDTH = 750
    const mobileAdapter = () => {
    // 为了适配其他屏幕,需要动态的设置 initial-scale 的值
    let scale = screen.width / WIDTH
    let content = `initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}`
    let meta = document.querySelector('meta[name=viewport]')
    if (!meta) {
    meta = document.createElement('meta')
    meta.setAttribute('name', 'viewport')
    document.head.appendChild(meta)
    }
    meta.setAttribute('content',content)
    }
    mobileAdapter()
    // 屏幕翻转时再次执行
    window.onorientationchange = mobileAdapter
    </script>
    </head>

优点

  • 元素尺寸直接按设计稿来

缺点

  • 边线问题,不同尺寸下,边线的粗细是不一样的(等比缩放后),全部元素都是等比缩放,实际显示效果可能不太好

vw/wh(部分等比缩放)

原理

浏览器默认将设备宽度分为 100 份,对于不同的设备,每一份的宽度是不一样的,以 vw 为单位的同一个元素在不同设备上的 css 像素也是不一样的

  1. 开发者拿到设计稿(假设设计稿尺寸为750px,设计稿的元素标注是基于此宽度标注
  2. 开始开发,对设计稿的标注进行转换,把 px 换成 vw
    • 比如页面元素字体标注的大小是 28px,换成 vw 为 (100/750)*28 vw
  3. 对于需要等比缩放的元素,CSS 使用转换后的单位 vw
  4. 对于不需要缩放的元素,比如边框阴影,使用固定单位 px

为了开发方便,利用自定义属性,CSS变量

1
2
3
4
5
6
7
8
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<script>
const WIDTH = 750
//:root { --width: 0.133333 } 1像素等于多少 vw
document.documentElement.style.setProperty('--width', (100 / WIDTH))
</script>
</head>

注意此时,meta 里就不要去设置缩放了

业务代码里就可以写

1
2
3
p {
font-size: calc(28vw * var(--width));
}

实现了按需缩放

rem

原理

根据设备宽度给不同的设备设置不同的 rem(根元素字体大小),以 rem 为单位的同一个元素在不同设备上的 css 像素是不一样的

  1. 开发者拿到设计稿(假设设计稿尺寸为750px,设计稿的元素标是基于此宽度标注
  2. 开始开发,对设计稿的标注进行转换
  3. 对于需要等比缩放的元素,CSS 使用转换后的单位 rem
  4. 对于不需要缩放的元素,比如边框阴影,使用固定单位 px

根据不同屏幕宽度,设置 html 的 font-size 值

1
2
3
4
5
6
7
8
9
10
11
12
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<script>
// 设计稿尺寸
const WIDTH = 750
const setView = () => {
document.documentElement.style.fontSize = (100 * screen.width / WIDTH) + 'px'
}
window.onorientationchange = setView
setView()
</script>
</head>

对于需要等比缩放的元素,CSS 使用转换后的单位 rem

1
2
3
header {
font-size: .28rem;
}

对于不需要缩放的元素,比如边框阴影,使用固定单位px

1
2
3
4
header > span.active {
color: #fff;
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
}

⚠️ 以上的三种适配方案,都是等比缩放

  • 放到 ipad 上时(设计稿以手机屏幕设计的),页面元素会很大很丑
  • 有些场景下,并不需要页面整体缩放(viewport 自动处理的也很好了),所以有时只需要合理的布局即可

rem & less 适配方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// variables.less
@charset "UTF-8";

// 主流设备尺寸列表, 可根据需要增减
@adapterDeviceList: 750px, 720px, 640px, 540px, 480px, 424px, 414px, 400px, 384px, 375px, 360px, 320px;

// 设计稿尺寸, 可根据需要修改
@psdWidth: 750px;

// 预设基准值, 可根据需要修改
@baseRem: 100px; // 当基准值设为 100px 时, 对 750px 的设计稿, 只需对 设计稿尺寸/ 100 就转成了对应的rem 尺寸

// 设备种类长度
@len: length(@adapterDeviceList); // less 内置函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// mixins.less
// less没有循环语法
// 我们可以使用函数的迭代
// 根据数组长度停止当前循环
// 函数的执行有条件
// less 里 @index 是从 1 开始的
.adapter(@index) when (@index > 0) {
@media (min-width: extract(@adapterDeviceList, @index)) {
html {
font-size: @baseRem / @psdWidth * extract(@adapterDeviceList, @index);
}
};
.adapter(@index - 1);
}
1
2
// adapter.less
.adapter(@len);
1
2
3
4
// index.less
@import "variables";
@import "mixins";
@import "adapter";

弹性盒布局(合理布局)

1
<meta name="viewport" content="width=device-width,initial-scale=1.0">

使用 flex 布局

1
2
3
section {
display: flex;
}

对于多列的布局情形非常适合

百分比布局(宽度适配)

只做到了宽度适配

响应式布局(媒体查询)

核心还是利用 媒体查询

  • 如果是大项目,自己写响应式代码不现实,尽量利用 Bootstrap

总结

对于更复杂的场景,需要更灵活考虑,没有一种适配方式可以囊括所有场景

1px 细线边框

方式一—— rem + initial-scale

  • 所有元素先放大 dpr 倍,再缩放 1/dpr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
<script>
// 设计稿宽度
var WIDTH = 750
var dpr = window.devicePixelRatio
// 1 rem
document.documentElement.style.fontSize = (100 * screen.width / WIDTH) * dpr + 'px'

let scale = 1 / dpr
let content = `initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}`
let meta = document.querySelector('meta[name=viewport]')
meta.content = content
</script>
</head>
1
2
3
4
5
.box {
width: 1rem;
heiht: 1px;
background: black;
}

方式二 —— 伪元素 + transform

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
.border-1px-bot,
.border-1px-top,
.border-1px-left,
.border-1px-right {
position: relative;
}

.border-1px-bot::after,
.border-1px-top::after,
.border-1px-left::after,
.border-1px-right::after {
position: absolute;
content: '';
background-color: #000;
}

.border-1px-bot::after {
width: 100%;
height: 1px;
left: 0;
bottom: 0;
}

.border-1px-top::after {
width: 100%;
height: 1px;
left: 0;
top: 0;
}

.border-1px-left::after {
width: 1px;
height: 100%;
left: 0;
top: 0;
}

.border-1px-right::after {
width: 1px;
height: 100%;
right: 0;
top: 0;
}

@media screen and (-webkit-min-device-pixel-ratio: 1.5) and (-webkit-max-device-pixel-ratio: 2.49) {

.border-1px-bot::after,
.border-1px-top::after {
transform: scaleY(.5);
}

.border-1px-left::after,
.border-1px-right::after {
transform: scaleX(.5);
}
}

@media screen and (-webkit-min-device-pixel-ratio: 2.5) {

.border-1px-bot::after,
.border-1px-top::after {
transform: scaleY(.33);
}

.border-1px-left::after,
.border-1px-right::after {
transform: scaleX(.33);
}
}
1
<div class="border-1px-right"></div>