导读
适配问题,是前端开发中经常会遇到的,里面可能会遇到非常多的问题,比如:
1px
问题UI
图完美适配方案iPhone
适配方案- 横屏适配
- 高清屏图片模糊问题
- ...
上面这些问题有的人可能已经知道如何解决了,但是问题产生的原理,以及解决方案的原理可能会模糊不清或者一知半解。而在探寻这些问题的过程中,我们会遇到非常多的概念,比如:像素、分辨率、PPI、DPI、DP、DPR、视口扽等,这些概念往往分不清。
这篇文章争取就是解决以上问题的
1 英寸
生活中,我们经常说手机、显示器是多少寸的,其实这里的寸
说的就是英寸
,比如:我的手机是 6.5 寸的屏,其实就是在说手机屏幕的对角线长度为 6.5 英寸。
英寸(inch
,缩写为in
)在荷兰语中的本意是大拇指,而一英寸就是指甲盖底部普通人拇指的宽度,英寸和厘米的缓存为:1英寸 = 2.54厘米
。
2 分辨率
通常我们说的分辨率有两种,一是屏幕分辨率,二是图像分辨率;讲分辨率之前我们先看看像素
2.1 像素
像素,可以理解为一个具有特定位置和颜色的小方块。
我们平时看到的图片、电子屏幕(手机、电脑、电视的显示屏)就是又众多具有特定颜色和位置的像素块组成的;像素是其最小组成单位。
可以通过 sketch 打开一张图片,然后将图片放大,就可以看到这些像素点
2.2 屏幕分辨率
屏幕分辨率是指一个屏幕具体由多少个像素点组成,我们经常调节自己电脑显示器的分辨率,比如
这是 windows 设置屏幕分辨率的界面,推荐1366 * 768,这个数字表示水平和垂直方向上分辨所具有的像素点数。
当然有一点需要注意的是,分辨率
高不代表屏幕就清晰,屏幕的清晰程度还与尺寸
有关。
2.3 图片分辨率
通常所说的 图片分辨率
是指图片还有的像素数,比如一张图片的分辨率为 800 * 400
,这里的 800 就表示其水平方向上有 800 个像素点,400 表示垂直方向上有 400 个像素点。
同一尺寸
的图片、屏幕,分辨率越高,就越清晰
2.4 PPI
PPI (Pixel Per Inch)
,表示每英寸包含的像素数。
刚才说,同一尺寸的屏幕、图片,分辨率越高就越清晰
,其实 PPI
也可以用于描述屏幕和图片的清晰图(质量),因为它就是在 同一尺寸
的前提条件下描述像素数的。
PPI
的计算公式:
2.4 DPI
DPI (Dot Per Inch)
,表示每英寸包括的点数,它是一个抽象单位,它可以是描述屏幕、图片时说的像素点,也可以是打印机的墨点。
当 DPI
被用来描述图片和屏幕时,DPI
和PPI
是等价的,但它其实最常用于描述打印机,表示打印机每英寸可以打印的点数。
当打印机打印出片时,如果 DPI 越高,则说明其打印点的密度就越大,打印的图像的质量就越好,当然也会消耗更多的墨点和打印时间。
3 设备独立像素
实际上,以上我们提到的像素都是指 物理像素
,即设备上真是的物理单元。
下面我们来看看 设备独立像素 (Device Indpendent Pixels, DIP or DP)
是如何产生的。
智能手机发展速度之快,远远超乎我们的想象,在过去,我们刚开始还使用着分辨率非常低的手机,比如下面的白色手机,它的分辨率是 320 * 480
,我们可以在上面浏览正常的文字、图片等。
但是,随着科技的发展,很快,低分辨率的手机已经不能满足我们的需求了。后来,也很快,高分辨的手机就问世了,比如下面的黑色手机,它的分辨率为 640 * 940
,正好是白色手机分辨率的两倍。
理论上来讲,同样尺寸、分辨率(同样的 DPI)的图片和文字,在黑色手机上应该会被缩放一倍,因为它的分辨率提高了一倍,这样,那不是,后面出现更高分辨率的手机,页面的元素会变的越来越小吗?
然后,事实却并不是这样,不论分辨率有多高,他们所展示的内容比例都是基本类似的。乔布斯在 iPhone4
的发布会上首次提出 Retina Display (视网膜屏幕)
的概念,它正是解决了上面的问题,这也使它成为一款跨时代的手机。
在 iPhone4
使用的视网膜屏幕中,把 2 * 2
个像素当 1
个像素使用,这样让屏幕看起来更精致,但是元素的大小却不会发生改变。
如果上面我们提到的黑色手机使用了视网膜屏幕
的技术,那么显示结果就是下面这种情况了,比如列表的宽度为 300
个像素,那么在水平方向上,白色手机会用 300
个物理像素渲染它,而黑色手机实际上会用 600
个物理像素渲染。
这时候问题就来了,开发者怎么开发?不同分辨率屏幕之间又如何统一呢?
需要用一种统一的单位来告诉开发者和不同分辨率的设备,元素在界面上显示的大小,这个单位就是设备独立像素 (Device Independent Pixels)
,简称 DIP
或 DP
。上面我们说,列表的宽度为 300
个像素,实际上我们可以说列表的宽度为 300
个设备独立像素。
打开 chrome
的开发者工具,我们可以模拟各个手机幸好的显示情况,每种型号上面会显示一个尺寸,比如 iPhone X
的显示尺寸为 375 * 812
,实际上 iPhone X
的分辨率比这高很多,这里显示的就是 设备独立像素。
我们平时开发时,写的各个像素其实也是 设备独立像素
,这样在开发者和各个分辨率的设备之间就有了一个统一的单位。
3.1 设备像素比
设备像素比 Device Pixel Ratio
简称 DPR
,即 物理像素
/ 设备独立像素
的值。
在浏览器中,可以通过 window.devicePixelRatio
来获取 DPR
。
CSS 中,可以使用媒体查询 min-device-pixel-ratio
,来获取 DPR
。
@media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {}
在 React Native
中,可以通过 PixelRatio.get()
来获取 DPR
。
当然,上面的规则也会有例外,比如 iPhone 6、7、8 Plus
的实际物理像素为 1080 * 1920
,在开发者工具我们可以看到,它的设备独立像素为 414 * 736
,设备像素比(DPR)为 3
,设备独立像素和设备像素比的乘积并不等于 1080 * 1920
,而是 1242 * 2208
.
实际上,手机会自动把 1242 * 2208
个像素点塞进 1080 * 1920
个物理像素点来渲染,我们不用关心这个过程,而 1242 * 2208
被称为屏幕的设计像素,也就是 UI 原型图中给到的 像素
,当然我们开发过程中也是以这个 设计像素
为准。
从苹果提出视网膜屏幕开始,才出现设备像素比(DIP)
这个概念,因为在之前,移动设备都是直接使用物理像素来进行展示的。
紧接着,Android
同样使用了其它的技术来实现 DPR
大于 1
的屏幕,不过原理类似。由于 Android 屏幕尺寸非常多、分辨率高低跨度大,不像苹果只有它自己的几款固定设备、尺寸。所以,为了保证各种设备的显示效果,Android 设备按照设备的像素密度将设备分成了几个区间:
当然,所有的 Android 设备不一定会严格按照上面的分辨率,每个类型可能对应几种不同的分辨率,所以,每个 Android 手机都能根据给定的区间范围,确定自己的 DPR,从而拥有类似的显示。当然,仅仅是类似,由于各个设备的尺寸、分辨率上的差异,设备独立像素也不会完全相同,所以各种 Android 设备上仍然不能做到在展示上完全一致。
3.2 移动端开发
在IOS
、Android
、React Native
开发中,样式单位使用的都是设备独立像素。
IOS
的尺寸单位是pt
,Android
的尺寸单位是dp
,React Native
中没有指定明确的单位,他们其实都是设备独立像素 dp
。
在使用 React Native
开发APP
时,UI
给我们的原型图一般都是基于iPhone 6
的物理像素给定的。
为了适配所有机型,我们在写样式时需要把物理像素转换为设备独立像素,例如:如果给定一个元素的高度为200px
(这是物理像素,而非 CSS 像素),iPhone 6
的设备像素比(DPR)为2
,我们开发时的height
应为200px / 2 = 100dp
。
当然,最好的是,可以和设计沟通好,所有的UI
图都按照设备独立像素出。
我们还可以在代码(React Native)中进行 px 和 dp 的转换:
import {PixelRatio } from 'react-native';
const dpr = PixelRatio.get();
/**
* px转换为dp
*/
export function pxConvertTodp(px) {
return px / dpr;
}
/**
* dp转换为px
*/
export function dpConvertTopx(dp) {
return PixelRatio.getPixelSizeForLayoutSize(dp);
}
3.3 WEB 端开发
在写CSS
时,我们用到最多的单位是px
,即CSS 像素
,当页面的缩放比例为100%
时,一个CSS 像素
等于一个设备独立像素。这句话的意思就是说,我们在开发时可以直接按照 UI
图给定的像素来进行开发。
但是 CSS 像素
是很容易被改变的,当用户对浏览器进行了放大,CSS 像素
会被放大,这是一个CSS 像素
会跨域更多的物理像素,你会发现,文字越来越大,也越来越清晰。
页面的缩放系数 = CSS 像素 / 设备独立像素
3.4 关于屏幕
这里多说两句 Retina
屏幕,因为我在很多文章中看到对 Retina
屏幕有误解。
Retina 屏幕只是苹果提出的一个营销术语:
在普通的使用距离下,人的肉眼无法分辨单个的像素点。
为什么强调普通的使用距离下
呢?我们来看一下它的计算公式:
a
代表人的视角,h
代表像素间距,d
代表肉眼和屏幕的间距,符合以上条件的屏幕可以使肉眼看不见单个物理像素点。
它不能单纯的表达分辨率和PPI (Pixel Per Inch)
,只能表达一种视觉效果。
让多个物理像素渲染一个独立像素只是Retian
屏幕为了达到效果而使用的一种技术,而不是所有DPR > 1
的屏幕就是Retina
屏幕。
比如:给你一个块超大尺寸的屏幕,即使它的PPI
很高,DPR (Device Pixel Ratio)
也很高,但在近距离你能看清它的像素点,这就不算是Retina
屏幕。
我们经常见到用K
和P
这两个单位来形容屏幕:
P
代表的是屏幕纵向的像素数,1080P
即纵向有1080
个像素,分辨率1920 * 1080
的屏幕就属于1080 P
屏幕。
我们平时所说的高清屏其实就是屏幕的物理分辨率达到或超过1920 * 1080
的屏幕。
K
代表屏幕横向有几个 1024
个像素,一般来讲横向像素数超过2048
就属于2 K
屏,横向像素超过4096
就术语4 K
屏。
以上内容来自 关于移动端适配,你必须要知道的 + 一些自己的理解
4 视口
视口(viewport)
指浏览器的窗口,即浏览器上用来显示网页的那部分区域,不包括浏览器的 UI, 菜单栏等 —— 是用户网页的可视区域,即指你正在浏览的文档的那一部分。可以理解为容纳html
元素的容器。
在移动端和 pc 端,视口是不同的。pc 端的视口是浏览器窗口的区域,而在移动端则有三个不同的视口概念:布局视口
、视觉视口
、理想视口
。
viewport
的设置不会对 PC 页面产生影响,但对于移动端页面却很重要。
4.1 layout viewport(布局视口)
网页布局的基准窗口,在 PC 浏览器上,相当于当前浏览器的窗口大小(不包括 borders、margins、滚动条),根据窗口大小,该值也可动态改变。在移动端,布局视口被赋予一个默认值,大部分为 980px,这保证 PC 的网页可以在手机浏览器上呈现,但是网页内容会变小,用户可以手动对网页进行放大,当然放大的时候浏览器也会出现滚动条。可以通过document.documentElement.clientWidth / clientHeight
获取网页在标准模式下的布局视口的大小,且值不可变。
4.2 visual viewport(视觉视口)
用户在移动设备屏幕上能看到的那部分区域。可以通过window.innerWidth / innerHeight
来获取视觉视口大小。视觉窗口在 PC 端和移动端都可改变,PC 端调整窗口大小时,视觉视口大小会发生变化;移动端比如键盘弹出时,视觉视口会变小。
4.3 ideal viewport(理想窗口)
网站页面在移动端展示的理想大小。是页面的宽度跟设备的宽度一致,上面介绍 CSS 像素
时提到 页面的缩放系数 = CSS像素 / 设备独立像素
,实际上是说 页面的缩放系数 = 理想视口宽度 / 视觉视口宽度
更为准确,所以当页面缩放比例为 100% 时,CSS像素 = 设备独立像素
,布局视口 = 视觉视口 = 理想视口
,我们就能在屏幕上看到正常舒服的页面,可以通过 screen.width / height
来获取理想视口的大小。
4.4 Meta Viewport
4.4.1 属性介绍
属性 | 含义 | 取值 |
---|---|---|
width | 定义视口的宽度,单位为像素 | 正整数或设备宽度device-width |
height | 定义视口的高度,单位为像素 | 正整数或device-height |
initial-scale | 定义初始缩放值 | 整数或小数 |
minimum-scale | 定义缩小最小比例,它必须小于或等于maximum-scale设置 | 整数或小数 |
maximum-scale | 定义放大最大比例,它必须大于或等于minimum-scale设置 | 整数或小数 |
user-scalable | 定义是否允许用户手动缩放页面,默认值yes | yes/no |
4.4.2 基本用法和作用
<meta name = "viewport" content = "width = device-width, initial-scale = 1.0, maximum-scale = 1.0, user-scalable = no" />
上面这个基本设置:
-
width = device-width
:表示让布局视口
的宽度等于设备宽度 -
initial-scale = 1
:表示页面的初始缩放比例为 1,相当于让视觉视口 = 理想视口
,1 个 CSS像素 就等于 1 个设备独立像素 -
maximum-scale = 1.0
:表示页面的最大缩放比为 1 -
user-scalable = no
:表示不允许用户对页面进行缩放操作
viewport
属性的作用,就是让布局视口
通过缩放来适配屏幕宽度,widht = device-width
仅仅是让布局视口
初始大小等于设备宽度,后面设置的initial-scale
是用来缩放布局视口
大小,而且默认是布局视口
初始大小等于设备宽度,也就是所谓的理想窗口
。
以上内容来自 拿到一份设计稿,我该如何进行移动端开发? + 一些自己的理解
4.5 初始缩放
上面提到的meta
标签中的width
可以决定布局视口的宽度,实际上它并不是布局视口的唯一决定性因素,设置initial-scale
也有可能影响到布局视口,因为布局视口的宽度取width
和视觉视口
宽度的最大值。
例如:若手机的理想视口宽度为400px
,设置width = device-width
,initial-scale = 2
,此时视觉视口宽度 = 理想视口宽度 / initial-scale
即 200px
,布局视口取两者最大值即device-width
400px
,其实 initial-scale 大于 1 的情况下,布局视口 都等于 理想视口的大小,即页面的初始大小始终都是 理想视口 大小。
若设置width=device-width
,initial-scale=0.5
,此时视觉视口宽度 = 理想视口宽度 / initial-scale
即800px
,布局视口取两者最大值即800px
,会发现页面出现了滚动条,因为现在布局视口 > 理想窗口
这里有一点不理解就是,为什么文字也变大了 ??
个人理解
布局视口变大,一个 CSS 像素会跨域多个 物理像素 去显示,所以文字就变大了。
4.6 获取浏览器大小
浏览器为我们提供了获取窗口大小的API
有很多:
-
window.innerHeight
:获取浏览器视觉视口高度(包括垂直滚动条) -
window.outerHeight
:获取浏览器窗口外部的高度,这个数值是固定的,设备的分辨率 / 设备像素比
-
window.screen.availHeight
:浏览器窗口可用的高度 -
document.documentElement.clientHeight
:获取浏览器布局视口高度,包括内边距,但不包括垂直滚动条、边框和外边距 -
document.documentElement.offsetHeight
:包括内边距、滚动条、边框和外边距 -
document.documentElement.scrollHeight
:在不使用滚动条的情况下适合视口中的所有内容的最小宽度。测量方式与clientHeight
相同,它包含元素的内边距,但不包括边框,外边距或垂直滚动条
5 1px 问题
为了适配各种屏幕,我们写代码时一般使用设备独立像素来对页面进行布局和开发。
而在设备独立像素比大于1
的屏幕上,我们写的1px
实际上是被多个物理像素渲染,这就会出现1px
在有些屏幕上看起来很粗的现象。
5.1 border-image
基于media
查询判断不同的设备像素比给定不同的border-image
:
.border__1px {
border-bottom: 1px solid #000;
}
@media only screen and (-webkit-min-device-pixel-ratio:2) {
.border__1px {
border-bottom: none;
border-width: 0 0 1px 0;
border-image: url(../img/1pxline.png) 0 0 2 0 stretch;
}
}
5.2 background-image
和border-image
类似,准备一张符合条件的边框背景图,模拟在背景上:
.border__1px {
border-bottom: 1px solid #000;
}
@media only screen and (-webkit-min-device-pixel-ratio: 2) {
.border__1px {
background: url(../img/1pxline.png) repeat-x left bottom;
background-size: 100% 1px;
}
}
上面两种都需要单独准备图片,而且圆角不是很好处理,但是可以应对大部分场景。
5.3 伪类 + transform
基于media
查询判断不同的设备像素比对线条进行缩放:
.border__1px:before {
content: '';
position: absolute;
top: 0;
height: 1px;
width: 100%
background-color: #000;
transform-origin: 50% 0%;
}
@media only screen and (-webkit-min-device-pixel-ratio:2) {
.border__1px:before {
transform: scaleY(0.5)
}
}
@media only screen and (-webkit-min-device-pixel-ratio:3) {
.border__1px:before {
transform: scaleY(0.33)
}
}
这种方式可以满足各种场景,如果需要圆角,只需要给伪类也加上border-radius
即可。
5.4 svg
上面我们通过border-image
和background-image
都可以模拟1px
边框,但是使用的都是位图,还需要外部引入。
借助PostCss
的postcss-write-svg
我们能直接使用border-image
和background-image
创建svg
的1px
边框:
@svg border__1px {
height: 2px;
@rect {
fill: var(--color, black);
width: 100%;
height: 50%;
}
}
.example {
border: 1px solid transparent;
border-image: svg(border__1px param(--color #00b1ff)) 2 2 stretch;
}
编译后:
.example { border: 1px solid transparent; border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E") 2 2 stretch; }
5.5 设置viewport
痛殴设置缩放,让CSS
像素等于真正的物理像素。
例如:当设备像素比为 3 时,我们将页面缩放 1/3 倍,这是 1px 等于 1物理像素。
const scale = 1 / window.devicePixelRatio
const viewport = document.querySelector('meta[name="viewport"]')
if (!viewport) {
viewport = document.createElement('meta')
viewport.setAttribute('name', 'viewport')
window.document.head.appendChild(viewport)
}
viewport.setAttribute('content', 'width=device-width, user-scalable=no, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale)
实际上,上面这种方案是早先flexible
采用的方案。
当然,这样做是要付出代价的,这意味着你页面上所有的布局都要按照物理像素来写,意味着你的页面只能在固定的设备上有比较好的用户体验,显然不能这么做。这时,我们可以借助flexible
或vw、vh
来帮助我们适配。
6 响应式布局方案
尽管我们可以使用设备独立像素
来保证页面在各个设备上显示的效果类似,但这并不能保证显示完全一致,我们需要一种方案来让设计稿得到更完美的适配。
6.1 rem 布局
flexible
方案是阿里早起开源的一个移动端适配解决方案,引用flexible
后,我们在页面上统一使用rem
来布局
6.1.1 flexible 方案 1
它的核心代码非常简单:
// set 1rem = viewWidth / 10
function setRemUnit () {
var docEl = document.documentElement
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit()
rem
是相对于html
节点的font-size
来做计算的。
我们通过设置document.documentElement.style.fontSize
就可以统一整个页面的布局标准。
上面的代码中,将html
节点font-size
设置为页面clientWidth
(布局视口)的1/10
,即1rem
就等于页面布局视口的1/10
,这就意味着我们后面使用的rem
都是按照页面比例来计算的。
这时,我们只需要将UI
出的图转换为rem
即可。
以iPhone 6
为例,布局视口为375px
,则1rem = 37.5px
,这是UI
给定一个元素的宽为75px
(设备独立像素),我们只需要将它设置为 75 / 37.5 = 2rem
。
当然,每个布局都要计算非常繁琐,我们可以借助PostCSS
的px2rem
插件来帮助我们完整这个过程。
下面的代码可以保证页面大小变化时,布局可以自适应,当出发了window
的resize
和pageShow
事件之后自动调整html
的fontSize
大小。
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit)
window.addEventListener('pageShow', function (e) {
if (e.persisted) {
setRemUnit()
}
})
6.1.2 flexible 方案2
上面的方案 1 是一个比较通用的方案,但是要配置 PostCSS
的 px2rem
插件来帮助我们完成 px -> rem 的自动计算转换,但是有时候我们可能会碰到不需要、没必要或者不能使用该插件的情况,比如,我现在就碰到这种情况,一个项目中,只有一个页面需要响应式
的实现,所以不能给整个项目配置这种插件,我们可以采用以下方案来实现,该方案是方便计算
function setRemUnit () {
var docEl = document.documentElement
// 假设我的设计图的独立设备像素为 375 px
var rem = docEl / 3.75
// 1rem = 100px
docEl.style.fontSize = rem + 'px'
}
该方案其实就按照1rem = 100px
这个比例去开发,方便计算,比如
我的设计图是 375px 的独立设备像素,现在有一个 div 为 75px,那我开发时该div的尺寸代码就是 width = 0.75rem
,非常方便计算
6.1.3 flexible 方案3
上面的方案是采用 1:100 这种方便计算的情况去开发的,实际上有更方便的方式,就是按照 1:1 的比例去开发,1rem = 1px
这种方式网上也有人提到,但是被大家反驳,说是浏览器(chrome)有最小字体为12px
的限制,如果你html
的font-size = 1px
,当前浏览器设定的最小字体大小为12px,所以浏览器自动把html{font-size: 1px} 识别为html{font-size: 12px},所以效果也就是1rem = 12px。
我去实验了下,这么设置没问题,你会发现你设置的 rem 依旧是响应式的,而且尺寸也是对的,当然也有可能以前的浏览器确实存在该问题,但现在没了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
html {
font-size: 1px;
}
.test {
padding: 1rem;
background: blue;
margin: 1rem;
width: 5rem;
height: 5rem;
}
.text1 {
font-size: 5rem;
}
.text2 {
font-size: 12rem;
}
</style>
</head>
<body>
<!-- div 的尺寸以及文字大小,说明 font-size 的 1px 没有被浏览器识别成 12px -->
<div class = "test"></div>
<div class = "text">我是测试文字1</div>
<div class = "t2">我是测试文字2</div>
</body>
</html>
由于viewport
单位得到众多浏览器的兼容,上面这种方案现在已经被官方弃用:
lib-flexible 这个过渡方案已经可以放弃使用,不管是现在的版本还是以前的版本,都存有一定的问题。建议大家开始使用viewport来替代此方案。
6.1.2、6.1.3两种方案是我自己补充上去的,如有错误和原文没有关系
6.2 vw、vh 方案
vw、vh
方案是将视觉视口宽度window.innerWidth
和视觉视口高度window.innerHeight
等分为 100 份。
上面的 flexible
方案其实就是模仿这种方案,因为早些时候vw
还没有得到很好的兼容。
-
vw (viewport's width)
:1vw
等于视觉视口的1%
-
vh (viewport's height):
1vh为视觉视口高度的
1%` -
vmin
:vw
和vh
中的较小值 -
vmax
:选取vw
和vh
中的较大值
如果视觉视口为375px
,那么1vw = 3.75px
,这时UI
给定的一个元素的宽为75px
(设备独立像素)我们只需要将它设置为 75 / 3.75 = 20vw
。
这里的比例关系我们也不用自己换算,我们可以使用·PostCSS
的postcss-px-to-viewport
插件帮我们完成这个过程。写代码时,我们只需要根据UI
给的设计图写px
单位即可。
当然,没有一种方案是十全十美的,vw
同样有一定的缺陷:
-
px
转换成vw
不一定能完全整除,因此存在一定的像素差 -
当容器使用
vw
,margin
采用px
时,很容易造成整体宽度超过100vw
,从而影响布局效果。当然我们也可以避免,例如使用padding
代替margin
,结果calc()
函数使用,不实用px
单位等
7 适配iPhoneX
iPhoneX的出现将手机的颜值带上了一个新的高度,它取消了物理按键,改成了底部的小黑条,但是这样的改动给开发者适配移动端又增加了难度。
7.1 安全区域
在iPhoneX发布后,许多厂商相继推出了具有边缘屏幕的手机。
这些手机和普通手机在外观上无外乎做了三个改动:圆角(corners)、刘海(sensor housing)和小黑条(Home Indicator)。为了适配这些手机,安全区域这个概念变诞生了:安全区域就是一个不受上面三个效果的可视窗口范围。
为了保证页面的显示效果,我们必须把页面限制在安全范围内,但是不影响整体效果。
7.2 viewport-fit
viewport-fit
是专门为了适配iPhoneX
而诞生的一个属性,它用于限制网页如何在安全区域内进行展示。
-
contain
: 可视窗口完全包含网页内容 -
cover
:网页内容完全覆盖可视窗口
默认情况下或者设置为auto
和contain
效果相同。
7.3 env、constant
我们需要将顶部和底部合理的摆放在安全区域内,iOS11新增了两个CSS函数env、constant,用于设定安全区域与边界的距离。
函数内部可以是四个常量:
- safe-area-inset-left:安全区域距离左边边界距离
- safe-area-inset-right:安全区域距离右边边界距离
- safe-area-inset-top:安全区域距离顶部边界距离
- safe-area-inset-bottom:安全区域距离底部边界距离
注意:我们必须指定viweport-fit后才能使用这两个函数:
<meta name="viewport" content="viewport-fit=cover">
constant
在iOS < 11.2
的版本中生效,env
在iOS >= 11.2
的版本中生效,这意味着我们往往要同时设置他们,将页面限制在安全区域内:
body {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
当使用底部固定导航栏时,我们要为他们设置padding
值:
{
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
8 横屏适配
很多视口我们要对横屏和竖屏显示不同的布局,所以我们需要检测在不同的场景下给定不同的样式:
8.1 JavaScript 检测横屏
window.orientation
: 获取屏幕旋转方向
window.addEventListener("resize", ()=>{
if (window.orientation === 180 || window.orientation === 0) {
// 正常方向或屏幕旋转180度
console.log('竖屏');
};
if (window.orientation === 90 || window.orientation === -90 ){
// 屏幕顺时钟旋转90度或屏幕逆时针旋转90度
console.log('横屏');
}
});
8.2 CSS检测横屏
@media screen and (orientation: portrait) {
/*竖屏...*/
}
@media screen and (orientation: landscape) {
/*横屏...*/
}
9 图片模糊问题
9.1 产生原因
我们平时使用的图片大多数都属于位图(png、jpg...),位图由一个个像素点构成的,每个像素都具有特定的位置和颜色值:
理论上,位图的每个像素对应在屏幕上使用一个物理像素来渲染,才能达到最佳的显示效果。
而在dpr > 1
的屏幕上,位图的一个像素可能由多个物理像素来渲染,然而这些物理像素点并不能被准确的分配上对应位图像素的颜色,只能取近似值,所以相同的图片在dpr > 1
的屏幕上就会模糊:
9.2 解决方案
为了保证图片质量,我们应该尽可能让一个屏幕像素来渲染一个图片像素,所以,针对不同DPR的屏幕,我们需要展示不同分辨率的图片。
如:在dpr=2
的屏幕上展示两倍图(@2x
),在dpr=3
的屏幕上展示三倍图(@3x
)。
9.3 media查询
使用media查询判断不同的设备像素比来显示不同精度的图片:
.avatar{
background-image: url(conardLi_1x.png);
}
@media only screen and (-webkit-min-device-pixel-ratio:2){
.avatar{
background-image: url(conardLi_2x.png);
}
}
@media only screen and (-webkit-min-device-pixel-ratio:3){
.avatar{
background-image: url(conardLi_3x.png);
}
}
只适用于背景图
9.4 image-set
使用image-set:
.avatar {
background-image: -webkit-image-set( "conardLi_1x.png" 1x, "conardLi_2x.png" 2x );
}
只适用于背景图
9.5 srcset
使用img标签的srcset属性,浏览器会自动根据像素密度匹配最佳显示图片:
<img src="conardLi_1x.png"
srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x">
9.6 JavaScript拼接图片url
使用window.devicePixelRatio获取设备像素比,遍历所有图片,替换图片地址:
const dpr = window.devicePixelRatio;
const images = document.querySelectorAll('img');
images.forEach((img)=>{
img.src.replace(".", `@${dpr}x.`);
})
9.7 使用svg
SVG的全称是可缩放矢量图(Scalable Vector Graphics)。不同于位图的基于像素,SVG 则是属于对图像的形状描述,所以它本质上是文本文件,体积较小,且不管放大多少倍都不会失真。
除了我们手动在代码中绘制svg,我们还可以像使用位图一样使用svg图片:
<img src="conardLi.svg">
<img src="data:image/svg+xml;base64,[data]">
.avatar {
background: url(conardLi.svg);
}
小结
希望你阅读本篇文章后可以达到以下几点:
-
理清移动端适配常用概念
-
理解移动端适配问题产生的原理,至少掌握一种解决方案
以上内容来自 关于移动端适配,你必须要知道的
以上内容摘自两篇博客 + 自己的一些理解