Flexible方案是淘宝的移动端适配方案,相关的文章在这里

1
2
3
4
;(function(win, lib) {
})(window, window['lib'] || (window['lib'] = {}));

这是一个插件的写法之一。你还可以在这里找到其它方法(IIFE)[http://benalman.com/news/2010/11/immediately-invoked-function-expression/]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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));
}
}
}

首先这里会判断是否写了<meta name="viewport" content="initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">元素。如果有则从页面中取出对应的缩放系数(scale)。然后通过(scale)算出对应的设备像素比(dpr).

如果页面上写了<meta name="flexible" content="initial-dpr=2,maximum-dpr=3" />则会取initial-dprmaximum-dpr之中最大者。

如果以上两都都没设置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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倍屏使用2倍方案,3的屏3倍的方案,其余的用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;
}

这里有检测当在安卓下的时候并没有使用高清方案,具体的原因可见issue
大概的意思就是说有些安卓设置的initial-scale不为1的时候会无效。
。由于这个原因会产生一些问题比如1px边框线的问题,具体可见网友的文章基于淘宝弹性布局方案lib-flexible的问题研究

这里在实际的工作过程假设有用vue的话,在移动端的适配过程中假设引用了一个vue-star-rating组件由于组件的star-size是设置的是数值,然后当你在安卓下的时候会发现这个星星会变得很大解决办法是利用lib.flexible.dpr或者lib.flexible.rem来动态设置这个组件的星星的大小,这里暂且只发现这种解决方案,如果有其它的方法或者其它的评价组件,或者自己写一个?欢迎指正-^.^-。

接下来设置页面根元素的data-dpr属性,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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');
//判断页面上是否有head标签没有则写一个
if (docEl.firstElementChild) {
docEl.firstElementChild.appendChild(metaEl);
} else {
var wrap = doc.createElement('div');
wrap.appendChild(metaEl);
doc.write(wrap.innerHTML);
}
}

接下来是刷新rem函数计算出rem的值:

1
2
3
4
5
6
7
8
9
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;
}

docEl.getBoundingClientRect().width这里是计算出页面在视窗里面的宽度,具体可见这里
宽度大于540*dpr的时候,则最大为540 * dpr。rem值为width / 10,即假设是750的iphone6的时候rem值为75,设置根元素html的字体大小值。

接下来是监听页面事件resizepageshow刷新页面rem大小。

1
2
3
4
5
6
7
8
9
10
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);

监听window.document的状态。

1
2
3
4
5
6
7
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);
}

当文档的readyStatecomplete或者为DOMContentLoaded即页面内容载入完成,设置body的字体大小。

最后获得主动获得rem值,页面的dpr赋值到lib.flexible下还有一些工具函数rem2pxpx2rem。这里在实际的工作过程中,lib.flexible.dpr相当的有用,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

2017.10.19后续

翻看了ant-design中关于1px线的处理再和libflexible.js结合整理出了以下的sass函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
@mixin hairline($color: #C7C7C7, $direction: left, $radius: 0) {
@if $direction != 'all' {
border-#{$direction}: 1PX solid $color;
[data-dpr="1"] & {
border-#{$direction}: none;
@media (min-resolution: 2dppx), (min-resolution: 192dpi) {
position: relative;
&:before {
content: " ";
position: absolute;
border-#{$direction}: 1PX solid $color;
@if $direction == 'top' {
left: 0;
top: 0;
right: 0;
height: 1PX;
transform-origin: 0 0;
transform: scaleY(0.5);
} @else if $direction == 'right' {
right: 0;
top: 0;
bottom: 0;
width: 1PX;
transform-origin: 100% 0;
transform: scaleX(0.5);
} @else if $direction == 'bottom' {
left: 0;
bottom: 0;
right: 0;
height: 1PX;
transform-origin: 0 100%;
transform: scaleY(0.5);
} @else {
left: 0;
top: 0;
bottom: 0;
width: 1PX;
transform-origin: 0 0;
transform: scaleX(0.5);
}
}
@media (min-resolution: 3dppx), (min-resolution: 288dpi) {
&:before {
@if $direction == 'top' {
transform: scaleY(0.33);
} @else if $direction == 'right' {
transform: scaleX(0.33);
} @else if $direction == 'bottom' {
transform: scaleY(0.33);
} @else {
transform: scaleX(0.33);
}
}
}
}
}
} @else {
border: 1PX solid $color;
border-radius: $radius;
[data-dpr="1"] & {
@media (min-resolution: 2dppx), (min-resolution: 192dpi) {
position: relative;
border: none;
transform: translateZ(0);
&:before {
content: '';
position: absolute;
left: 0;
top: 0;
width: 200%;
height: 200%;
border: 1PX solid $color;
border-radius: $radius * 2;
transform-origin: 0 0;
transform: scale(0.5);
box-sizing: border-box;
pointer-events: none;
z-index: -1;
}
}
}
}
}
@mixin hairline-remove($direction: left) {
border-#{$direction}: 0;
&:before {
display: none !important;
}
}

大体的意思是当处理高清方案的时候边框线是用的那个border-#{$direction}或者border边框线,然后利用缩放功能来获得细腻的边框线,当在安卓下的时候由伪类来显示那个边框线。

后面的更新的2.0版本,接下来将会进行一些思考和研究。

Todolist:

  • docEl.firstElementChild 什么情况下会没有写head?
  • 地图的显示问题
  • 二维码显示问题
  • window.document的readyStateDOMContentLoaded的理解
  • 与其它移动端适配方案的比较