移动端开发基础
屏幕适配
屏幕适配,一直是作为一个前端开发始终逃不掉的问题,这个话题可以追溯到最开始的PC端浏览器的不同分辨率,再到移动端不同的屏幕尺寸,一直伴随着前端工程师的日常的页面开发工作。所谓屏幕适配,可以理解为一个网页元素或者网页布局,在不同尺寸,分辨率等场景下,如何呈现最佳的效果。 从最早的PC端屏幕来说,大部分的屏幕适配采取的是:
- 页面框架最外层元素宽度固定,并且居中,高度随内容自适应,比较常见的是宽度为960px~1080px。
- 页面内部的元素大多数使用盒子模型构建,采用固定宽高,当内容超出时,会出现滚动条。
- 对于一些需要根据屏幕不同而展示不同大小的元素,可以给元素设置百分比的单位。
随着HTML5和CSS3的到来,逐渐出现了弹性布局(flex布局),媒体查询Media Query,和响应式页面概念,这些特性都可以应用在PC端以及移动端屏幕适配解决方案中。除了这些之外,还有rem和vw方案更加有针对性的解决移动web页面的适配问题。
viewport视窗(视口)
在HTML代码的<head>
标签中,都有一行设置的代码,如下:
1 | <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> |
这行代码的作用就是设置浏览器的视窗大小,具体的含义我们后面在介绍,在讲解视窗之前,我们首先需要了解一下什么是物理像素和CSS像素。
像素
像素,也就是px,实际是pixel的缩写,它是图像显示的基本单元,每个像素可以有色彩数值和位置,每个图像是由若干个像素组成,比如对一幅标有1024×768像素的图像,就表明这幅图像的长边有1024个像素,宽边有768个像素,共有1024×768=786432个像素组成。
但是从概念上来说,像素既不是一个确定的物理量,也不是一个点或者小方块,而是一个抽象概念。所以像素所代表的具体含义要从其处于的上下文环境来具体分析。物理像素和CSS像素就是不同的上下文。
物理像素和css像素
- 物理像素:设备屏幕实际拥有的像素点,主要和渲染硬件相关。比如iPhone6的屏幕在宽边有750个像素点,长边有1334个像素点,所以iPhone6总共有750*1334个物理像素。
- CSS像素:也叫逻辑像素,是软件程序系统中使用的像素,每种程序可以有自己的逻辑像素,在web前端页面就是对应的CSS像素,逻辑像素在最终渲染到屏幕上时由相关系统转换为物理像素。
- 设备像素比:一个设备的物理像素与逻辑像素之比。可以在JavaScript中使用
window.devicePixelRatio
获取到。
其实对于早期PC端web页面来说,在的CSS里写个1px,屏幕就给你渲染成1个实际的像素点,此时的设备像素比是1,这时物理像素和CSS像素是一样的。但是对于一些高清屏,例如苹果的retina屏幕,这种屏幕使用2个或者3个物理像素来渲染1个CSS像素,所以这些屏幕的显示效果要清晰很多。例如下图a代表物理像素,b代表CSS像素,它们之间的关系如图下图所示。
可以想象一下,一个传统的PC端web页面,如果想要完全放在手机端使用浏览(可以想象成把PC端显示器替换成手机屏幕),一定是放不下的,而这时就需要对页面进行缩放,那么对页面进行放大和缩小,其实就是改变像素比,例如下图,用4个CSS像素和4个物理像素来模拟放大和缩小。
在页面处于正常状态时,4个物理像素的区域需要4个CSS像素刚好展示完,当页面缩小时,原本4个物理像素需要大于4个CSS像素才能显示完这片区域,而当页面放大时,原本4个物理像素需要小于4个CSS像素就可以显示完,或者说是4个CSS像素能够放下更多于4个物理像素的位置。这就实现了页面的放大和缩小,而对于HTML而言,控制放大和缩小的就是视窗Viewport。
物理像素就是上述所说的小点点
编写网页时,我们用到的像素是css像素
浏览器在显示网页时,需要将css像素转换成物理像素,然后再呈现
那么,一个css像素最终由几个物理像素组成,是由浏览器决定的:
默认
情况下,一个css像素 = 一个物理像素
CSS像素和物理像素之间的比例取决于屏幕的特性(是否为高密度)以及用户进行的缩放,由浏览器自行换算。
视窗
在了解了物理像素和CSS像素的概念之后,然后就需要引入下一个概念,移动设备中的视窗,视窗就是浏览器显示页面内容的屏幕区域,有3种不同的类别,主要分为:
物理视窗(Visual Viewport):表示物理屏幕的可视区域,屏幕显示器的物理像素,也就是长宽边上有多少个像素点。同样尺寸的屏幕,像素点越多,像素密度越大,它的硬件像素会更多。可以理解成物理视窗的大小就是屏幕的大小。
布局视窗(Layout Viewport):是由浏览器厂商提出的一种虚拟的布局视窗,用来解决页面在手机上显示的问题。这种视窗可以通过
<meta>
标签设置viewport来修改。每个浏览器默认都会有一个设置,例如iOS,Android这些机型设置布局视窗宽度为980px,所以PC上的网页基本能在手机上呈现,只不过元素看上去很小,一般可以通过手指动双击缩放网页。理想视窗(Ideal Viewport):理想中的视口。这个概念最早由苹果提出,其他浏览器厂商陆续跟进,目的是解决在布局视窗下页面元素过小的问题,显示在理想视口中的页面具有最理想的宽度,用户无需进行缩放。所以理想视窗就相当于把布局视窗修改成一个理想的大小,这个大小和物理视窗基本相等。
如下图,可以表示物理视窗和布局视窗的关系,底部的网页大小相当于布局视窗,而半透明灰色区域表示物理视窗大小,看起来就像一个手机屏幕大小。
所以如果想要在物理视窗里面完全展示布局视窗里的内容,肯定要将页面缩小。那么缩小到多少合适呢,就需要有理想视窗,如下图所示。
设置Viewport
对于移动端web页面,可以采用<meta>
标签对视窗的大小,缩放等进行配置,也就是之前提到的在<head>
标签内设置的<meta>
的代码如下:
1 | <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> |
其中,可以配置的属性含义如下:
- width:该属性被用来控制视窗的宽度,可以将width设置为320这样确切的像素数,也可以设为device-width这样的关键字,表示设备的实际宽度,一般为了自适应布局,普遍的做法是将width设置为device-width。
- height:该属性被用来控制视窗的高度,可以将height设置为640这样确切的像素数,也可以设为device-height这样的关键字,表示设备的实际高度,一般不会设置视窗的高度,这样内容超出的话采用滚动方式浏览。
- initial-scale:该属性用于指定页面的初始缩放比例,可以配置0.0~10的数字,initial-scale=1表示不进行缩放,视窗刚好等于理想视窗,当大于1时表示将视窗进行放大,小于1时表示缩小。这里只表示初始视窗缩放值,用户也可以自己进行缩放,例如双指拖动手势缩放或者双击手势放大。
- maximum-scale:该属性表示用户能够手动放大的最大比例,可以配置0.0~10的数字。
- minimum-scale:该属性类似maximum-scale,用来指定页面缩小的最小比例。通常情况下,不会定义该属性的值,页面太小将难以浏览。
- user-scalable:该属性表示是否允许用户手动进行缩放,可配置no或者yes。当配置成no时,用户将不能通过手势操作的方式对页面进行缩放。
在使用<meta>
标签设置viewport时有几点需要注意,首先viewport只对移动端浏览器有效,对PC端浏览器是无效的,其次对于移动端浏览器,某些属性也并不是完全支持,例如对于iOS的Safari浏览器,从10.0版本开始将不在支持user-scalable=no,所以即使设置了user-scalable=no,用户依然可以对页面进行手势操作来缩放。如果依然需要禁用,可以参考如下代码:
1 | window.onload = function () { |
通过手势来进行缩放是属于浏览器的默认功能,上面代码的原理就是利用event.preventDefault()
方法,来禁用浏览器的默认事件,这样就不能触发这个默认的缩放功能。具体逻辑可以将代码运行之后看一下效果。 Viewport视窗的相关知识点是了解移动web适配的基础,通过动态的设置viewport可以实现不同屏幕下的页面适配,例如对设备像素比不为1的机型进行缩放,强制让物理像素和CSS像素相等,代码如下:
1 | (function(){ |
这种方法有时候不准确,比如devicePixelRatio不为整数的时候,会出现除不尽的情况,那缩放的倍数就会出现很长的小数,再去算物理像素的时候就会有误差,所以现在大部分移动web页面采用更加完善的rem或者vw加flex的方案来进行适配。
Rem适配
Rem适配方案是当下流行并且兼容性最好的移动端适配解决方案,它支持大部分的移动端系统和机型,Rem实际上是一个字体单位,即rem(font size of the root element)是指相对于根元素的字体大小的单位,简单的说它就是一个相对单位。看到rem大家一定会想起em单位,em(font size of the element)是指相对于父元素的字体大小的单位。它们之间其实很相似,只不过一个计算的规则是依赖根元素一个是依赖父元素计算。 所以Rem适配方案的适配原理就是:将我们之前写px的单位换成rem单位,然后根据屏幕大小动态设置根元素<html>
的font-size大小,那么只要跟元素的font-size改变,对应的元素的大小就会改变,从而达到在不同屏幕下的适配的目的。
动态设置根元素font-size
使用浏览器浏览网页时,网页中的字体大小由根元素<html>
来决定,而<html>
的字体大小由浏览器本身决定,在不修改浏览器默认字体情况下是16px,即默认情况下1rem = 16px,但是如果采用Rem的适配方案就需要动态设置<html>
的font-size。一般情况下是根据屏幕的宽度来动态设置,即采用屏幕宽度来识别不同的机型,以达到对不同机型的适配,具体有两种方案来设置,第一种是采用媒体查询(Media Query),代码如下:
1 | @media screen and (min-width:461px){ |
上面代码中,使用screen媒体特性,来定义了3组屏幕宽度区间,当小于400px,大于401px且小于460px,大于461px,当屏幕宽度位于不同的区间时,则会应用上对应的<html>
的font-size。 另外一种则是使用Javascript动态设置<html>
的font-size,代码如下:
1 | // 获取屏幕视窗宽度 |
上面代码中,得到屏幕宽度后,一般要除以一个系数,这里使用的系数是10,这样得到的font-size值更加灵活,适配性更强,所以实际应用当中,大多数采用的JavaScript来动态设置。如果想要实时监听屏幕大小的变化动态修改font-size,可以引入resize事件,代码如下:
1 | window.addEventListener('resize',function(){ |
计算rem数值
设置完font-size之后,就可以直接利用rem单位来给我们的div或者其他元素设置宽高等等的属性了,这里就有一个问题,我们一般拿到的UI稿都会提供标注,这些标注一般会标识出某个元素例如按钮,图片具体大小数值,单位是px,并且整个UI稿都会基于一个具体的移动设备,例如iPhone6s等,可以参考下图所示。
那么,我们如何根据视觉稿上的px单位值转换成对应的rem单位值呢?这里举一个例子,一个按钮在视觉搞上标注的大小是:宽200px,高400px,那么我们根据这个来进行如下计算:
以iPhone6s视觉搞来说,屏幕是375*667单位是px。
根据上面JavaScript方法设置的<html>
的font-size得到是37.5px,这里37.5px称做rem的基准值,下面的计算会用。
根据1rem=37.5px,得到200px=5.3rem,400px=10.6rem。
根据上面的方法,我们就可以给按钮元素设置rem单位了,代码如下:
1 | .button { |
我们给一个元素采用了rem单位来设置了宽高,那么这个元素在不同机型中显示时,由于设置的根元素<html>
的font-size大小不一样,那么rem所实际渲染出来的大小也就不一样,可以比较一下分别在Chrome开发者工具中的Device Mode中采用iPhone6s和iPhone6P运行的效果区别,如图下图所示。
如上图所示,对于同一个按钮,在不同的机型上表现出的大小是不一样的,这就是rem带来的适配效果。 当然,采用rem适配,必须针对rem基准值来将px转换成对应的rem值,这个计算是很繁琐的一件事情,但是这个工作可以交给Sass[ Sass(英文全称:Syntactically Awesome Stylesheets)是一个最初由Hampton Catlin设计并由Natalie Weizenbaum开发的一个CSS预处理器,采用类CSS语法并在最后解析成CSS的脚本语言。]来帮助我们完成,例如可以在Sass代码中定义一个公式,代码如下:
1 | @function px2rem($px){ |
当然,上面的代码已经不是一个标准的CSS代码了,而是一个Sass语言的CSS代码,不过没有学过Sass也没有关系,我们只会用到Sass的很少一部分知识点。 上面代码中,定义了一个方法,方法名为px2rem,这个方法接收一个参数就是将要转换的px值,然后根据rem基准值来计算。当在给元素设置宽高时,调用这个方法即px2rem(200),将需要转换的px值作为参数传递进去,这样经过编译后,最终得到的就是rem单位的值了即width: px2rem(200)转换成了width:5.3rem。 总结下来,使用Rem适配方案主要有以下几点需要注意:
首先需要有一段JavaScript脚本来动态设置根元素
<html>
的font-size,这段脚本一般放置在<head>
标签里面,让font-size更早的设置,可以让适配更早的生效。一旦页面使用了Rem适配,那么除特殊情况除外(例如雪碧图定位background-position时),页面中凡是用到px为单位的元素都应该改为rem单位,这样才能做到整体适配。
对于宽度比高度大很多的机型例如横屏下的iPad以及一些手写笔记本,是不适合采用Rem方案的,因为宽度较大会导致
<html>
的font-size设置不准确。另外就是一些小说网站,屏幕越小的移动设备如果用了rem单位就会导致文字就越小,就会导致看文章的时候特别费眼。
vw适配
vw其实也是一个CSS单位,类似的还有vh,vmin,vmax共四个单位,这些单位伴随着CSS3的出现就已经有了,但是当时移动web的浪潮已经来临,并且Rem出现的要早一些,所以很多开发人员对此并不熟悉。 和Rem适配方案相比,vw适配方案不需要使用JavaScript脚本来提前设置font-size,vw适配方案完全基于CSS自身,这也是相对于Rem适配方案的优势所在,并且对于横竖屏切换较为频繁的页面时可以采用vmin单位,更加灵活。我们先来了解一下vw,vh,vmin,vmax这几个单位,含义如下:
vw : 1vw 等于视口宽度的1%。
vh : 1vh 等于视口高度的1%。
vmin : 选取vw和vh中最小的那个,1vmin等于视口宽度的1%和视口高度的1%中最小的值。
vmax : 选取vw和vh中最大的那个,1vmax等于视口宽度的1%和视口高度的1%中最大的值。
从上面的解释可以看出,vw和vh这些单位也并不是一个固定的值,而是根据视口宽度或者高度而变。那么什么是视口呢?还记得之前讲解的viewport吗,通过标签:
1 | <meta name="viewport" content="width=device-width"> |
设置的这个宽度就是视口宽度,并且可以通过JavaScript中的document.documentElement.clientWidth
或者document.body.clientWidth
获取到这个值,这里就和前面讲解Rem适配方案时获取屏幕宽度时的用法是一样的。 有些同学会遇到例如window.innerWidth
或者window.screen.width
来获取屏幕或者视口的宽度,这种方法获取到的一般是设备的物理宽度,例如真实的分辨率或者物理像素值,这个和视口宽度不一定相等,当<meta>
标签设置viewport时,如果width=!device-width时,这种情况下就是不相等的,所以各位在使用时还是需要注意一下。
计算vw数值
对于vw适配方案,也是需要计算vw值的,同理我们还是以iPhone6的UI稿为例子,例如一个按钮在视觉搞上标注的大小是宽200px,高400px,那么我们根据这个来做如下计算:
- 以iPhone6s视觉搞来说,屏幕是375*667单位是px。
- 根据1vw等于视口宽度的1%,即1vw等于3.75px,得到200px=53vw,400px=106vw(这里取整)。
根据上面的方法,我们就可以给按钮元素设置vw单位了,代码如下:
1 | .button { |
和计算rem值同理,也可以利用Sass来声明一个方法,做px到vw的转换,代码如下:
1 | @function px2vw($px) { |
Rem适配和vw适配兼容性
从上面的对两种相关的适配方案讲解,可以知道vw适配方案要优于Rem适配方案的,但是没有Rem流行就在于vw的兼容性问题,我们从caniuse[ caniuse是一个当下流行的前端技术兼容性查询网站,地址是:www.caniuse.com/]网站中查询到兼容性如下。
Rem适配方案在主流浏览器中整体支持性98.93%,而vw适配方案在主流浏览器中整体支持性94.44%,并且对于Android4.4之前的机型来说vw不支持是硬伤,毕竟这部分机型的市场占有率还是有一部分的。所以各位在选取适配方案时,要根据自己业务的场景来选择合适的方案,避免出现兼容性问题。
视口就是屏幕中,显示网页的区域;简单说,视口就是浏览器的窗口
视口是可以拖拽,即大小是可以改变的
可以通过查看视口的大小,来观察css像素和物理像素的比值
1 | 打开浏览器,查看html元素的宽度,即为视口的长度(一般只看宽度) |
默认情况下,审查元素查看html元素宽度:
1 | 视口宽度:1920px(css像素) |
放大网页时,可视区域变小,视口变小
放大两倍的情况,审查元素查看html元素宽度:
1 | 视口宽度:960px(css像素) |
在放大两倍的情况下,创建一个100px的正方形盒子,用截图工具量取物理像素时,是200px
要理解的是,css像素和物理像素不一样;我们可以通过改变视口的大小,来改变css像素和物理像素的比值
手机像素
在不同的屏幕,单位像素的大小是不一样的,像素越小屏幕越清晰
1 | 以电脑为例,24寸 1920 x 1080 px,可以算出一寸有多少像素 |
这里关注移动端,查看不同手机机型的分辨率
智能手机的像素点,远远小于计算机的像素点
问题:一个宽度为900px的网页,在iphone6中要如何显示呢?
1 | 创建宽为900px的盒子,审查元素调为手机端,查看iphone6机型的显示模式,发现还没有把宽度撑满 |
完美视口
移动端默认的(布局)视口大小是980px(css像素)
对于iphone6而言,移动端的像素比就是css像素/物理像素
980/750 = 1/0.77
,表示一个css像素,占0.77个物理像素
如果直接在网页中编写移动端代码,这样在980视口下,像素比非常不好,导致网页中的内容非常小
所以,编写移动端页面时,必须确保有一个比较合理的像素比
1 | 1 css像素 对应 2 物理像素 |
那么,怎么调整像素比呢?
1 | 可以通过改变视口的大小,来改变css像素和物理像素的比值 |
那么,怎么调整视口大小呢?
1 | 可以通过meta标签,来设置视口大小 |
完美视口
那么,css像素究竟写多少,像素比究竟是多少才最合适呢
每一款移动设备设计时,都会有一个最佳的像素比
一般我们只需将像素比设置为该值即可得到一个最佳效果,以iphone6为例,查看不同手机机型的分辨率 ,发现iphone6的最佳像素比为2,算出视口大小为375px,这样的视口刚好和iphone6实际的硬件宽度一致。
所以我们将像素比设置为最佳像素比的视口大小称为完美视口
那么,375px是iphone6的完美视口,会是其他机型的完美视口吗?
答案是否定的。
所以设置content = device-width
,device-width
是浏览器提供的变量,表示设备的宽度,即完美视口
设置完美视口
1 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
结论
以后再写移动端的页面,就把上面的代码写上。
vw单位
不同的设备,完美视口的大小是不一样的(硬件本身的设备宽度就不一样,导致了在css渲染时依赖的布局视口宽度不一样)
1 | iphone6 -- 375 |
由于不同设备视口和像素比不同,所以同样的375像素在不同的设备下,意义是不一样的
1 | 比如在iphone6,375就是全屏, |
完美视口的设置,只解决布局视口的依赖问题,但没有解决同一个css像素,在像素比下的呈现问题。
所以在移动端开发中,就不能再使用px来进行布局了
vw
表示的是视口宽度(viewport width)
- 100vw = 一个视口宽度
- 1vw = 1/100 = 1% 视口宽度
vw这个单位永远相当于视口宽度进行计算
1 | 在学习js之前,移动端可以用vw来进行适配 |
px与vw单位的比较
1 | <!-- |
iphone6机型
iphone6 plus机型
可以看到,在以vw为单位时,不同机型的呈现效果是同步的,因为是基于视口宽度动态计算的,而375px的盒子宽度,在iphone6上(布局视口750)是占据了一半,但在iphone plus(布局视口414)上并没有占据一般,所以对于移动端,单位不能用px写死,其本质是即使动态设置了布局视口,但不同机型的分辨率不一样,导致渲染的最终结果就不一样,不如让单位也动态计算。
设计图的宽度
现在一般设计图的宽度,都是按照苹果的375px的布局视口来做的
现在的设计图都是375的倍数
1 | 2倍图:750px3倍图:1125px |
设计图宽度 | 单位 |
---|---|
750px | vw |
现在想创建一个48 x 35 大小的元素(设计图里的大小)
1 | 100vw = 750px ----> 1px = 100/750vw = 0.13333vw |
em和rem
1em
= 一个字体大小
1rem
= 一个html的字体大小
em单位为一个相对的度量单位,它通过寻找父标签的font-size。然后通过计算得出自身的font-size。利用em单位设置便签的width或者height等属性原理也一样。
rem是一个灵活的、可扩展的单位,由浏览器转化像素并显示。与em单位不同,rem单位无论嵌套层级如何,都只相对于浏览器的根元素(HTML元素)的font-size。默认情况下,html元素的font-size为16px。
可以借助rem来存储上述的比值;
1 | html{ |
那么,现在想创建一个48 x 35 大小的元素(设计图里的大小)
可以写成下述形式吗
1 | html{ |
不能,因为网页字体大小,最小是12px,不能设置一个比12px还小的值(除了0),否则,字体自动设置为12px;
1 | 所以,当设置html{font-size:0.1333333vw}时,默认的值大小其实是12px;,所以实际展示的是盒子大小是 576 x 420px |
视口理论
Web开发:布局视口、视觉视口、理想视口
拓展:https://juejin.cn/post/6844904007756939271
拓展:浅谈移动端中的视口(viewport) - 掘金 (juejin.cn)
rem适配
实现rem适配要做两件事
- 把px设置成rem
- postcss-pxtorem
- …
- 根据设备宽度动态设置根节点的fontsize
- 手写
- amfe-flexible
- …
方案一
将px设置成rem
需要借助postcss-px2rem-exclude
插件:postcss-px2rem-exclude - npm (npmjs.com)
安装:npm i postcss-px2rem-exclude
(使用时有问题,废弃)
配置:根目录新建.postcssrc.js
1 | module.exports = { |
使用:
1 | header { |
结果:
问题,postcss-px2rem-exclude
库会导致一些问题
1 | npm install postcss-pxtorem --save-dev |
更新
使用postcss-pxtorem
这个库,对应的需要安装下autoprefixer
根目录新建.postcssrc.js
,也可以是postcss.config.js
1 | // https://www.npmjs.com/package/postcss-pxtorem |
注意
自定义动态设置根节点字体
新建src/utils/flexible.js
1 | (function flexible(window, document) { |
注意点
vue-cli4中配置
备注:这种方式还没用过,目前用的方案一
vue-cli4中配置移动端自适应postcss-pxtorem
1 | npm install postcss-pxtorem --save-dev |
amfe-flexible来根据屏幕动态改变根元素font-size,postcss-pxtorem把代码中px转为rem;在项目根目录建vue.config.js
vue.config.js内容:
1 | module.exports = { |
然后在main.js里引入amfe-flexible
1 | import Vue from 'vue' |
注:如果你创建时候选择了prettier格式化代码而且编译器也装了插件,你的css里不想被转化的”PX”编译器会自动帮你转化为”px”,此时只要在不想转化前一行加上
/* prettier-ignore */
即可
vue-cli5中配置
见方案一