什么是适配, 为什么要适配
我们拿到的设计图一般是以 640,750,1080 分辨率为基准设计的, 而现在的手机终端各式各样, 分辨率不同, 逻辑像素不同 , 视口不同, 所以为了让我们的页面在每个设备上都可以良好的展示, 那么就需要为这些设备做统一的处理, 这个过程就称为移动端适配.
需要知道的一些概念:
物理像素(physical pixel)
一个物理像素是显示器 (手机屏幕) 上最小的物理显示单元, 可以理解为我们平时说的分辨率.
设备独立像素(density-independent pixel)
设备独立像素(也叫密度无关像素), 可以认为是计算机坐标系统中得一个点, 这个点代表一个可以由程序使用的虚拟像素(比如: CSS 像素), 然后由相关系统转换为物理像素, 在这里可以理解为我们说的视觉视口的大小;
所以说, 物理像素和设备独立像素之间存在着一定的对应关系, 这就是接下来要说的设备像素比.
设备像素比(device pixel ratio)
设备像素比 (简称 dpr) 定义了物理像素和设备独立像素的对应关系, 它的值可以按如下的公式的得到: 设备像素比 = 物理像素 / 设备独立像素 // 在某一方向上, x 方向或者 y 方向
设备像素比也是设备生产的时候设置好的, 在 JavaScript 中, 可以通 Windows.devicePixelRatio 获取到当前设备的 dpr.
视口(viewport)
pc 端视口指浏览器窗口内的内容区域, 不包含工具条, 滚动条.
移动浏览器中视口分为几种情况:
<metaname="viewport"content="width=device-width,minimum-scale=1.0,maximum-scale=1.0"/>中 content 所设置的视口, 称为布局视口, 最大值由浏览器厂商规定 , 可以 document.documentElement.clientWidth 获取其宽度.
而我们看到的浏览器的窗口, 网页区域的大小, 称为视觉视口, 用 CSS 像素表示(设备逻辑像素)
rem
rem 是 css3 的一个长度单位 , 相对文档跟元素 html; 比如设置 HTML font-size=100px; 那么 1rem=100px; 之后的所有元素都可以用这个基准值来设置大小;
常用的方案:
固定高度, 宽度自适应(百分比, em)
使用 rem 布局
下面总结了网易 淘宝首页使用 rem 的方案
网易的做法:
1) 将布局适口设置为视觉视口, 不进行缩放, 即理想视口.
<meta name="viewport"content="initial-scale=1,maximum-scale=1, minimum-scale=1">
2) 以设计稿分辨率为基准, 取 100px 为 font-size 的参照, 那么设计稿的宽如果是 640,body 元素的宽度就可以设置为 width:6.4rem(640/100), 当我们将布局视口设置为 320 时, 于是 HTML 的 font-size=deviceWidth / 6.4.
3) 通过 document.documentElement.clientWidth 获取 deviceWidth;
4) 当页面的 dom ready 后设置 HTML font-size,
document.documentElement.style.fontSize =document.documentElement.clientWidth / 6.4 + 'px'
5) 通过 mediaQuery 设置字体大小, 字体大小不可以使用 rem, 原因是误差太大.
以 640 的设计稿为例最终的设置 HTML font-size 代码如下, 布局时拿设计稿标注的尺寸除以 100, 就是 rem 的值, 相当简单啊
- var deviceWidth = document.documentElement.clientWidth;
- if(deviceWidth> 640) deviceWidth = 640;
- document.documentElement.style.fontSize = deviceWidth / 6.4 + 'px';
这里 if(deviceWidth> 640) deviceWidth = 640; 是因为当 deviceWidth 大于 640 时物理分辨率已经大于 1280(取决于 dpr), 应该去访问 pc 的网站;
淘宝的做法:
原理
1) 通过 dpr 设置缩放比, 实现布局视口大小,
- var scale = 1 / devicePixelRatio;
- document.querySelector('meta[name="viewport"]').setAttribute('content','initial-scale='+ scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
2) 动态计算 HTML 的 font-size
document.documentElement.style.fontSize = document.documentElement.clientWidth / 10 + 'px';
这里的意思是, clientWidth / 10 得到是布局视口下的 rem 基准值(以 iphone6 为例 1rem=75px), 那么设计稿正好也是 750, 所以对应的关系 clientWidth / 10== 设计稿的尺寸 / x, 那么 x = 设计稿的尺寸 / rem 基准值. 如果是 iphone6 plus rem 基准值等于 clientWidth / 10 等于 124.2, 那么 x=750/124.2.
关于具体的实现 淘宝提供了一个开源的方案 lib-flexible: https://github.com/amfe/lib-flexible ;
具体逻辑 :
1)判断 head 中是否设置了 viewport, 如果有设置, 按照已有 viewport 设置缩放比;
- if (metaEl) {
- console.warn('将根据已有的 meta 标签来设置缩放比例');
- var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/);
- if (match) {
- scale = parseFloat(match[1]);
- dpr = parseInt(1 / scale);
- }
- }
2)如果没有设置 meta viewport, 判断是否设置 dpr, 如果有, 通过 dpr 计算缩放 scale.
- var content = flexibleEl.getAttribute('content');
- if (content) {
- var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
- var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);//maximum 设置最大值, 与 initial 的值比较, 取最小值;
- if (initialDpr) {
- dpr = parseFloat(initialDpr[1]);
- scale = parseFloat((1 / dpr).toFixed(2));
- }
- if (maximumDpr) {
- dpr = parseFloat(maximumDpr[1]);
- scale = parseFloat((1 / dpr).toFixed(2));
- }
- }
3)如果 dpr &scale 都没有设置, 那么就通过设备的 dpr 设置起缩放 scale,
- if (!dpr && !scale) {//meta[name="viewport"]&&meta[name="flexible"]都不存在.
- 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;
- }
4)得到 scale 之后 , 如果 meta 的 viewport 不存在, 那么就创建一 meta[name="viewport"], 将 scale 配置进去.
- 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);
- }
5)动态改写 HTML 的 font-size
- var width = docEl.getBoundingClientRect().width;// 获取 HTML 的宽度
- if (width / dpr> 540) {// 判断屏幕逻辑像素大于 540 时, 取 540
- width = 540 * dpr;
- }
- var rem = width / 10;
- docEl.style.fontSize = rem + 'px';
- flexible.rem = win.rem = rem;
总结:
使用 rem 布局, 实质都是通过动态改写 HTML 的 font-size 基准值, 来实现不同设备下的良好统一适配;
网易与淘宝不同 的地方是 , 网易将布局视口设置成了 视觉视口, 淘宝将布局视口设置成了物理像素大小, 通过 scale 缩放嵌入了 视觉视口中;
容器元素的字体大小都不使用 rem, 需要额外的 media 查询;
参考文章链接:
- http://www.cnblogs.com/lyzg/p/4877277.html ;
- http://mobile.51cto.com/web-484304.htm
来源: https://juejin.im/entry/5c580c02f265da2dc8493f8a