响应式布局
最近写第三个移动端 H5 的项目了,准备记录下自己在 H5 项目中的一些实践探索。移动端 H5 与 PC 端开发最大的区别之一,大概就是响应式布局问题。
那么下面我们来聊聊移动端响应式布局,了解他的来龙去脉,对现有的最佳解决方案探索。
问题
全文将围绕下面几个问题进行论述和展开:
- 写移动端 H5 相关页面,相比 PC 端有哪些值得注意的点?
- 关于H5 响应式布局有哪些解决方案?
- 什么是 rem?如何在项目中完美使用它?
- vh/vw 是最佳解决方案吗?它有什么优势和缺陷
- 大型开源库里面常用解决方案是什么?
- 怎样快速搭建一套移动端布局解决方案?
由来
概念
什么是 H5 技术?
H5 这个命名本身是一个很不讨巧的命名,咋一眼看上去认为 HTML5,或者第 5 级标题的标签,对一些造成一些不小的误解。
比如:我的一个某后端同事,谈论到 H5 很简单,HTML 之前我也学过一些,以后要是你请假,我来帮你写。
我是一脸蒙蔽,H5 === HTML?
如此看来,将 H5 视作 HTML 的大有人在,而 H5 这个概念只在中国特有,所以对外国人来说他们也认为是 HTML, 所以,对于外国人和非这个领域的人来说,他们存在一样的误解。
目前的 H5 算是一个比较大的概念了,我认为的 H5 技术是一系列移动端 web 前端技术的集合
我们这里只谈 web 前端中 H5 技术,H5 技术本身是用于移动端的 web 网页。由于App本身有个 “ webview ” 的容器,在容器里可以运行 web 前端相关代码,由此 H5 和原生 App 结合又衍生出来了 Hybrid App 技术。
Hybrid App 技术大致原理
这是我给公司同事普及 H5 知识绘制的图像。
解决方案一:rem + pxToRem
概念
css 中用于计量的单位有两种,一种是绝对单位,另一种是相对单位
绝对单位
单位 | 描述 |
---|---|
cm | 厘米 |
mm | 毫米 |
in | 英寸(1in= 96px = 2.54cm) |
px | 像素 |
pt | point (1pt = 1/72in) |
pc | pica 大约12pt(1pc = 12pt) |
对于绝对单位,一般来说常用的也就 px
, 其他的可能打印需要用到
相对单位
单位 | 描述 |
---|---|
em | 它是描述相对于应用在当前元素的字体尺寸,所以它是相对长度单位。一般浏览器字体大小默认为16px,则2em == 32px |
ex | 依赖于英文字母小x的高度 |
ch | 数字0的宽度 |
rem | rem是根(root em)的缩写,相对于根元素字体大小(相对的只是HTML根元素); |
vw | viewpoint width,视窗宽度,1vw = 视窗宽度的1% |
vh | viewpoint height,视窗高度,1vh = 视窗高度的1% |
vmin | vw和vh中较小的一个 |
vmax | vw和vh中较大的一个 |
对于相对单位来说,em
和 rem
属于一对,vw
和 vh
属于一对。
前一对相对于字体大小,区别在于 rem
相对于根字体,对于我们控制整体的大小相对容易些,所以我们可以使用它来控制整个页面的缩放。
后一对,相对于视窗的大小,这个将在下一个节中发挥主要作用。
原理
- 监听屏幕视窗的宽度,通过一定比例换算赋值给
html
的font-size
。此时,根字体大小就会随屏幕宽度而变化。 - 将
px
转换成rem
, 常规方案有两种,一种是利用sass
/less
中的自定义函数pxToRem
,写px
时,利用pxToRem
函数转换成rem
。另外一种是直接写px
,编译过程利用插件全部转成rem
。这样 dom 中元素的大小,就会随屏幕宽度变化而变化了。
实现
- 动态更新根字体大小
const MAX_FONT_SIZE = 420
// 定义最大的屏幕宽度
document.addEventListener('DOMContentLoaded', () => {
const html = document.querySelector('html')
let fontSize = window.innerWidth / 10
fontSize = fontSize > MAX_FONT_SIZE ? MAX_FONT_SIZE : fontSize
html.style.fontSize = fontSize + 'px'
})
px
转rem
pxToRem
方案一
$rootFontSize: 375 / 10;
// 定义 px 转化为 rem 的函数
@function px2rem ($px) {
@return $px / $rootFontSize + rem;
}
.demo {
width: px2rem(100);
height: px2rem(100);
}
pxToRem
方案二
vue-cli3
中配置 装 postcss-pxtorem
插件就可以了,其他平台大致差不多
const autoprefixer = require('autoprefixer')
const pxtorem = require('postcss-pxtorem')
module.exports = {
// ...
css: {
sourceMap: true,
loaderOptions: {
postcss: {
plugins: [
autoprefixer(),
pxtorem({
rootValue: 37.5,
propList: ['*']
})
]
}
}
}
}
继续探索postcss-pxtorem插件源码,查看它实现的原理
function createPxReplace (rootValue, unitPrecision, minPixelValue) {
return function (m, $1) {
if (!$1) return m;
var pixels = parseFloat($1);
if (pixels < minPixelValue) return m;
var fixedVal = toFixed((pixels / rootValue), unitPrecision);
return (fixedVal === 0) ? '0' : fixedVal + 'rem';
};
}
px
变换成 rem
主要是这个函数,当然里面有很多可配置的参数, 核心原理和我们方案一差不多,方便在于,不需要每次写px
都要加上一个函数,代码也清晰很多
是不是所有元素
px
都要转换成rem
呢?,那可不一定哦,border 中的px
不应该转 rem,涉及到另外一个 1px 的问题,上一篇文章详细论述过,避免 px 转 rem,将 border 中的 px 大写成 PX/Px/pX
1px 适配问题请移至 吃透移动端 1px
解决方案二:vh + vw
原理
vw
相对于视窗宽度的单位,随宽度变化而变化。由此看来,方案一其实是方案二的一种 Hack, 通过使用监听实现了方案二的效果
实现
与 rem 类似做法,直接使用 postcss-px-to-viewport 插件进行配置, 配置方式也是和 postcss-pxtorem 大同小异
我们看看插件的原理是不是也是一样的
function createPxReplace(opts, viewportUnit, viewportSize) {
return function (m, $1) {
if (!$1) return m;
var pixels = parseFloat($1);
if (pixels <= opts.minPixelValue) return m;
var parsedVal = toFixed((pixels / viewportSize * 100), opts.unitPrecision);
return parsedVal === 0 ? '0' : parsedVal + viewportUnit;
};
}
果然呢,连方法名、变量名、代码逻辑,都一摸一样哈哈哈,谁抄谁,我就不指出来啦 - -
其他解决方案
方案 | 缺陷 | |
---|---|---|
1 | 百分比 | 高度无法百分比 |
2 | 媒体查询 + meta 中 viewport | 不同设备宽度不同,缩放比无法完全确定 |
3 | flex | 还是无法解决宽度超出问题 |
上面方案均存在致命缺陷,不推荐使用它完成移动端布局计算。
flex 与 rem 结合使用更佳
兼容性
上述两种方案,兼容性主要在于 rem,vh,vw 关键词上
rem
在移动端表现高达 100%,令人惊叹!
vh vw
表现惨不忍睹
不得不说 rem
仍然是移动端 h5 布局的最佳方案
开源库解决方案
vant 组件库
vant 组件库中,默认采用 px 做计量单位,如果需要使用 rem,直接使用插件完美适配。
对于 vw 方案,vant 也是可以通过插件将 px 转成 vw,对于 vw 可能会存在一些坑点。
ant-design-mobile 组件库
ant-design-mobile 组件库仍然使用 px
单位
@hd: 1px; // 基本单位
// 字体尺寸
// ---
@font-size-icontext: 10 * @hd;
@font-size-caption-sm: 12 * @hd;
@font-size-base: 14 * @hd;
@font-size-subhead: 15 * @hd;
@font-size-caption: 16 * @hd;
@font-size-heading: 17 * @hd;
// 圆角
// ---
@radius-xs: 2 * @hd;
@radius-sm: 3 * @hd;
@radius-md: 5 * @hd;
@radius-lg: 7 * @hd;
@radius-circle: 50%;
// 边框尺寸
// ---
@border-width-sm: 1PX;
@border-width-md: 1PX;
@border-width-lg: 2 * @hd;
与 vant
组件一样,还是由开发者来决定到底用哪一种方案 这种把选择权交给开发者,算是一种开源库的最灵活的做法了。