目录 
前言
移动端适配是一个十分重要的问题。在移动端页面中,其布局和交互方式可能相比于PC端少一些,但是由于设备的多样化,其适配问题就必须多加关注。设备情况种类繁多复杂,从网上的几张图中足以看出:










基本概念
物理像素(physical pixel)
一个物理像素是显示器(手机屏幕)上最小的物理显示单元,在操作系统的调度下,每一个设备像素都有自己的颜色值和亮度值。
设备独立像素(density-independent pixel)
设备独立像素(也叫密度无关像素),可以认为是计算机坐标系统中得一个点,这个点代表一个可以由程序使用的虚拟像素(比如: css像素),然后由相关系统转换为物理像素。可以理解为就是下面要说的逻辑像素。所以说,物理像素和设备独立像素之间存在着一定的对应关系,这就是接下来要说的设备像素比。
设备像素比(device pixel ratio,即简称dpr)

设备像素比(简称dpr)定义了物理像素和设备独立像素的对应关系,它的值可以按如下的公式的得到:
设备像素比 = 物理像素 / 设备独立像素
- 在javascript中,可以通过window.devicePixelRatio获取到当前设备的dpr。
- 在css中,可以通过-webkit-device-pixel-ratio,-webkit-min-device-pixel-ratio和 -webkit-max-device-pixel-ratio进行媒体查询,对不同dpr的设备,做一些样式适配(这里只针对webkit内核的浏览器和webview)。
viewport
通俗来讲,移动端的viewport就是我们所能看到的手机端浏览器的可视窗口大小,但viewport又不仅仅局限于可视窗口的大小,一般情况下,它是比默认窗口大小要大的,这是因为考虑到移动设备的分辨率相对于桌面电脑来说都比较小,所以为了能在移动端正常显示为桌面浏览器而设计的网页,移动端的浏览器都会默认把自己的默认的viewport设为980px到1024px不等,但其后果就是会出现横向滚动条,因为移动端浏览器可视区域的大小是比默认的viewport宽度要小的。
1
| <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1,user-scalable=no">
|
该meta标签的作用是让当前viewport的宽度等于设备的宽度,同时不允许用户手动缩放。
PPI(Pixels Per Inch)

像素密度,这项指标是连接数字世界与物理世界的桥梁。Pixels per inch,准确的说是每英寸的长度上排列的像素点数量。1英寸是一个固定长度,等于2.54厘米,大约是食指最末端那根指节的长度。像素密度越高,代表屏幕显示效果越精细。Retina屏比普通屏清晰很多,就是因为它的像素密度翻了一倍。
![]()
DPI(Dots Per Inch)
每英寸所打印的点数,是打印机、鼠标等设备分辨率的单位。这是衡量打印机打印精度的主要参数之一,一般来说,该值越大,表明打印机的打印精度越高。dpi是指每英寸的像素,也就是扫描精度。国际上都是计算一平方英寸面积内像素的多少。
倍率与逻辑像素

我们在css中声明的是逻辑像素值,而不同设备具体用多少物理像素进行显示有所不同。Retina屏幕把2x2个像素当1个像素使用。比如原本44像素高的顶部导航栏,在Retina屏上用了88个像素的高度来显示。导致界面元素都变成2倍大小,画质更清晰。iOS应用的资源图片中,同一张图通常有多个尺寸。你会看到文件名有的带@2x、@3x字样,有的不带。其中不带@2x、@3x的用在普通屏上,带@2x的用在Retina屏上(iPhone6等尺寸),带@3x的也用在Retina屏上(iPhone6plus等尺寸)。只要图片准备好,iOS会自己判断用哪张,Android道理也一样。而在web app开发中,则需要自己对图片进行选择。苹果以普通屏为基准,给Retina屏定义了一个2倍或3倍的倍率。实际像素除以倍率,就得到逻辑像素尺寸。只要两个屏幕逻辑像素相同,它们的显示效果就是相同的。
依赖库 lib-flexible
使用实践
引入
1.npm包安装
npm install --save-dev lib-flexible
2.在基础js文件靠前位置引入包文件(ES6写法)
import 'lib-flexible';
对于长度、宽度等“距离”的写法(less文件)
1 2 3 4 5 6 7 8 9
| @font-size-base: 75;
.home { position: fixed; top: 88rem / @font-size-base; bottom: 98rem / @font-size-base; width: 100%; background-color: #ddd; }
|
对于文字字号大小的写法(less文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @font-size-base: 75; .name { display: flex; font-family: @font-family-base; color: #000000; letter-spacing: 0; font-size: 17px; [data-dpr="2"] & { font-size: 34px; } [data-dpr="3"] & { font-size: 51px; } font-weight: bold; padding-bottom: 20rem/@font-size-base }
|
源码、原理分析
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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
| ;(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) { if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) { dpr = 3; } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){ dpr = 2; } else { 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; 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'] = {}));
|
手淘团队相关文献参考
使用Flexible实现手淘H5页面的终端适配