一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第27天,点击查看活动详情
移动互联网的快速发展,让人们已经越来越习惯于使用手机来完成大部分日常的事务
但是移动端的屏幕大小多种多样,所以需要对移动端的屏幕进行适配
所以我们需要使用某种适配方案,来实现移动端页面的自适应
自适应: 网页会根据不同的设备屏幕大小来自动调整尺寸、大小
响应式: 网页会随着屏幕的实时变动而自动调整,是一种自适应的一种表现形式
视口
在一个浏览器中,我们可以看到的区域就是视口(viewport)
在PC端的页面中,我们是不需要对视口进行区分,因为我们的布局视口和视觉视口是同一个
但是在移动端网页的视口被划分为三种不同的类别:
分类 | 说明 |
---|---|
布局视口(layout viewport) | 移动网页真正用来布局的视口,默认的宽度为980px 会自动等比例进行缩放或拉伸,以使网页适应于视觉视口 |
视觉视口(visual layout) | 移动设备真正可见区域 |
理想视口(ideal layout) | 当布局视口 等于 视觉视口的时候,该视口就被称之为理想视口 |
设置布局视口
如果默认情况下,我们按照980px显示内容,那么右侧有一部分区域 就会无法显示,所以手机端浏览器会默认对页面进行缩放以显示到用 户的可见区域中
事实上这种方式是不利于我们进行移动的开发的,我们希望的是设置100px,那么显示的就是100px
此时我们可以通过设置布局视口,来阻止页面在移动端进行缩放
<!-- content中的多个值之间使用逗号进行分割,也可以使用分号或空格进行分割,推荐使用逗号 -->
<!--
user-scalable=no 属性可以禁止用户对网页进行自主的缩放
但是user-scalable这个属性 有一些小部分浏览器是不支持的
所以我们需要将最大缩放比和最小缩放比都设置1.0来兼容一些小部分浏览器
-->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0">
属性 | 值 |
---|---|
width | 可以是具体的值,如980px, 375px等 也可以是关键字device-width, 表示的是设备宽度 |
initial-scale | 设置网页的默认缩放比为1.0 其值可以是一个 0.0 和 10.0 之间的正数 |
user-scalable | 避免用户通过手指或鼠标键盘对网页进行缩放,从而破坏网页的布局 可选值为: yes 或者 no 默认值为yes 也可以设置为1或0 1代表yes,0代表no 但是还是推荐直接使用yes或no |
maximum-scale | 定义缩放的最大值,必须大于等于 minimum-scale,否则表现将不可预测 一个 0.0 和 10.0 之间的正数 |
minimum-scale | 定义缩放的最小值,必须小于等于 maximum-scale,否则表现将不可预测 一个 0.0 和 10.0 之间的正数 |
移动端布局方案
移动端的屏幕尺寸通常是非常繁多的,很多时候我们希望在不同的屏幕尺寸上显示不同的大小
比如我们设置一个100x100的盒子
- 在375px的屏幕上显示是100x100
- 在320px的屏幕上显示是90+ x 90+
- 在414px的屏幕上显示是100+ x 100+
其他尺寸也是类似,比如padding、margin、border、left,甚至是font-size等等
常见的适配方案:
- 百分比设置
- 因为不同属性的百分比值,相对的可能是不同参照物,所以百分比往往很难统一
- 所以百分比在移动端适配中使用是非常少的
- rem单位 + 动态html的font-size
- vw单位
- flex的弹性布局
- Grid布局
rem单位 + 动态html的font-size
rem单位是相对于html元素的font-size来设置的,
那么如果我们需要在不同的屏幕下有不同的尺寸,可以动态的修改html的 font-size尺寸
例如实现如下方案:
所以,在使用rem进行移动端适配的时候,主要需要实现如下问题:
- 针对不同的屏幕,设置html不同的font-size
- 将原来要设置的尺寸,转化成rem单位
针对不同的屏幕,设置html不同的font-size
方案一: 使用媒体查询
可以通过媒体查询来设置不同尺寸范围内的屏幕html的font-size尺寸
@media screen and (min-width: 320px) {
:root {
font-size: 12px;
}
}
@media screen and (min-width: 375px) {
:root {
font-size: 14px;
}
}
@media screen and (min-width: 414px) {
:root {
font-size: 16px;
}
}
@media screen and (min-width: 480px) {
:root {
font-size: 18px;
}
}
.box {
width: 10rem;
height: 10rem;
background-color: skyblue;
}
媒体查询实现移动端适配的缺点:
- 我们需要针对不同的屏幕编写大量的媒体查询,
- 如果动态改变尺寸,不会实时的进行更新
- 因为编写的媒体查询是一个范围值
- 只有触发范围边界值的时候才会触发元素大小的改变
- 并不会做到随着屏幕大小的改变,元素进行实时大小的改变
方案二: 编写JavaScript代码
如果希望实时改变屏幕尺寸时,font-size也可以实时更改,可以通过js代码
- 根据html的宽度计算出font-size的大小,并且设置到html上
- 监听页面的实时改变,并且重新设置font-size的大小到html上
事实上, lib-flexible库 实现了相同的功能,我们可以在开发中,直接使用lib-flexible库来完成相同的功能
// 编写为一个IIFE,避免污染全局变量,并可以在加载的时候自动执行
(function flexible (window, document) {
// 获取HTML元素
const htmlEl = document.documentElement
// 获取当前设备的dpr
const dpr = window.devicePixelRatio || 1
// adjust body font size
/*
因为font-size是一个可继承的css属性
所以在html上设置的font-size会依次继承
导致在默认情况下,默认的字体大小变得很大
所以需要在body中对字体进行重置,以免默认字体过大
*/
function setBodyFontSize () {
// document.body 用于获取body元素
if (document.body) {
// body的字体乘以dpr以便于默认字体的大小也可以自适应跳转
// document.body.style.fontSize = (12 * dpr) + 'px'
// 上述代码可以写成如下形式
document.body.style.fontSize = (12 / (htmlEl.clientWidth / 10) * dpr) + 'rem'
}
else {
// 如果在执行setBodyFontSize的时候,混 界面没有加载完毕
// 那么就等到界面加载完毕以后再次执行setBodyFontSize
document.addEventListener('DOMContentLoaded', setBodyFontSize)
}
}
// 在执行函数的时候进行调用
setBodyFontSize();
// set 1rem = viewWidth / 10
function setRemUnit () {
// 获取页面的宽度
const width = htmlEl.clientWidth
// 设置rootFontSize
// 10在这里只是一个参考值,也可以是别的值
const rem = htmlEl.clientWidth / 10
htmlEl.style.fontSize = rem + 'px'
}
// 初始化的时候,不会触发resize函数,需要手动调用
setRemUnit()
// reset rem unit on page resize
// 监听页面改变事件
window.addEventListener('resize', setRemUnit)
// 页面通过前进后退进入的时候,会读取缓存,需要重新设置
window.addEventListener('pageshow', function (e) {
if (e.persisted) {
setRemUnit()
}
})
// detect 0.5px supports
// 只有dpr大于等于2的时候才有可能支持0.5px
// 因为此时0.5px的逻辑像素可以对应1px的物理像素
if (dpr >= 2) {
// create fake body
const fakeBody = document.createElement('body')
const testElement = document.createElement('div')
testElement.style.border = '.5px solid transparent'
fakeBody.appendChild(testElement)
htmlEl.appendChild(fakeBody)
// 如果 0.5px border-top + 0.5px border-bottom = 1px 成立
// 就表示当前浏览器支持0.5px
if (testElement.offsetHeight === 1) {
// 如果支持0.5px,就在html元素上添加一个名为hairlines的特殊样式
htmlEl.classList.add('hairlines')
}
// remove fake body
htmlEl.removeChild(fakeBody)
}
}(window, document))
rem单位的换算
- 手动换算
比如有一个在375px屏幕上,100px宽度和高度的盒子
我们需要将100px转成对应的rem值
100/37.5=2.6667,其他也是相同的方法计算即可
- 使用less/scss函数
// px -> rem
.px2rem(@px) {
// 推荐将 1rem 写在最前边 是因为less会以第一个单位作为最终单位
res: 1rem * (@px / 37.5)
}
.box {
width: .px2rem(100)[res];
height: .px2rem(100)[res];
background-color: gray;
}
.lorem {
font-size: .px2rem(18)[res];
}
这是一个postcss的插件,通过结合webpack,vite之类的打包工具可以实现自动的px2rem
- 使用VScode插件,px to rem 在编写的时候,自动进行转化
在绝大多数情况下,一般是以iPhone6的宽度作为设计稿的设计标准
而iphone6的宽度为750px,又因为iPhone6的dpr是2,所以iphoe6的视口宽度是375px
但是px to rem 默认的rootFontSize为16px, 所以我们需要对该插件进行对应的配置
如图,点击配置按钮,进入插件的配置页面
vw单位
rem事实上是作为一种过渡的方案,它利用的也是vw的思想
前面不管是我们自己编写的js,还是flexible的源码,都是将1rem等同于设计稿的1/10,在利用1rem计算相对于整个屏幕的尺寸大小
但是1vw本质上,就是视口宽度的一百分之一,所以使用vw进行移动端适配的时候,不需要自己再去编写对应的适配函数或使用第三方库,因此vw的使用相比rem的使用更为的方便
vw相比rem的好处:
- 不需要去计算html的font-size大小,也不需要给html设置这样一个font-size
- 因为没有设置html的font-size大小,所以也就不需要给body再设置一个font-size,以防止继承
- vw相比于rem更加语义化,1vw刚才是1/100的viewport的大小
但是vw相比rem有一个缺点:
因为vw是相对于视口的,所以如果视口改变,对应的基于vw的值也会相应的发送改变,因此没有办法设置页面的最大宽度
如果我们希望页面放大到一定的宽度,例如600px的时候,页面不在相应的放大,此时依旧需要结合rem来进行使用
:root {
/* 375 / 10 = 37.5 */
font-size: 10vw;
}
.box {
width: 2.6667rem;
height: 2.6667rem;
background-color: gray;
}
.lorem {
font-size: 0.48rem;
}
但是,其实1vw是视口宽度的一百分之一,所以上边的代码可以修改为
.box {
width: 26.6667vw;
height: 26.6667vw;
background-color: gray;
}
.lorem {
font-size: 4.8vw;
}
vw的单位换算
-
手动换算
比如有一个在375px屏幕上,100px宽度和高度的盒子 -> 1vw === 3.75px
我们需要将100px转成对应的vw值
100/3.75=26.667,其他也是相同的方法计算即可
-
使用less/scss函数
.px2vw(@px) {
res: (@px / 3.75) * 1vw
}
.box {
width: .px2vw(100)[res];
height: .px2vw(100)[res];
background-color: gray;
}
.lorem {
font-size: .px2vw(18)[res];
}
-
使用postcss的插件postcss-px-to-viewport-8-plugin在打包的时候进行自动转换
-
使用px to rem插件在编写的时候自动进行转换
vw 结合 rem一起使用
vw的好处是计算方便,我们可以不使用JavaScript脚本就可以完成移动端的适配
但是vw没有办法设置最大的宽度,所以在实际开发中,我们一般会将vw和rem结合在一起进行使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
:root {
box-sizing: border-box;
/*
在M站的时候,假设设计稿的宽度为375px
1rem = 4vw = 3.75 * 4 = 15px
1px = 1 / 15px ~= 0.0667rem(保留小数后 4 位)
所以当宽度为100%的时候,正好是25rem
*/
font-size: 4vw;
}
@media screen and (min-width: 600px) {
:root {
/*
M站页面在PC端显示的时候,字体大小默认为24px
viewport >= 600px 时,1rem = 600px / 100 * 4 = 24px
(因为我们限制了 div.-mobile-wrapper 的 max-width 为 600px)
*/
font-size: 24px;
}
}
/* 重置样式 */
body {
margin: 0;
padding: 0;
}
/* M站页面需要在最外层容器上使用该类进行包裹,以保证页面的最大宽度为600px */
/* 全局 css 以 - 开头 */
.-mobile-wrapper {
max-width: 600px;
min-height: 100vh;
background: #f5f5f5;
/*
添加底部安全区的距离,避免iphone手机 页面被底部的圆角和小黑条所遮挡
如果设计稿的底部没有任何的内容需要显示的情况下,这个属性可以被移除
*/
padding-bottom: calc(env(safe-area-inset-bottom));
/* M站在PC显示的时候,页面可以居中显示 */
margin: 0 auto;
/* 使 absolute 子元素基于此容器定位 */
position: relative;
/* 建立 BFC,防止第一个子元素的 margin-top collapsing */
display: flow-root;
}
/* ----------------------------------- */
/* 自定义的CSS样式 */
.wrapper {
background-color: gray;
}
.box {
width: 6.6667rem;
height: 6.6667rem;
background-color: skyblue;
}
.lorem {
font-size: 1.2rem;
}
</style>
</head>
<body>
<div class="-mobile-wrapper wrapper">
<div class="box"></div>
<span class="lorem">lorem</span>
</div>
</body>
</html>