移动端适配涉及的基本概念理解
viewport(视窗)
移动端的viewport包括layout viewport、visual viewport、ideal viewport
layout viewport:布局layout(也就是你的html页面实际可以展示的宽度),一般layout viewport默认大小980px, 明显这个值大于移动端浏览器的可视区域宽度。我们可以通过document.documentElement.clientWidth来获取layout viewport的宽度。当你设置meta标签的content属性的时候也会改变layout viewport的大小。
visual viewport:表示浏览器可视区域的大小
ideal viewport:设备理想viewport,它需要满足用户在不需要缩放和滚动条就能查看所有内容,显示的文字和图片的大小是合适的,要达到这种效果方法(这也是我们移动端适配需要实现的效果),要使layout viewport = visual viewport
实现方法有
<meta name="viewport" content="width=device-width,initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">设置width=device-width,代表将layout viewport设置为设备宽度,例如iphone6的设备宽度为375px,此时layout viewport = 375px。这也是为什么我们写移动端页面的时候,会二话不说,先把这个meta标签引到head里面
设备像素(也叫物理像素)
他是显示设备中一个最微小的物理部件。
设备独立像素
可以认为是计算机坐标系统中的一个点,这个点代表一个可以由程序使用的虚拟像素(比如说CSS像素),然后由相关系统转换为物理像素。
设备像素比(device pixel ratio/dpr)
设备像素比 = 设备像素/设备独立像素
在JavaScript中,可以通过window.devicePixelRatio获取到当前设备的dpr。而在CSS中,可以通过-webkit-device-pixel-ratio,-webkit-min-device-pixel-ratio和 -webkit-max-device-pixel-ratio进行媒体查询,对不同dpr的设备,做一些样式适配。
普通屏和Retina屏
Retina屏也叫视网膜屏和高清屏(最初的设定是dpr=2的屏幕,但由于手机制造厂商的不同,不同的品牌手机的dpr各有不同,下面附件一些手机的设备参数,最右边一列表示设备的dpr,糟心的是android手机的dpr很混乱,有1.75、3等各种比较特殊的dpr值,这也是最初为什么淘宝在设计移动端适配的时候直接忽略android机的dpr,全部默认为1【下面会有代码说明】),Retina屏作用显而易见,更高清。在普通屏幕下1个CSS像素对应1个设备像素,而在Retina屏幕下,1个CSS像素对应的却是dpr * dpr 个设备像素。
大概一些基本概念的了解就上面这些,接下来进入代码分析,flexble.js和vw到底是如何实现移动端适配的呢?
关于淘宝的适配方案flexble.js
github地址:github.com/amfe/lib-fl…
官方文档地址:github.com/amfe/articl…
关于flexble.js,它主要做的事情其实就是三件
1、动态设置html的font-size
2、动态设置meta的content 属性,也就是页面的scale(缩放比例)
3、动态设置html的data-dpr
下面我将附上代码和注解
;(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 = {});
// 个人觉得可以省略以下这些代码(/* */内的),上面文字中有提到过flexble.js作用包括:根据手
// 机的不同dpr动态设置meta标签和html的data-dpr,如果你自己写上meta标签,并设置它的initial
// -scale属性,就等同你强行设置了initial-scale和data-dpr
/*
if (metaEl) {
// 根据已有的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));
}
}
}
*/
// 如果没有自己设置meta标签
if (!dpr && !scale) {
// 淘宝的flexble.js只对ios机的dpr做了动态设置,android机则默认设置为1
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;
// 因为现在很多android机的dpr不再是1,也可能是2或者3等一些值,所以我个人觉得为了兼容,
// 可以将以上代码稍做改造,不区分ios和android
var devicePixelRatio = win.devicePixelRatio;
if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
dpr = 3;
} else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
dpr = 2;
} else {
dpr = 1;
}
scale = 1 / dpr;
} // 设置html的data-dpr属性是为了设置不同dpr下的css样式,例如你要设置dpr=1的div下的字体为12px
// 那dpr=2下的字体应该为24px才能显示正常
[data-dpr="1"] div{
font-size: 12px;
}
[data-dpr="2"] div {
font-size: 24px;
}
// 为了方便开发,可以用sass来实现
@mixin font-dpr($font-size){
font-size: $font-size;
[data-dpr="2"] & {
font-size: $font-size * 2
}
[data-dpr="3"] & {
font-size: $font-size * 3
} }
@include font-dpr(12px)
docEl.setAttribute('data-dpr', dpr);
// 不存在viewport的meta标签时,进行meta的标签的动态设置,如直接覆盖原有meta标签,
// 用flexble.js的动态设置,则不需要加这个判断
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);
}
}
// 设置rem大小
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;
}
// 当浏览器视窗大小发生改变时触发resize时间,重新计算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);
// 设置body的字体大小,以12px为基准,防止页面元素未设置字体大小时,字体过大
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;
// rem 与 px的相互转换
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'] = {}));vw适配方案
淘宝flexble官方有这样一段话
由于viewport单位得到众多浏览器的兼容,lib-flexible这个过渡方案已经可以放弃使用,不管是现在的版本还是以前的版本,都存有一定的问题。建议大家开始使用viewport来替代此方案。vw的兼容方案可以参阅《如何在Vue项目中使用vw实现移动端适配》一文。所以我们可以开始在项目中试着使用vw来做移动端的兼容,本人只是偶尔用一下vw和vh,还没有完全脱离rem来进行移动适配。
vw的概念
vw 是基于Viewport视窗的长度单位,这里的视窗(Viewport)指的是浏览器可视化的区域。vw来替代以前Flexible中的rem缩放方案。先来回归到我们的实际业务中来。目前出视觉设计稿,我们都是使用750px宽度的,那么100vw = 750px,即1vw =7.5px。那么我们可以根据设计图上的px值直接转换成对应的vw值。看到这里,很多同学开始感到崩溃,又要计算,这是插件的作用就体现了,我们可以使用PostCSS的插件postcss-px-to-viewport,让我们可以直接在代码中写px。
关于插件如何用可以参考文章 www.w3cplus.com/css/vw-for-…
https://github.com/evrone/postcss-px-to-viewport
Viewport不足之处
采用vw来做适配处理并不是只有好处没有任何缺点。有一些细节之处还是存在一定的缺陷的。比如当容器使用vw单位,margin采用px单位时,很容易造成整体宽度超过100vw,从而影响布局效果。对于类似这样的现象,我们可以采用相关的技术进行规避。比如将margin换成padding,并且配合box-sizing。只不过这不是最佳方案,随着将来浏览器或者应用自身的Webview对calc()函数的支持之后,碰到vw和px混合使用的时候,可以结合calc()函数一起使用,这样就可以完美的解决。
另外一点,px转换成vw单位,多少还会存在一定的像素差,毕竟很多时候无法完全整除。