
移动端布局适配的方案
视口
视口(viewport)就是可视区域的大小。在前端页面布局中,一共有三种类型的视口:理想视口(ideal viewport),布局视口(layout viewport),视觉视口(visual viewport). 需要注意的是,在PC端,我们一般只有一个视口,就是pc浏览器的大小,而在移动端,采用上面的三种视口。
1. 布局视口
也就是我们html文档整个页面的布局大小(网页的宽度)。当我们以百分比来指定一个元素的大小时,它的计算值是由这个元素的包含块计算而来的。当这个元素是最元素时,它就是基于布局视口来计算的。
在PC浏览器上,布局视口就等于当前浏览器的窗口大小(不包括borders, margins, 滚动条)。 在移动端,布局视口默认为980px,这保证PC的网页可以在手机浏览器上呈现,但是非常小,用户可以手动进行放大。
获取方式:
document.documentElement.clientWidth/clientHeight
复制代码
2. 视觉视口
就是我们人眼能看到的可视区域大小,默认等于浏览器的宽度。
当用户对浏览器进行缩放时,不会改变布局视口的大小,所以页面布局是不变的,但是缩放会改变视觉视口的大小。 比如:将浏览器窗口放大200%,css像素会随着视觉视口的放大而放大,这时一个css像素会跨越更多物理像素
小结:布局视口会限制你的CSS布局,视觉视口决定用户具体能看到什么。
获取方式:
window.innerWidth/innerHeight
复制代码
3. 理想视口:
布局视口在移动端展示的效果并不是一个理想的效果,因为不同屏幕大小的移动设备视觉视口都不一样,而布局视口默认都是固定的,因此展示效果不理想,所以出现了理想视口,网页在移动端展示的理想大小
页面的缩放系数 = CSS像素/设备独立像素, 实际上 = 理想视口宽度/视觉视口更准确。当页面缩放比例为100%时,CSS像素 = 设备独立像素,理想视口 = 视觉视口
获取方式:
screen.width/height
复制代码
获取浏览器大小的常用api
上面已经介绍过三种视口的大小的获取方式,其实在实际应用中,还有一些其他的常见api。
- window.innerHeight: 获取视觉视口的高度(包括滚动条)
- window.outerHeight: 获取浏览器窗口外部的高度,表示整个浏览器窗口的高度,包括侧边栏、窗口镶边和调正窗口大小的边框。
- window.screen.height: 获取屏幕理想视口的高度,这个数值是固定的,等于设备的分辨率/设备的像素比
- window.screen.availHeight: 浏览器窗口可用的高度
- document.documentElement.clientHeight: 获取布局视口的高度,包括内边距,但不包括滚动条,边框和外边距
- document.documentElement.offsetHeight: 获取布局视口的高度,包括内边距,滚动条,边框和外边距
- document.documentElement.scrollHeight: 在不使用滚动条的情况下适合视口中的所有内容所需的最小宽度。与clientHeight对应,包括滚动条,边框和外边距
常见的布局适配方案
1. 设置视口
基于上面对视口的介绍,我们在适配浏览器的时候,通常需要在meta中给viewport设置视口和缩放,从而达到理想视口的情况。通常设置如下:
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
复制代码
配置说明:
- width:以正整数或像素为单位,定义布局视口的宽度。这里我们设置width=device-width就是让布局视口的宽度等于设备宽度。也就是让布局视口等于视觉视口
- initial-scale: 0.0 – 10.0, 定义页面初始缩放比率。
- minimum-scale: 0.0 – 10.0, 定义缩放的最小值。
- maximum-scale: 0.0 – 10.0 定义缩放的值。
- user-scalable:布尔值(yes或者no),如果设置为 no,用户将不能放大或缩小网页。默认值为 yes。这几个缩放的属性是用于不让用户缩放,这样布局视口就会一直等于视觉视口
需要注意的一点是:我们在做移动端做适配的时候,不仅是只有宽度需要适配,盒子的高度,边框,内外边距以及font-size都要适配。
2. 百分比
百分比这种就是将每一个盒子的宽高等都设置成百分比,这样不同设备的布局视口不一样,每次都是根据百分比来计算,也能得到适配的效果。
优点
- 上手容易,适配方案易理解
缺点
- 不同的css属性的百分比所基于的标准不一样
- 每个属性都需要手动计算百分比,很麻烦
3. rem
rem是相对单位。rem适配方案是基于html的font-size来计算的,即1rem = html的font-size。不论在什么屏幕下,css属性的rem是不变的,变化的是html的font-size,这样最后浏览器将rem转成px渲染出来也就能达到一个适配不同屏幕啦。 如: 设计稿以750px为标准, 某个盒子的宽:300px, 高: 300px 按照设计稿的编码: html的font-size = 1rem = 750/10 = 75px 盒子的width = 300/750*10 = 4rem
屏幕宽度 | html的font-size | 盒子的宽度 |
---|---|---|
640px | 640/10 = 64px | 64*4 = 264px |
480px | 480/10 = 48px | 48*4 = 192px |
320px | 320/10 = 32px | 32*4 = 128px |
上述html的font-size(也就是rem)需要根据布局视口的大小进行改变,这一步一般有两种方法:
3.1. rem的实现方式
3.1.1 自己实现js动态转换
3.1.1.1 首先要根据设计稿的尺寸将元素的格式都转成rem
我们这里采用的公式如下: 1rem = 设计稿的尺寸 / 0 元素的width = 设计稿上元素的宽度 / 设计稿的尺寸 * 10
这里我们可以写一个px转成rem的函数,以scss为例, 设计稿尺寸为750。
@function rem($px) {
@return $px / 750 * 10rem;
}
复制代码
3.1.1.2 将上述函数引入全局
这里我们以vue项目为例,将上述函数所在的scss文件引入main.js中
// main.js中
import '@scss/index.scss';
复制代码
3.1.1.3 在页面中使用rem
设计稿是什么尺寸大小,函数里的参数就是什么值
// 比如750设计稿里的width是300
width: rem(300);
复制代码
3.1.1.4 根据屏幕动态设置html的font-size
上述步骤只是实现了设计稿的尺寸下rem为单位的正常显示,现在需要根据不同屏幕动态设置html的font-size。
function setRem() {
const htmlEl = document.documentElement;
const htmlWidth = htmlEl.clientWidth;
const htmlFontSize = htmlWidth / 10;
htmlEl.style.fontSize = htmlFontSize + 'px';
};
// 次进入页面调用
setRem();
window.addListenner('resize', setRem);
复制代码
实现了上述逻辑之后,我们就可以实现rem适配不同屏幕了。
当然这一步也可以使用媒体查询去根据不同尺寸的屏幕设置html的font-size.
/* pc width > 1100px */
html{ font-size: 20px;}
/* ipad pro */
@media screen and (max-width: 1024px) {
html{ font-size: 18px;}
}
/* ipad */
@media screen and (max-width: 768px) {
html{ font-size: 16px;}
}
/* iphone6 7 8 plus */
@media screen and (max-width: 414px) {
html{ font-size: 15px;}
}
/* iphoneX */
@media screen and (max-width: 375px) and (-webkit-device-pixel-ratio: 3) {
html{ font-size: 14px;}
}
/* iphone6 7 8 */
@media screen and (max-width: 375px) and (-webkit-device-pixel-ratio: 2) {
html{ font-size: 13px;}
}
/* iphone5 */
@media screen and (max-width: 320px) {
html{ font-size: 12px;}
}
复制代码
3.1.2. 使用插件
如何是使用插件的话,主要会用到两个插件:
- lib-flexible: 这个插件主要是用于将适配不同屏幕,设置对应的html的font-size。
- px2rem-loader: 这个插件主要是用来将页面上设置的样式的px转成rem, 也就是我们上面写的rem函数。
使用步骤:
3.1.2.1 安装插件
npm i lib-flexible px2rem-loader --save-dev
复制代码
3.1.2.2. 配置
main.js中引入lib-flexible
// 引入lib-flexible
import 'lib-flexible/flexible';
复制代码
vue.config.js中配置px2rem-loader
// 在loaderOptions中添加如下配置
postcss: {
plugins: [
require('px2rem-loader')({
remUnit: 75
})
]
}
复制代码
特别需要注意的是:remUnit: 75这里的75是相对于设计稿给的是750的尺寸,如果设计稿给的是375,那么这里的值为37.5.
3.1.2.3. 使用
重启项目后我们就可以写我们的样式了。
特别注意:
1) postcss-px2rem和postcss-pxtorem的区别
场景:我的项目中安装了autoprefixer,安装postcss-px2rem插件后项目启动报错:node.getIterator is not a function postcss
原因:postcss-px2rem插件在全局引入时,main.js导入样式文件报错。
解决方案:卸载postcss-px2rem, 安装postcss-pxtorem.然后在postcss.config.js中按照如下配置:
// 安装postcss-pxtorem
npm install postcss-pxtorem --save-dev
复制代码
// postcss.config.js
const autoprefixer = require('autoprefixer')
const px2rem = require('postcss-pxtorem')
module.exports = {
plugins: [autoprefixer(), px2rem({ rootValue: 75, unitPrecision: 5, propList: ['*'] })]
}
复制代码
参数配置如下:
- rootValue: 换算基数,即根元素字体大小。如何设计稿是750,这里设置成75,如果设计稿是375, 这里设置成37.5
- unitPrecision: 允许rem单位增长的十进制数
- propList: 可以从px更改为rem的属性
2)lib-flexible和amfe-flexible
amfe-flexible是lib-flexible的升级方案 lib-flexible是一种过渡方案,不能与响应式布局兼容。由于viewport单位得到众多浏览器的兼容,上面这种方案现在已经被官方弃用,不管是现在的版本还是以前的版本,都存有一定的问题。建议大家开始使用viewport来替代此方。amfe-flexible的缺点是字体大小适配问题。
flexible的核心代码
// set 1rem = viewWidth / 10
function setRemUnit () {
var rem = docEl.clientWidth / 10
docEl.style.fontSize = rem + 'px'
}
setRemUnit();
// reset rem unit on page resize
window.addEventListener('resize', setRemUnit)window.addEventListener('pageshow', function (e) {
if (e.persisted) {
setRemUnit()
}
})
复制代码
3.2 优缺点
优点:可以根据屏幕显示合适的大小,能够适配不同屏幕 缺点:
- 在响应式布局中,必须通过js来动态控制根元素font-size的大小,也就是说css样式和js代码有一定的耦合性,且必须将改变font-size的代码放在css样式之前,如果改变系统的字体大小,页面会错乱
4. vw/vh
vw/vh这种方案是将视觉视口的宽度/高度分为100份,其实上面的flexible方案就是模仿这种方法,因为当时vm的兼容性不好,flexible就变成了一种过渡方案。
- vw(viewport’s width): 1vw等于视觉视口宽度的1%
- vh(viewport’s height): 1vh等于视觉视口高度的1%
- vmin: vw和vh的最小值
- vmax: vw和vh的值
如果设计稿的标准是375px, 某个元素的宽度是75px。那么1vw = 375 / 100 = 3.75px, 75 / 3.75 = 20vw,设置元素的宽度width: 20vw。 如果每个css属性都这样去计算的话工作量太大,这里也可以像rem一样,自己去手动计算,或者使用插件。
4.1 手动将px转化成vw
// index.scss
@function pxToVw($px) {
@return $px / (750 / 100) + 'vw';
}
复制代码
4.2 使用插件postcss-px-to-viewport
4.2.1. 安装插件
npm install postcss postcss-loader postcss-px-to-viewport -D
复制代码
4.2.2. 配置插件
在postcss.config.js中配置以下代码
plugins: {
autoprefixer: {},
"postcss-px-to-viewport": {
unitToConvert: 'px', // 把什么单位转换成vw
viewportWidth: 750, // 这个可以按照你的设计稿来设置,是750就设置750,375就设置成375
unitPrecision: 6, // 转换成vw单位的小数点后的保留位数
propList: ['*'], // 属性列表,表示你要把哪些css属性的px转换成vw,这个*表示所有
viewportUnit: 'vw', // 使用的单位,目前可选单位有vw,vh。一般我们都有vw
fontViewportUnit: 'vw', // 字体使用的单位
selectorBlackList: [], // 匹配不被转换为vw的选择器
minPixelValue: 1, // 需要转换的最小值,一般1px像素不转换,以上才转换
mediaQuery: false, // 允许在媒体查询中转换px
replace: true, // 替换包含vw的规则,而不是添加回退
exclude: [], // 忽略一些文件,比如“node_modules”,可以是正则表达式
landscape: false, // ......
landscapeUnit: 'vw', // ......
landscapeWidth: 568 // ......
}
}
复制代码
4.2.3. 使用插件
安装完成之后重启项目我们就可以像以前一样使用px了,但是在浏览器中我们可以看到所有的单位都被转成了vw。 代码:
.test_box {
width: 300px;
height: 300px;
background-color: aquamarine;
}
复制代码
浏览器效果: image.png
4.3 优缺点
优点:上手简单,易于理解
缺点:
- px转换成vw不一定能整除,因此会有一定的像素差
- 比如当容器使用vw,margin采用px时,很容易造成整体宽度超过100vw,从而影响布局效果。当然我们也是可以避免的,例如使用padding代替margin,结合calc()函数使用等等…
5. flex布局
flex布局主要是用来实现弹性布局,比如页面的多列的响应式布局,总之弹性布局是适配多种屏幕的响应式布局的一种补充。flex布局的内容可以参考我的另一篇文章:
在做移动端适配的时候如何选择
对于大部分主流浏览器来说,我们可以选择使用vw的方案(postcss-px-to-viewport)来实现移动端的适配,如果你所在公司项目需要适配很早之前的浏览器,那么你可以配合使用(amfe-flexible + postcss-pxtorem)的方案去适配。当然,这些适配方案能适配大多数屏幕,但是对于一些特殊的机型,比如刘海屏等可能会出现一些样式不正常的情况,对于这种情况,我们可以再进行特定的优化和调试。
比如,对于有圆角,刘海屏,小黑条等,为了适配这些手机,产生了安全区域的概念,安全区域指的就是不受前面这几种情况的影响,把页面限制在安全区域内,使页面显示正常。
特殊屏幕的问题
1. viewport-fit
这个属性就是为了适配上述的屏幕,用于限制网页在安全区域内进行展示。它一共有两个属性值
- contain:视觉视口完全包含网页内容
- cover: 网页内容完全覆盖视觉视口
<meta name="viewport" content="viewport-fit=cover">
复制代码
2. env、constant
我们需要将顶部和底部合理地摆放在安全区域内,ios11新增了两个CSS函数env、constant, 用于设定安全区域与边界的距离。函数的内部可以是四个常量:
- safe-area-inset-left: 安全区域距离左边边界距离
- safe-area-inset-right: 安全区域距离右边边界距离
- safe-area-inset-top: 安全区域距离顶部边界距离
- safe-area-inset-bottom: 安全区域距离底部边界距离
使用方式
这个函数必须是指定viewport-fit之后才可以使用
<meta name="viewport" content="viewport-fit=cover">
body {
// constant在iOS < 11.2的版本中生效,env在iOS >= 11.2的版本中生效
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
复制代码
常见问题
1. 1px问题
现象:在有的手机屏幕上,1px的大小看起来很粗
产生原因:在设备像素比大于1的屏幕上,我们写的1px实际上是被多个物理像素渲染。
解决方案:
// 伪类 + transform
.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);
}
}
复制代码
2. 图片模糊问题
现象:我们平时使用的图片都是位图(PNG, JPG格式),在有些手机上显示很模糊
产生原因:位图的每个像素对应在屏幕上使用一个物理像素来渲染,,而在dpr > 1的屏幕上,位图上的一个像素对应屏幕上的多个物理像素来渲染,然而这些物理像素点并不能被准确的分配上对应位图像素的颜色,只能取近似值,所以相同的图片在dpr > 1的屏幕上就会模糊。
解决方案:
- 在dpr=2的屏幕上使用两倍图(@2x), 在dpr=3的屏幕上展示三倍图(@3x) 这里我们主要分为两种情况。图片是背景图片还是img标签1.1 背景图1.1.1 采用媒体查询的方式:
.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); } } 复制代码
1.1.2 使用-image-set
.avatar { background-image: -webkit-image-set( "conardLi_1x.png" 1x, "conardLi_2x.png" 2x ); } 复制代码
1.2 img标签
使用img的srcset属性,浏览器会根据像素密度匹配显示适当的照片
<img src="conardLi_1x.png" srcset=" conardLi_2x.png 2x, conardLi_3x.png 3x"> 复制代码
- 使用svg图片格式
<img src="conardLi.svg"> <img src="data:image/svg+xml;base64,[data]"> .avatar { background: url(conardLi.svg); } 复制代码
文章来源网络,作者:运维,如若转载,请注明出处:https://shuyeidc.com/wp/114590.html<