移动端适配主要指两个方面
自适应:根据不同的设备屏幕大小来适配元素大小
响应式:当屏幕尺寸发生变化时可以重新计算元素大小
viewport
我们先看看viewport的一些设置,带着疑问我们往下分析
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover">
在了解viewport的时候我们首先要理解三个概念(布局视口,理想视口,视觉视口)
布局视口
布局视口可以理解为文档对象的逻辑尺寸,单位为逻辑像素(CSS像素)。
在PC端上,布局视口宽度默认等于浏览器窗口的宽度。而在移动端上,由于要使为PC端浏览器设计的网站能够完全显示在移动端的小屏幕里,此时的布局视口会大于移动设备的屏幕。
由于历史原因,默认布局视口宽度存在以下兼容性
我们可以通过调用document.documentElement.clientWidth / clientHeigh
来获取布局视口大小。
理想视口
理想视口是与设备相关的,是布局视口的一个理想尺寸,只有当布局视口的尺寸等于设备屏幕的尺寸时(逻辑尺寸,即有多少个逻辑像素),才是理想视口,此时文档对象宽度与屏幕宽度相同。
移动设备一般具有固定的DPR,即在缩放100%时,用多少个物理像素显示一个逻辑像素,在Web开发中就是用多少个物理像素去显示一个CSS像素。
分辨率:屏幕分辨率指一个屏幕具体由多少个像素点(物理像素)组成。比如我们的电脑1920*1080
理想视口宽度 = 移动设备横向分辨率 / DPR
我们可以通过调用screen.width / height
来获取理想视口大小。
视觉视口
用户正在看到的网页的区域。用户可以通过缩放来查看网站的内容。如果用户缩小网站,我们看到的网站区域将变大,此时视觉视口也变大了,同理,用户放大网站,我们能看到的网站区域将缩小,此时视觉视口也变小了。
不管用户如何缩放,都不会影响到布局视口的宽度。
视觉视口的宽度 = 理想视口宽度 / 缩放比例
我们可以通过调用window.innerWidth / innerHeight
来获取视觉视口大小。
利用meta标签控制视口
移动设备的默认布局视口往往大于理想视口,这就意味着缩放或者出现滚动条才能完整的容纳页面,我们需要的是将页面的布局视口设置为理想视口
也就是
<!-- width: 设置布局视口的宽度 device-width是获取用户设备的宽度 -->
<meta name="viewport" content="width=device-width">
注意:meta viewport 标签只对移动端浏览器有效,对 PC 端浏览器是无效的。
值 | 可能的附加值 | 描述 |
---|---|---|
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,将无法缩放当前页面。浏览器可以忽略此规则 |
设置的时候需要注意两点
1.当只设置width属性值,而不指定initial-scale属性值时,大多数浏览器会自动缩放。
如果width属性值大于理想视口宽度,则视觉视口放大为width的值,计算得到scale小于1.0;
如果width属性值小于理想视口宽度,则视觉视口缩小为width的值,计算得到scale大于1.0;
这么做的目的是让设备屏幕把完整的页面呈现出来(页面横向塞满屏幕,计算得到scale)
2.当只指定initial-scale属性,而不设置width属性时,浏览器会将布局视口宽度设置为视觉视口宽度,以将页面正好铺满屏幕。可以认为这是一条定律。
width=device-width, initial-scale=1
原因:理论上只设置一个就好了,这里是因为有兼容问题
只设置width = device-width 苹果移动设备横屏时会有bug
只设置init-scale = 1 WP7系统IE浏览器横屏会有bug
现在我们实现了第一步,就是防止浏览器的自动缩放,导致元素变化的不可控,下一步就是实现响应式
响应式
移动端的屏幕尺寸非常多,我们需要在不同的尺寸下,元素的大小,margin,padding等也要相应的发生变化,比如我们在375px基础下写的100px盒子
在375下为100100
在414下为110.4110.4
在320下为85.3*85.3
适配方案有很多,比如百分比,rem+动态html font-size设置,vw+vh等等
我们今天主要讨论两种方案
rem+动态html font-size设置
rem单位是相对于html元素的font-size来设置的,那么如果我们需要在不同的屏幕下有不同的尺寸,可以动态的修改html的font-size尺寸。在开发中,我们只需要考虑两个问题:
1.针对不同的屏幕,设置html不同的font-size;
2.将原来要设置的尺寸,转化成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;}
这里有些缺点就是,这也是现在不常见的原因
1.我们需要针对不同的屏编写大量的媒体查询
2.如果动态改变尺寸,不会实时的进行更新
第二种:用js动态获取设备宽度
1.根据html的宽度计算出font-size的大小,并且设置到html上
2.监听页面的实时改变,并且重新设置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的一些优点
1.具备rem的所有功能
2.不用去计算html的font-size大小,也不需要给html设置这样一个font-size
3.因为不依赖font-size的尺寸,所以不用担心某些原因html的font-size尺寸被篡改,页面尺寸混乱
4.vw相比于rem更加语义化,1vw刚好是1/100的viewport的大小
那么现在就有一个问题了,使用vw就面临前面提到的那个换算兼容性问题,拿设计稿375px为例
1.手动换算,每个尺寸都需要xx/3.75换算成vw
2.lass/sass预处理,借用变量
@vwUnit:3.75;.pxToVw(@px) {result: 1vw * (@px / @vwUnit);}
.box {width: .pxToVw(100)[result]}
3.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,所以我们就要想一些兼容手段
1.伪类+transform+媒体查询
.border-top-1px::after{
...
height: 1px;
}
@media (-webkit-min-device-pixel-ratio: 2) {
.border-top-1px::after {
transform: scaleY(0.5);
}
}
2.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" );
3.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)
}
}