从此掘金,以前其他平台发布的内容逐步迁移到掘金
自接触移动端H5页面以来,从未停止对H5页面适配不同屏幕的解决方案的探索。从最初的bootstrap响应式框架来做手机适配; 后来尝试用百分比去做H5的适配;接着又去尝试媒体查询,但移动端的屏幕大小个各异,各种尺寸的机型都有,难以做到不同手机适配, 后来看到京东,网易,手淘等使用rem做手机适配,使用rem前端开发者可以很方便的在各种屏幕尺寸下,做出设计图要求的效果。本文重在说明 手机端不同的自适应解决方案的优缺点以及rem作为主流的移动端自适应布局方案的原理以及使用方案。
1. 使用rem做手机适配
什么是rem
rem是css3新增的相对长度单位,是指相对于根元素html的font-size值的大小
扩展: em也是css的相对长度单位,em作为font-size的单位时,其代表父元素的字体大小,em作为其他属性单位时,代表自身字体大小
rem适配原理
rem布局适配的本质是等比缩放,根据不同屏幕的宽度,以相同的比例动态修改html的font-size值,它跟设计师给出的设计图的大小没有关系,不管设计图给出的 是480,720,750,1080,都是将设计稿等比缩放在设备上;那么根html的font-size的值是怎么计算的呢?
- 网易的方案是根据屏幕宽度与设计图宽度的比值作为font-size 大小,但此时计算出的font-size值小于12px会造成一些错误和奇怪的问题,为此我们把比例扩大100倍,即,根html的font-size值=屏幕宽度/设计图宽度*100 为了使比例不变,相应的设计图元素在变为rem的过程中要除以100;
- 阿里的方案是根据设备像素比设置scale的值,保持视口device-width始终等于设备物理像素,接着根据屏幕宽度/10动态计算根html的font-size的大小, 设计稿的像素单位如何换成以rem为单位呢?可以用一个比例来计算,具体是将设计图分成100份,每一份的宽度用a来表示,同时认定1rem单位为10a,即1rem=10a; 如设计稿宽度为750px,此时:
750px=100a --- 1a=7.5px
1rem=10a --- 1rem=75px
同理,如果设计稿总宽度是640px,则1rem=64px。
rem适配使用方案
(function(doc, win) {
var docEl = doc.documentElement,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
recalc = function() {
var clientWidth = docEl.clientWidth;
if(!clientWidth) return;
docEl.style.fontSize = (clientWidth / 7.5) + 'px';
};
if(!doc.addEventListener) return;
win.addEventListener(resizeEvt, recalc, false);
doc.addEventListener('DOMContentLoaded', recalc, false);
})(document, window);
小米移动端使用方案(基于720的设计图)
!function(e){
var t = e.document,
n = t.documentElement,
i = e.devicePixelRatio || 1,
a = "orientationchange" in e ? "orientationchange" : "resize",
d = function(){
var e = n.getBoundingClientRect().width || 360; // Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置。
(1 == i || e > 720) && (e = 720), n.style.fontSize = e/7.2 + "px" // 小米就是6啊 设计图都是安卓 720*1280界面
};
n.setAttribute("data-dpr", i),
t.addEventListener && (e.addEventListener(a, d, !1), "complete" === t.readyState ||
t.addEventListener("DOMContentLoaded",
function() {
setTimeout(d)
}, !1)
)
} (window)
flexible移动端使用方案
;(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) {
console.warn('将根据已有的meta标签来设置缩放比例');
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;
if (isIPhone) {
// iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
dpr = 2;
} else {
dpr = 1;
}
} else {
// 其他设备下,仍旧使用1倍的方案
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;
if (width / dpr > 540) {
width = 540 * dpr;
}
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(e) {
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'] = {}));
手机淘宝适配方案(基于750的设计图)
!function(e, t) {
var n = t.documentElement,
d = e.devicePixelRatio || 1; // 设备DPR
function i() {
var e = n.clientWidth / 3.75; // iPhone 6 布局视口375
n.style.fontSize = e + "px"
}
if (function e() { t.body ? t.body.style.fontSize = "16px" : t.addEventListener("DOMContentLoaded", e)}(),
i(),
e.addEventListener("resize", i),
e.addEventListener("pageshow", function(e) { e.persisted && i() }), d >= 2){
var o = t.createElement("body"), a = t.createElement("div");
a.style.border = ".5px solid transparent", o.appendChild(a), n.appendChild(o),
1 === a.offsetHeight && n.classList.add("hairlines"), n.removeChild(o)
}
}(window, document)
2. vw/vh
vw/vh是基于Viewport视窗的长度单位,这里的视窗(Viewport)指的就是浏览器可视化的区域,视口单位包括以下4个
- vw : 1vw 等于视口宽度的1%
- vh : 1vh 等于视口高度的1%
- vmin : 选取 vw 和 vh 中最小的那个
- vmax : 选取 vw 和 vh 中最大的那个
假如你的设计图是750px的宽度,从vw、vh的原理上看100vw=750px,即1vw=7.5px,我们可以根据设计图上的px值转换成对应的vw的值; 可以使用vw适配我们的页面的地方:
- 容器适配,可以使用vw
- 文本的适配,可以使用vw
- 大于1px的边框、圆角、阴影都可以使用vw
- 内距和外距,可以使用vw
3. 百分比
百分比做手机适配的方法是子元素相对于父元素的百分之多少,做手机端的适配,整体布局可以实现不同屏幕的缩放,但 元素内的字体以及位置难以做到不同屏幕上的放大和缩小,百分比布局只适合布局简单的页面,定制化要求比较高复杂的页面 实现很困难。
4 媒体查询
媒体查询是早期使用的手机适配方案,通过查询设备的宽度执行不同的css代码,展示设计图的UI; 针对移动和PC维护同一套代码时使用的较多,例如使用Bootstrap做响应式布局时,底层使用的就是媒体查询。 该方案缺点是代码量比较大,维护不方便,做定制化需求时不能很好的满足效果,以及为了兼顾移动端和PC端 各自特有的交互方式不能单独使用。
5 uni-app方案】
支持多平台,不同平台采用不同的自适应方案,优先推荐吧。
总结
随着移动端的发展会出现更多的适配方案,目前主流的仍是rem做手机端的适配,随着浏览器对vw的支持,使用vw做手机端的适配也是不错的选择, 假如你的公司需要使用响应式布局移动和PC维护同一套代码,可以考虑使用基于媒体查询适配方案外加less和sass减少工作量。百分比做手机适配只能做简单的页面。