移动端适配
移动端适配主要指两个方面
- 自适应:根据不同的设备屏幕大小来适配元素大小
- 响应式:当屏幕尺寸发生变化时可以重新计算元素大小
viewport
我们先看看viewport的一些设置,带着疑问我们往下分析
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover">
在了解viewport的时候我们首先要理解三个概念
- 布局视口
- 视觉视口
- 理想视口
布局视口
这两个盒子都是100px*100px的,但是在移动端尺寸就显得更小,这是为什么呢
我们在viewport没有设置参数的时候会发现,移动端会按照默认宽度980px来布局,为了完整的展示整个页面,会适当的等比例进行缩放,所以移动端的显示尺寸就和实际尺寸不一致
我们相对于980px布局的这个视口就称为布局视口
视觉视口
视觉视口实际上就是我们屏幕的可视区域,比如上面的设备尺寸,如果按照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-scalable | yes 或者 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来做兼容性方案
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越大的设备显示的内容就会越清晰,越细腻
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{
...
height: 1px;
}
@media (-webkit-min-device-pixel-ratio: 2) {
.border-top-1px::after {
transform: scaleY(0.5);
}
}
- viewport + rem 同时通过设置对应
viewport
的rem
基准值,这种方式就可以像以前一样轻松愉快的写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准备合适的图片,比如一半有颜色,一半透明,修改起来麻烦
图片选择
一个位图像素是栅格图像(如:png, jpg, gif等)最小的数据单元,理论上,1个位图像素对应于1个物理像素,图片才能得到完美清晰的展示。
在普通屏幕下是没有问题的,但是在retina屏幕下就会出现位图像素点不够,就近取色,从而导致图片模糊的情况。
那如果普通屏幕下,用了两倍图片会怎么样呢,这就会出现一个物理像素点对应4个位图像素点,所以它的取色也只能通过一定的算法,肉眼看上去虽然图片不会模糊,但是会觉得图片缺少一些锐利度,或者是有点色差
一般的完整写法为了兼容各个多倍屏需要用媒体查询来兼容。一般来说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-ratio: 2) {
.img {
background-image: url(image@2x.png)
}
}