屏幕适配,一直是作为一个前端开发始终逃不掉的问题,这个话题可以追溯到最开始的PC端浏览器的不同分辨率,再到移动端不同的屏幕尺寸,一直伴随着前端工程师的日常的页面开发工作。所谓屏幕适配,可以理解为一个网页元素或者网页布局,在不同尺寸,分辨率等场景下,如何呈现最佳的效果。
从最早的PC端屏幕来说,大部分的屏幕适配采取的是:
- 页面框架最外层元素宽度固定,并且居中,高度随内容自适应,比较常见的是宽度为960px~1080px。
- 页面内部的元素大多数使用盒子模型构建,采用固定宽高,当内容超出时,会出现滚动条。
- 对于一些需要根据屏幕不同而展示不同大小的元素,可以给元素设置百分比的单位。
随着HTML5和CSS3的到来,逐渐出现了弹性布局(flex布局),媒体查询Media Query,和响应式页面概念,这些特性都可以应用在PC端以及移动端屏幕适配解决方案中。除了这些之外,还有rem和vw方案更加有针对性的解决移动web页面的适配问题。
Viewport视窗
在HTML代码的<head>
标签中,都有一行设置的代码,如下:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
这行代码的作用就是设置浏览器的视窗大小,具体的含义我们后面在介绍,在讲解视窗之前,我们首先需要了解一下什么是物理像素和CSS像素。
物理像素和CSS像素
像素,也就是px,实际是pixel的缩写,它是图像显示的基本单元,每个像素可以有色彩数值和位置,每个图像是由若干个像素组成,比如对一幅标有1024×768像素的图像,就表明这幅图像的长边有1024个像素,宽边有768个像素,共有1024×768=786432个像素组成。但是从概念上来说,像素既不是一个确定的物理量,也不是一个点或者小方块,而是一个抽象概念。所以像素所代表的具体含义要从其处于的上下文环境来具体分析。物理像素和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像素的概念之后,然后就需要引入下一个概念,移动设备中的视窗,视窗就是浏览器显示页面内容的屏幕区域,有3种不同的类别,主要分为:
- 物理视窗(Visual Viewport):表示物理屏幕的可视区域,屏幕显示器的物理像素,也就是长宽边上有多少个像素点。同样尺寸的屏幕,像素点越多,像素密度越大,它的硬件像素会更多。可以理解成物理视窗的大小就是屏幕的大小。
- 布局视窗(Layout Viewport):是由浏览器厂商提出的一种虚拟的布局视窗,用来解决页面在手机上显示的问题。这种视窗可以通过
<meta>
标签设置viewport来修改。每个浏览器默认都会有一个设置,例如iOS,Android这些机型设置布局视窗宽度为980px,所以PC上的网页基本能在手机上呈现,只不过元素看上去很小,一般可以通过手指动双击缩放网页。 - 理想视窗(Ideal Viewport):理想中的视口。这个概念最早由苹果提出,其他浏览器厂商陆续跟进,目的是解决在布局视窗下页面元素过小的问题,显示在理想视口中的页面具有最理想的宽度,用户无需进行缩放。所以理想视窗就相当于把布局视窗修改成一个理想的大小,这个大小和物理视窗基本相等。
如下图,可以表示物理视窗和布局视窗的关系,底部的网页大小相当于布局视窗,而半透明灰色区域表示物理视窗大小,看起来就像一个手机屏幕大小。
所以如果想要在物理视窗里面完全展示布局视窗里的内容,肯定要将页面缩小。那么缩小到多少合适呢,就需要有理想视窗,如下图所示。
设置Viewport
对于移动端web页面,可以采用<meta>
标签对视窗的大小,缩放等进行配置,也就是之前提到的在<head>
标签内设置的<meta>
的代码如下:
<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,用户依然可以对页面进行手势操作来缩放。如果依然需要禁用,可以参考如下代码:
window.onload = function () {
document.addEventListener('touchstart', function(event) {
// 当两个手指操作
if (event.touches.length > 1) {
// 组织浏览器默认事件
event.preventDefault();
}
});
var lastTouchEnd = 0;
document.addEventListener('touchend', function(event) {
var now = (new Date()).getTime();
// 判断是否是双击操作,即两次点击间隔小于300ms
if (now - lastTouchEnd <= 300) {
// 组织浏览器默认事件
event.preventDefault();
}
lastTouchEnd = now;
}, false);
}
通过手势来进行缩放是属于浏览器的默认功能,上面代码的原理就是利用event.preventDefault()
方法,来禁用浏览器的默认事件,这样就不能触发这个默认的缩放功能。具体逻辑可以将代码运行之后看一下效果。
Viewport视窗的相关知识点是了解移动web适配的基础,通过动态的设置viewport可以实现不同屏幕下的页面适配,例如对设备像素比不为1的机型进行缩放,强制让物理像素和CSS像素相等,代码如下:
(function(){
var scale = 1/window.devicePixelRatio;
var meta = document.createElement("meta");
meta.name = "viewport";
meta.content = "width=device-width,initial-scale="+scale+",minimum-scale="+scale+",maximum-scale="+scale;
document.head.appendChild(meta);
})();
这种方法有时候不准确,比如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),代码如下:
@media screen and (min-width:461px){
html{
font-size:18px;
}
}
@media screen and (max-width:460px) and (min-width:401px){
html{
font-size:22px;
}
}
@media screen and (max-width:400px){
html{
font-size:30px;
}
}
上面代码中,使用screen媒体特性,来定义了3组屏幕宽度区间,当小于400px,大于401px且小于460px,大于461px,当屏幕宽度位于不同的区间时,则会应用上对应的<html>
的font-size。
另外一种则是使用Javascript动态设置<html>
的font-size,代码如下:
// 获取屏幕视窗宽度
let htmlWidth = document.documentElement.clientWidth || document.body.clientWidth;// 获取宽度最好有个兼容的方案,避免某些情况下第一种获取不到可以选择第二种
//获取html
let htmlDom = document.getElementsByTagName('html')[0];
htmlDom.style.fontSize = htmlWidth / 10 + 'px'; //求出font-size
上面代码中,得到屏幕宽度后,一般要除以一个系数,这里使用的系数是10,这样得到的font-size值更加灵活,适配性更强,所以实际应用当中,大多数采用的JavaScript来动态设置。如果想要实时监听屏幕大小的变化动态修改font-size,可以引入resize事件,代码如下:
window.addEventListener('resize',function(){
/*上面设置font-size的代码*/
})
计算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单位了,代码如下:
.button {
width: 5.3rem;
height: 10.6rem;
font-size:0.53rem;
background-color: red;
}
我们给一个元素采用了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代码中定义一个公式,代码如下:
@function px2rem($px){
$rem: 37.5;
@return ($px/$rem) + rem;// $px表示变量,+号表示拼接,rem为字符串相当于'rem'
}
.button {
width: px2rem(200);
height: px2rem(400);
font-size: px2rem(20);
background-color: red;
}
当然,上面的代码已经不是一个标准的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吗,通过标签:
<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单位了,代码如下:
.button {
width: 53vw;
height: 106vw;
background-color: red;
}
和计算rem值同理,也可以利用Sass来声明一个方法,做px到vw的转换,代码如下:
@function px2vw($px) {
$vw: 3.75;
@return ($px/$vw)+vw;// $px表示变量,+号表示拼接,vw为字符串相当于'vw'
}
.button {
width: px2vw(53);
height: px2vw(106);
background-color: red;
}
无论是转换成rem值还是vw值,在后续的实战项目中,都可以通过另一种方式来更加方便的转换。例如可以通过构建的方式,在代码中只需要写px值,通过配置一些插件和工具,最终生成的项目中就是转换好的代码,这就是前端工程化带来的便利。
Rem适配和vw适配兼容性
从上面的对两种相关的适配方案讲解,可以知道vw适配方案要优于Rem适配方案的,但是没有Rem流行就在于vw的兼容性问题,我们从caniuse[ caniuse是一个当下流行的前端技术兼容性查询网站,地址是:https://www.caniuse.com/]网站中查询到兼容性如下。
Rem适配方案在主流浏览器中整体支持性98.93%,而vw适配方案在主流浏览器中整体支持性94.44%,并且对于Android4.4之前的机型来说vw不支持是硬伤,毕竟这部分机型的市场占有率还是有一部分的。所以各位在选取适配方案时,要根据自己业务的场景来选择合适的方案,避免出现兼容性问题。
更多关于移动web相关的内容,欢迎关注课程:
《从0到1 实战朋友圈移动Web App开发》
《移动Web App开发之实战美团外卖》
热门评论
物理像素和css像素,确实学到了~
这里的rem布局方案与阿里的高清布局方案还有哪些方面的差异呢?