1.1-移动端适配
Create by fall on 09 Jun 2022
Recently revised in 04 Apr 2023
移动端适配
很多项目都是移动端项目先开发,而后是 PC 端的开发,为了降低运营成本和开发成本,同一个网站要能兼容 PC 端和移动端。此时需要做移动端适配。
响应式,同一个网站兼容不同的大小的设备。如 PC 端、移动端(平板、横屏、竖排)的显示风格。
在移动端虽然整体来说大部分浏览器内核都是 webkit,而且大部分都支持 css3 的所有语法。但是,由于手机屏幕尺寸不一样,分辨率不一样,或者你需要考虑横竖屏的问题,或者考虑到各式各样的移动端兼容性问题。
像素大小
设备像素(设备分辨率):手机的物理像素分辨率,和显示器有关,设备像素和设备分辨率由操作系统控制并管理。
逻辑分辨率(设备独立像素、逻辑像素):逻辑分辨率用屏幕的 width * height
来表示(单位:设备独立像素)
CSS 像素:不会随着设备的变化而变化,是固定的,如果进行放大,放大的是 CSS 展示比例,像素大小不会随之改变。
屏幕的设备分辨率是
1920*1200
(单位:设备像素)我们可以在当前的分辨率下设置逻辑分辨率是1280*800
(单位:设备独立像素)
css 像素与设备独立像素的关系
在缩放比例为 100% 的情况下,1 个 css 像素大小等于 1*1
个设备独立像素;缩放比例为 200% 的情况下,1 css 像素等于 2*2
个像素。
css 像素与设备像素的关系
window.devicePixelRatio
获取设备像素比(同一直线上,设备像素的数量 / CSS 像素的数量)。
如果 devicePixelRatio = 2,表示设备像素的数量是 CSS 像素数量的 2 倍
viewport
layout viewport:布局窗口,即 html
元素的父容器,有时会无法展示出 layout viewport
全貌,但数据确实加载上了,这时候会出现滚动条
visual viewport:视觉窗口,显示出 layout viewport 的那一部分。
ideal viewport:理想视口,最适合移动设备的 viewport。只要在 css 中把某一元素的宽度设为 ideal viewport 的宽度(单位用 px ),那么这个元素的宽度就是设备屏幕的宽度了,也就是宽度为 100% 的效果。
对 viewport 进行控制
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<!-- 该 meta 标签的作用是让当前 viewport 的宽度等于设备的宽度,同时不允许用户手动缩放。 -->
属性 | 值 |
---|---|
initial-scale | 允许用户的最小缩放值,为一个数字,可以带小数 |
minimum-scale | 允许用户的最大缩放值,为一个数字,可以带小数 |
user-scalable | 是否允许用户进行缩放,值为"no"或"yes", no 代表不允许,yes 代表允许 |
适配方案
适配原则
- 开发时方便
- 方案要适配大多数手机屏幕,并且无 BUG
- 用户体验要好,页面看着没有不适感
media
@media
页面上需要根据宽度适配的元素需要在不同的 @media
中定义一遍
@media only screen and (min-width: 375px) {
.logo { width : 62.5px; }
}
@media only screen and (min-width: 360px) {
.logo { width : 60px; }
}
@media only screen and (min-width: 320px) {
.logo { width : 53.3333px; }
}
rem
假设设计稿尺寸为 750px,目标收集宽度为 420px
- 开发者拿到设计稿(设计稿的元素标是基于此宽度标注)
- 开发,对设计稿的标注进行转换
- 对于需要等比缩放的元素,CSS 使用转换后的单位
- 对于不需要缩放的元素,比如边框阴影,使用固定单位 px
假设设计稿的某个字体大小是 40px,手机屏幕上的字体大小应为 420/750*40 = 22.4px
(体验好),换算成 rem(相对于 html 根节点,假设 html 的 font-size = 100px)则这个字体大小为 0.224 rem。写样式时,对应的字体设置为 0.224 rem 即可,其他元素尺寸也做相同换算。
但是所有的数据都要进行运算,不合适,根据比例设定 rem,是更好的方法,更新 html 元素上的 font-size
为 (420*100)/750 = 56px
。
此时看到 40px,就直接写 40/100 = 0.4 rem(不用计算)
假设 html 的 font size = 1px 的话,就可以写 28 rem 了,更方便了,但是浏览器对字体大小有限制,最小值会为 12px(或者其他值),此时就会得到一个很夸张的结果,所以 html 的 font-size 要写的大一些。
根据不同屏幕宽度,设置 html 的 font-size 值
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1">
<script>
const WIDTH = 750 //设计稿尺寸
const setView = () => {
document.documentElement.style.fontSize = (100 * screen.width / WIDTH) + 'px'
}
window.onorientationchange = setView
// 使用 js 动态来设置根字体
window.onresize = setView
setView()
</script>
</head>
对于需要等比缩放的元素,CSS 使用转换后的单位
header {
font-size: .28rem;
}
对于不需要缩放的元素,比如边框,使用固定单位 px
header > span.active {
color: #fff;
border-bottom: 2px solid rgba(255, 255, 255, 0.3);
}
使用 sass 时,可以定义函数处理,但看着好看些,原理还是一样的
@function px2rem($px) {
@return $px * 1rem / 100;
}
header {
font-size: px2rem(28);
}
viewport
设置缩放
假设设计稿尺寸为 700px 宽度,需要适配 350 宽度的屏幕
- 开发者拿到设计稿(假设设计稿尺寸为 700px,设计稿的元素标注是基于此宽度标注)
- 对设计稿的标注进行转换,把 px 换成 vw。比如页面元素字体标注的大小是 32px,换成 vw 为
32 * (100/700)
vw,100/700 表示 1px 对应多少 vw - 对于需要等比缩放的元素,CSS 使用转换后的单位
- 对于不需要缩放的元素,比如边框阴影,使用固定单位 px
在 head 里设置如下代码
<meta name="viewport" content="width=700,initial-scale=0.5">
<!-- initial-scale = 屏幕的宽度 / 设计稿的宽度 -->
为了适配其他屏幕,需要动态的设置 initial-scale 的值
<head>
<script>
const WIDTH = 700
const mobileAdapter = () => {
let scale = screen.width / WIDTH
let content = `width=${WIDTH}, initial-scale=${scale}, maximum-scale=${scale}, minimum-scale=${scale}`
let meta = document.querySelector('meta[name=viewport]')
if (!meta) {
meta = document.createElement('meta')
meta.setAttribute('name', 'viewport')
document.head.appendChild(meta)
}
meta.setAttribute('content',content)
}
mobileAdapter()
window.onorientationchange = mobileAdapter // 屏幕翻转时再次执行
</script>
</head>
缺点就是边线问题,不同尺寸下,边线的粗细是不一样的(等比缩放后),全部元素都是等比缩放,实际显示效果可能不太好
postcss
因为每个屏幕的百分比是固定的、可预测、可控制的。
vw
:是 viewport's width 的简写,1vw 等于window.innerWidth
的 1%;vh
:是 viewport's height 的简写,1vh 等于window.innerHeihgt
的 1%;vmin
、vmax
:vmin
的值是当前window.innerWidth
和window.innerWidth
中较小的值,vmax 的值是较大的值
利用 PostCSS 减少计算,自动将 px 计算为 vw
npm install postcss-px-to-viewport --save-dev
因为会自动进行转换,当不需要转换时,使用下面的注释
.class {
/* px-to-viewport-ignore-next */
width: 10px;
padding: 10px;
height: 10px; /* px-to-viewport-ignore */
}
会解析如下内容
.class {
width: 10px;
padding: 3.125vw;
height: 10px;
}
Retina (视网膜屏)屏场景,可能对图片的高清程度、1px 等场景有需求,所以我们预留判断 Retina 屏坑位。
// index.html 文件
const dpr = devicePixelRatio >= 3? 3: devicePixelRatio >= 2? 2: 1;
document.documentElement.setAttribute('data-dpr', dpr);
控制 viewport
移动端直接控制 meta 中的 content 描述即可控制 viewport
<meta id="vp" name="viewport" content="width=750,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no">
<script>
var w1=window.innerWidth,scale=w1/750*0.5;
document.getElementById('vp').content='width=750,initial-scale='+scale+',maximum-scale='+scale+',minimum-scale='+scale+',user-scalable=no';
</script>
常见问题
1px
我们直接写 css 的大小 1px,那在 dpr = 2 时,则等于 2px 设备像素,dpr = 3 时,等于 3px 设备像素。
解决方案:
.calss1 {
position: relative;
&::after {
content:"";
position: absolute;
bottom:0px;
left:0px;
right:0px;
border-top:1px solid #666;
transform: scaleY(0.5);
}
}
图片不清晰
普通屏幕的图片在视网膜屏幕下,会出现模糊的情况,
如果全部应用高清图,浪费带宽,如果全部应用普通图,无法保证清晰,通过下面的方法实现适配
[data-dpr="1"] .hello {
background-image: url(image@1x.jpg);
[data-dpr="2"] .hello {
background-image: url(image@2x.jpg);
}
[data-dpr="3"] .hello {
background-image: url(image@3x.jpg);
}
安全区域
比如说因为曲面屏,因为屏幕下方的一条导航栏,导致最下方的数据和菜单被遮挡,所以采用安全区域,进行设置 margin
iphone 的 meta 标签中,viewport 添加了新的内容,可设置三个值
- contain:可视窗口完全包含网页内容
- cover:网页内容完全覆盖可视窗口
- auto:默认值,跟 contain 表现一致
谈谈移动端点击
移动端 300 ms 点击(click 事件)延迟
由于移动端会有双击缩放的这个操作,因此浏览器在 click 之后要等待 300ms,判断这次操作是不是双击。
解决方案:
- 禁用缩放:user-scalable=no
- 更改默认的视口宽度
- CSS touch-action
点击穿透问题
因为 click 事件的 300ms 延迟问题,所以有可能会在某些情况触发多次事件。
- 只用 touch
- 只用 click
插件
一般来讲,插件都是 postcss 插件,来处理 css
postcss-pxtorem
npm install postcss-pxtorem --save
vue 项目中,通过 vue.config.js
中添加以下内容,对该插件进行配置
const postToRem = require('postcss-pxtorem')({ // 把px单位换算成rem单位
rootValue: 16, // 初始根元素大小(换算基数)
unitPrecision: 3, //允许 REM 单位增长到的十进制数字,小数点后保留的位数。
propList: ['*'], // 需要从 px 转化为 rem 的属性,如:height,font-size 之类,或者通配符 *
exclude: /(node_module)/,
selectorBlackList: ['.van'],// 选择器白名单
mediaQuery: false, // 是否允许在 f12 查询中转换px,默认为false。
minPixelValue: 1 //设置要替换的最小像素值
})
module.exports={
css: {
loaderOptions: {
postcss: {
plugins: [postToRem]
}
}
}
}
随窗口变化
// 基准大小
const baseSize = 37.5
// 设置 rem 函数
function setRem () {
// 当前页面宽度相对于 750 宽的缩放比例,可根据自己需要修改。
const scale = document.documentElement.clientWidth / 750
// 设置页面根节点字体大小
document.documentElement.style.fontSize = (baseSize * Math.min(scale, 2)) + 'px'
}
// 初始化
setRem()
// 改变窗口大小时重新设置 rem
window.onresize = function () {
setRem()
}
参考文章
作者 | 链接 |
---|---|
我是你的超级英雄 | https://juejin.cn/post/7046169975706353701 |
MK麦客 | https://juejin.cn/post/7096367751626752008 |
弹性盒适配(合理布局)
<meta name="viewport" content="width=device-width">
总结一下,什么样的页面需要做适配(等比缩放)呢
- 页面中的布局是栅格化的
换了屏幕后,到底有多宽多高很难去做设置,整体的都需要改变,所以需要整体的缩放
- 头屏大图,宽度自适应,高度固定的话,对于不同的屏幕,形状就会发生改变(放到 ipad 上就变成长条了),宽度变化后,高度也要保持等比例变化
以上所有的适配都是宽度的适配,但是在某些场景下,也会出现高度的适配
比如大屏,需要适配很多的电视尺寸,要求撑满屏幕,不能有滚动条,此时若换个屏幕
此时需要考虑小元素用 vh, 宽和高都用 vh 去表示,中间的大块去自适应,这就做到了大屏的适配,屏幕变小了,整体变小了(体验更好),中间这块撑满了屏幕
对于更复杂的场景,需要更灵活考虑,没有一种适配方式可以囊括所有场景。
需要用到的技术
Media Query(媒体查询)
用于查询设备是否符合某一特定条件,这些特定条件包括屏幕尺寸,是否可触摸,屏幕精度,横屏竖屏等信息。
使用 em 或 rem 做尺寸单位
用于文字大小的响应和弹性布局。
禁止页面缩放
<meta name="viewport" content="initial-scale=1, width=device-width, maximum-scale=1, user-scalable=no" />
固定布局:页面居中,两边留白,他能适应大于某个值一定范围的宽度,但是如果太宽就会有很多留白,太窄会出现滚动条,在 PC 页面上很常见。
流动布局:屏幕尺寸在一定范围内变化时,不改变模块布局,只改变模块尺寸比例。比固定布局更具响应能力,两边不留白,但是也只能适应有限的宽度变化范围,否则模块会被挤(拉)得不成样子。
自定义布局:上面几种布局方式都无法跨域大尺寸变化,所以适当时候我们需要改变模块的位置排列或者隐藏一些次要元素。
栅格布局:这种布局方式使得模块之间非常容易对齐,易于模块位置的改变用于辅助自定义布局。
响应式设计注意事项
- 宽度不固定,可以使用百分比
#head{width:100%;}
#content{width:50%;}
- 图片处理
图片的宽度和高度设置等比缩放,可以设置图片的 width 为百分比,height:auto;
背景图片可以使用 background-size 指定背景图片的大小。