本文已参与「新人创作礼」活动,一起开启掘金创作之路。
常见的移动端适配方法
- 固定高度,宽度百分百:这种方法只适合简单的、要求不高的 webApp,几乎达不到大型项目的要求,属于过时方法;
- 媒体查询:这是一种比较主流的适配方案,很多优秀的 UI 框架都采用这种方案(如 Bootstrap),它能完成几乎大部分项目的需求,但是编写起来比较复杂;
- flex 布局 + rem:主流的布局方式,不仅适用于移动 Web,PC 端表现也良好,它能够完成大部分项目的需求,同时编写也比较简单,比较推荐使用。
rem 适配原理
rem 是 CSS3 引入的一个相对单位,它是指相对于根元素的字体大小的单位,它的值取决于<html>元素设定的 font-size 值的大小,即:1rem = 1(font-size 值);
根据 rem 的这个特性,我们得出来一种比较好的适配思路,即根据不同屏幕的宽度,以相同的比例动态的修改 html 的 font-size,从而实现根据屏宽控制根元素的字体大小,进而控制 rem 的显示比例,达到各种屏幕基本一致的体验效果。
rem 计算原理
- 屏宽为 clientWidth(px)。设计稿宽度为 750px,n = clientWidth / 750;
- 将 html 的 font-size 设置为 n(px)
- 此时已经确定 1(rem) = n(px)
- 假设有一个 div,在设计稿上测量的宽度 ruleW(px),则需要写入的宽度 width:w(px)
- 则有 w / clientWidth(px) = ruleW(px) / 750(px) 推导出 w = ruleW * n(px)
- 则有 w = ruleW * 1rem 推导出 w = ruleW(rem);
结论:当我们设置 html 的 font-size 为(屏宽 / 设计稿宽度)的 px 时,我们在设计稿上测量的 px 单位值可以直接转换为 rem 单位写到代码里面即可。
注意: 有一个很重要也很容易被忽略的问题,那就是浏览器存在最小字体,即字体大小不得小于 12px,而当我们设置根节点的 font-size 时,屏幕宽度/750 基本上 100%小于 12px,这个比例是无法通过浏览器的,所以我们要把它放大一百倍。
放大比例的计算规则如下:
- n = clientWidth / 750; n2 = clientWidth*100 / 750; n2 = 100n,即 n2 为放大 100 倍的 n;
- 将 html 元素的 font-size 设置为 n2;
- 1(rem) = n2(px) = 100n(px); 则 n = 1/100(rem);
- 假设有一个 div,在设计稿上测量的宽度 ruleW(px),则需要写入的宽度 width:w(px)
- 则有 w / clientWidth(px) = ruleW(px)/750(px) 推导出 w = (clientWidth / 750) * ruleW(px)
- w = n * ruleW(px) 推导出 w = 1/100 * n2 * ruleW(rem) = ruleW/100(rem);
结论:当我们设置 html 的 font-size 为(屏宽 * 100 / 设计稿宽度)的 px 时,当我们在设计稿上测得的 px 单位值,直接除以 100 换为 rem 单位写入代码即可。
配适方法
js 配适
<script type="text/javascript">
//手机端的适配
document.addEventListener("DOMContentLoaded", function() {
document.getElementsByTagName("html")[0].style.fontSize =
(document.documentElement.clientWidth / 750) * 100 + "px";
});
window.onresize = function() {
document.getElementsByTagName("html")[0].style.fontSize =
(document.documentElement.clientWidth / 750) * 100 + "px";
};
</script>
css 配适
移动端的rem布局需要通过JS设置不同屏幕宽高比的font-size,而采用这种方式就可以摆脱js的控制。不兼容低版本移动端系统。
html {
font-size: calc(100vw / 7.5);
}
less 配适
.re(@width) {
@xs: 100px/(750px/@width);
@media (max-width:(@width + 1px)) {
html {
font-size: @xs;
}
}
}
.re(1600px);
.re(1440px);
.re(1280px);
.re(1024px);
.re(960px);
.re(950px);
.re(900px);
.re(800px);
.re(773px);
.re(768px);
.re(736px);
.re(732px);
.re(731px);
.re(667px);
.re(640px);
.re(600px);
.re(568px);
.re(533px);
.re(435px);
.re(414px);
.re(411px);
.re(384px);
.re(375px);
.re(360px);
.re(320px);
scss
// 计算rem的基准字体
$rem-base-font-size: 100px;
// UI设计图的分辨率宽度
$UI-resolution-width: 750px;
// 需要适配的屏幕宽度
$device-widths: 240px, 320px, 360px, 375px, 390px, 414px, 480px, 540px, 640px, 720px, 768px,1080px, 1024px;
@mixin html-font-size() {
@each $current-width in $device-widths {
@media only screen and (min-width: $current-width) {
html {
$x: $UI-resolution-width / $current-width; //计算出是几倍屏
font-size: $rem-base-font-size / $x;
}
}
}
}
@include html-font-size();
@function pxToRem($px) {
@return $px / $rem-base-font-size * 1rem;
}
vue 移动端自适应实例
移动端设计稿一般都是以 750px 宽度为基础进行设计。页面自适应的大致思路就是在代码中 css 样式都用 px 为单位,然后再将 px 转换为 rem,再根据页面大小动态设置根元素 html 的 font-size,即可达到页面的自适应的效果。
以 vuecli4 创建的项目为例子,先安装 postcss-pxtorem 至项目中
npm install postcss-pxtorem
然后在 vue.config.js 文件中添加以下代码,即可将代码中的 px 转换为 rem 单位
module.exports = {
css: {
loaderOptions: {
postcss: {
plugins: [
require("postcss-pxtorem")({
rootValue: 75, //75px=1rem,这个值可以自行修改
propWhiteList: [],
minPixelValue: 2,
}),
],
},
},
},
};
然后在 public/index.html 中添加以下代码,设置 html 的 font-size,即可自动完成适配
(function(win, lib) {
var doc = win.document;
var docEl = doc.documentElement;
var metaEl = doc.querySelector('meta[name="viewport"]');
var flexibleEl = doc.querySelector('meta[name="flexible"]');
var dpr = 0;
var scale = 0;
var tid;
var flexible = lib.flexible || (lib.flexible = {});
if (metaEl) {
var match = metaEl
.getAttribute("content")
.match(/initial-scale=([\d.]+)/);
if (match) {
scale = parseFloat(match[1]);
dpr = parseInt(1 / scale);
}
} else if (flexibleEl) {
var content = flexibleEl.getAttribute("content");
if (content) {
var initialDpr = content.match(/initial-dpr=([\d.]+)/);
var maximumDpr = content.match(/maximum-dpr=([\d.]+)/);
if (initialDpr) {
dpr = parseFloat(initialDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
if (maximumDpr) {
dpr = parseFloat(maximumDpr[1]);
scale = parseFloat((1 / dpr).toFixed(2));
}
}
}
if (!dpr && !scale) {
var isAndroid = win.navigator.appVersion.match(/android/gi);
var isIPhone = win.navigator.appVersion.match(/iphone/gi);
var devicePixelRatio = win.devicePixelRatio;
//可配置安卓和ios不同的适配
if (isIPhone) {
dpr = 1;
} else {
dpr = 1;
}
scale = 1 / dpr;
}
docEl.setAttribute("data-dpr", dpr);
if (!metaEl) {
metaEl = doc.createElement("meta");
metaEl.setAttribute("name", "viewport");
metaEl.setAttribute(
"content",
"initial-scale=" +
scale +
", maximum-scale=" +
scale +
", minimum-scale=" +
scale +
", user-scalable=no"
);
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(metaEl);
} else {
var wrap = doc.createElement("div");
wrap.appendChild(metaEl);
doc.write(wrap.innerHTML);
}
}
function refreshRem() {
var width = docEl.getBoundingClientRect().width;
var rem = width / 10;
docEl.style.fontSize = rem + "px";
flexible.rem = win.rem = rem;
}
win.addEventListener(
"resize",
function() {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
},
false
);
win.addEventListener(
"pageshow",
function(e) {
if (e.persisted) {
clearTimeout(tid);
tid = setTimeout(refreshRem, 300);
}
},
false
);
if (doc.readyState === "complete") {
doc.body.style.fontSize = 12 * dpr + "px";
} else {
doc.addEventListener(
"DOMContentLoaded",
function() {
doc.body.style.fontSize = 12 * dpr + "px";
},
false
);
}
refreshRem();
flexible.dpr = win.dpr = dpr;
flexible.refreshRem = refreshRem;
flexible.rem2px = function(d) {
var val = parseFloat(d) * this.rem;
if (typeof d === "string" && d.match(/rem$/)) {
val += "px";
}
return val;
};
flexible.px2rem = function(d) {
var val = parseFloat(d) / this.rem;
if (typeof d === "string" && d.match(/px$/)) {
val += "rem";
}
return val;
};
})(window, window["lib"] || (window["lib"] = {}));
有些特殊的地方比如 echart 中 tooltip 的文字大小,是无法进行自适应的。那就需要动态获取当前页面的分辨率再进行计算
// 同样是以750px为基础进行计算
export let GetDpr = function() {
let fontData = document.querySelector("html").style.fontSize;
return fontData.slice(0, fontData.length - 2) / 37.5;
};