手淘H5移动端适配方案flexible源码分析

 

移动端适配一直是一个值得探讨的问题,在业余时间我找了一些页面,查看了一些厂商对于移动端H5页面的适配方案,看到了几个典型的例子,今天就来记录一下我看到的第一个典型的例子,也是我们公司目前普通H5项目正在使用的适配方案。

这个适配方案是lib-flexible,在看这个源码的同时,我想先来回顾一下几个概念:

1.  viewport

 在移动设备上,viewport是设备屏幕用来显示我们网页的那一块区域,或者说是浏览器(或者Hybird App内的webview)用来展示我们网页的那部分区域,viewport不局限于浏览器可视区域的大小,可能比浏览器的可视区域大,也可能比浏览器的可视区域小。viewport是一个与html中mate标签相关的概念,如下:

  1. <mate name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no"/>

上面这行代码的作用是让当前viewport的宽度等于设备宽度,页面初始缩放比例为1,viewport最大的缩放比例为1,viewport最小的缩放比例也为1,同时不允许用户拖动缩放。

  1. 属性
作用

值类型

  1. width
 规定页面的宽度

可以为字符串值"device-width",或者正整数

  1. initial-scale
 规定页面的初始缩放比例 为数字,可以为小数
  1. maximum-scale
 规定页面的最大缩放比例 为数字,可以为小数
  1. minimum-scale
 规定页面的最小缩放比例 为数字,可以为小数
  1. user-scalable
 规定是否允许用户进行拖动缩放 yes或no,yes是允许,no则不允许

好了,先熟悉到这里,后面如果想对viewport有更深入透彻的研究,可以查看PPK大神的关于viewport的三篇文章。

2.设备像素比

 关于设备像素比,我们先卖个关子,后面会说。我们先来看一下另一个值得思考的问题,我们CSS中常用的单位px到底和我们移动设备屏幕上的像素(pixel)是什么关系?CSS里的1px等于移动设备屏幕上的1像素吗?

 首先,我来绕一波,px确实是英文像素(pixel)的缩写;但是!!!我们这里为了将CSS中的px和设备中的物理像素加以区分,CSS中的单位描述我们就用熟悉的px,设备的物理像素,我们则用pixel来加以区分!!!

 那么问题来了,我CSS中的1px到底等于设备物理像素1pixel吗?----答案是:不一定!!!

 那么为什么是不一定呢?这里我们又要了解两个相关概念:

  (1)物理像素:设备的物理像素,顾名思义就是一个移动设备在出厂时就固定了的像素,整个屏幕是由一个挨着一个间隙极小的像素组成的,是屏幕显示中的基本单元,例如某款手机屏幕分辨率:1920*1080像素,这里所说的1920就是该款手机屏幕纵向的像素排布数量,1080就是横向像素排布数量,这里的像素就是我们所说的物理像素pixel。

  (2)独立像素:独立像素也可以称之为逻辑像素,一个逻辑像素是屏幕接受程序控制的最小单位,简言之我们可以将这里的逻辑像素和我们CSS中的px建立起联系,即CSS中的1px可以控制1个逻辑像素的显示。

 书接前文,前面提到我们CSS中的1px不一定等于我们设备的物理像素1pixel,那么什么情况下等于?什么情况下又不等于?

  等于的情况:早在移动端视网膜屏幕上市以前,绝大部分手机的物理像素和逻辑像素其实是对等的,比如iphone 3 的手机屏幕(物理像素:320x480;逻辑像素:320x480)。这里就是CSS 中的1px等于移动设备的物理像素1pixel。也就是说此时,物理像素÷逻辑像素=1,这个比值就是设备像素比(dpr)。

  不等于的情况:当 iphone 4 手机问世时,掀起了视网膜平屏幕的浪潮,以iphone 4 手机屏幕为例(物理像素:640x960;逻辑像素:320x480),由此可见iphone 4  相比于iphone 3 的手机屏幕,物理像素多了一倍,但是逻辑像素却没有变化,那么iphone 4 的设备像素比: 物理像素÷逻辑像素=2,也就是说  dpr=2 。当然,随着手机日新月异的发展,dpr=3的情况也是有的,例如总结的下表各主要手机型号的设备像素比:

手机型号 物理像素 独立像素(逻辑像素) dpr 倍图
iphone  5/5S/5E  640*1136  320*568  2  @2x
 iphone 6/7/8  750*1334  375*667  2  @2x
 iphone 6p/7p/8p  1242*2208  414*736  3  @3x

安卓手机由于厂商众多,并且型号尺寸众多,现仅概括几个常见比例供参考(不再列举详细的手机型号),重要的是理解原理:

手机型号 物理像素 逻辑像素 dpr 倍图
Android 1 320*480 320*480 1 @1x
Android 2 540*960 360*640 1.5 @1.5x
Android 3 640*960 320*480 2 @2x
Android 4 720*1280 360*640 2 @2x
Android 5 1080*1920 360*640 3 @3x

好了,巴拉了这么多,该切入正题上lib-flexible源码了,如下:

  1. ;(function(win, lib) {
  2. var doc = win.document;
  3. var docEl = doc.documentElement;
  4. var metaEl = doc.querySelector('meta[name="viewport"]'); // 获取名为viewport的mate标签
  5. var flexibleEl = doc.querySelector('meta[name="flexible"]'); // 获取名为flexible的mate标签
  6. var dpr = 0; // dpr (设备像素比)初始化置为0
  7. var scale = 0; // scale (缩放比例)
  8. var tid;
  9. var flexible = lib.flexible || (lib.flexible = {});
  10.  
  11. if (metaEl) { // 如果名为viewport的mate标签存在var match = metaEl.getAttribute('content').match(/initial\-scale=([\d\.]+)/); // 将name=viewport的mate标签里的,content属性里的initial-scale(初始缩放比)属性处理成数组
  12. if (match) {
  13. scale = parseFloat(match[1]); // 获得了页面的初始缩放比例
  14. dpr = parseInt(1 / scale); // 得到设备像素比
  15. }
  16. } else if (flexibleEl) { //
  17. var content = flexibleEl.getAttribute('content');
  18. if (content) {
  19. var initialDpr = content.match(/initial\-dpr=([\d\.]+)/);
  20. var maximumDpr = content.match(/maximum\-dpr=([\d\.]+)/);
  21. if (initialDpr) {
  22. dpr = parseFloat(initialDpr[1]);
  23. scale = parseFloat((1 / dpr).toFixed(2));
  24. }
  25. if (maximumDpr) {
  26. dpr = parseFloat(maximumDpr[1]);
  27. scale = parseFloat((1 / dpr).toFixed(2));
  28. }
  29. }
  30. }
  31.  
  32. if (!dpr && !scale) { // 当上面条件都不满足时
  33. var isAndroid = win.navigator.appVersion.match(/android/gi); // 安卓机
  34. var isIPhone = win.navigator.appVersion.match(/iphone/gi); // IOS机
  35. var devicePixelRatio = win.devicePixelRatio; // 获取window对象的 devicePixelRatio属性值,这个属性值就是我们所说的设备像素比,简称dpr
  36. if (isIPhone) {
  37. // iOS下,对于2和3的屏,用2倍的方案,其余的用1倍方案
  38. if (devicePixelRatio >= 3 && (!dpr || dpr >= 3)) {
  39. dpr = 3; //
  40. } else if (devicePixelRatio >= 2 && (!dpr || dpr >= 2)){
  41. dpr = 2;
  42. } else {
  43. dpr = 1;
  44. }
  45. } else {
  46. // 其他设备下,仍旧使用1倍的方案
  47. dpr = 1;
  48. }
  49. scale = 1 / dpr;
  50. }
  51.  
  52. docEl.setAttribute('data-dpr', dpr); // 给页面根元素设置自定义属性data-dpr,值为前面已经赋值好的dpr
  53. if (!metaEl) { // 当name=viewport的mate标签不存在时,就给页面添加一个,各元素值为前面计算好的scale,并不允许用户拖动缩放
  54. metaEl = doc.createElement('meta');
  55. metaEl.setAttribute('name', 'viewport');
  56. metaEl.setAttribute('content', 'initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
  57. if (docEl.firstElementChild) {
  58. docEl.firstElementChild.appendChild(metaEl);
  59. } else {
  60. var wrap = doc.createElement('div');
  61. wrap.appendChild(metaEl);
  62. doc.write(wrap.innerHTML);
  63. }
  64. }
  65.  
  66. function refreshRem(){
  67. var width = docEl.getBoundingClientRect().width;
  68. if (width / dpr > 540) { // 对于逻辑像素大于540的设备,其宽度就设置为设备像素比乘以540
  69. width = 540 * dpr;
  70. }
  71. var rem = width / 10; // 将屏幕宽度分成10份,每一份为1rem 所以整个屏幕的完整宽度为10rem
  72. docEl.style.fontSize = rem + 'px'; // 设置根元素字体大小为计算所得的值
  73. flexible.rem = win.rem = rem;
  74. }
  75.  
  76. win.addEventListener('resize', function() {
  77. clearTimeout(tid);
  78. tid = setTimeout(refreshRem, 300);
  79. }, false);
  80. win.addEventListener('pageshow', function(e) {
  81. if (e.persisted) {
  82. clearTimeout(tid);
  83. tid = setTimeout(refreshRem, 300);
  84. }
  85. }, false);
  86.  
  87. if (doc.readyState === 'complete') {
  88. doc.body.style.fontSize = 12 * dpr + 'px';
  89. } else {
  90. doc.addEventListener('DOMContentLoaded', function(e) {
  91. doc.body.style.fontSize = 12 * dpr + 'px';
  92. }, false);
  93. }
  94.  
  95. refreshRem();
  96.    // 后面这段代码是将rem单位值转换成px的和将px单位的值换算成rem单位的值
  97. flexible.dpr = win.dpr = dpr;
  98. flexible.refreshRem = refreshRem;
  99. flexible.rem2px = function(d) {
  100. var val = parseFloat(d) * this.rem;
  101. if (typeof d === 'string' && d.match(/rem$/)) {
  102. val += 'px';
  103. }
  104. return val;
  105. }
  106. flexible.px2rem = function(d) {
  107. var val = parseFloat(d) / this.rem;
  108. if (typeof d === 'string' && d.match(/px$/)) {
  109. val += 'rem';
  110. }
  111. return val;
  112. }
  113.  
  114. })(window, window['lib'] || (window['lib'] = {}));

源码的分析已经注释到代码后面的注释中了。通过源码的整体分析,我们会发现,lib-flexible的工作原理可以概括为:

  通过获取设备像素比dpr进行运算,设置页面里name=viewport的mate标签(包括内部的缩放比例),再在页面根元素--html上添加data-dpr属性以及值,并且设置根元素字体大小,来进行页面适配的。

  随着技术的飞速发展,当前lib-flexible适配方案也在逐渐被更新的适配方案所替代,但是截止目前为止,还没有发现哪种方案能完全满足适配各种机型的需要,也会有一些小的问题。lib-flexible是目前用到的比较成熟的适配方案,所以,让我们一起继续探索吧~

css 手机适配的更多相关文章

  1. css的手机适配

    在html篇里提到设置视口宽度和设备宽度,固定的meta配置就是写死的,==死记硬背== 应该清楚的是手机端的适配应该克服的难题就是宽度根据手机屏幕的大小变化,而高度却没有办法跟随比例变化,也就是宽高 ...

  2. 手淘的flexible.js解决手机适配问题

    如何使用flexible.js做手机适配 做移动端网页肯定需要做适配,以前都用的fixscreen.js,对比一下,觉得flexible.js更好吧,毕竟是大厂出的东西. 第一步要给页面加在viewp ...

  3. Bootstrap3简单好用,轻松实现手机适配

    个人官网http://FansUnion.cn,前端使用Bootstrap框架.大部分的样式,轻松就实现了. 只是呢,关于导航条,被无数网友吐槽了.      通过手机访问时,导航条把屏幕给完全占居了 ...

  4. Android手机适配——UI图片适配

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/50727753 在Android项目当中,drawable文件夹都是用来放置图片资源 ...

  5. css - 兼容适配坑点总结(。。。)

    1. transform为代表的这些css3属性一定要写-webkit-,不然低版本(目前遇到的是8)的苹果,不支持. 2. x的适配 /* x */ @media only screen and ( ...

  6. 关于手机适配中的rem的学习随笔

    githup 下载地址 :https://github.com/comjustforfun/remformobile adaptivejs利用rem解决移动端页面开发的自适应问题 页面模板初始化的时候 ...

  7. scss牛刀小试:解决css中适配浏览器前缀问题

    在css中为适配浏览器,新特性总加 -webkit,-o, -moz 来适配浏览器,写的烦心,看着也臃肿,让css可读性降低,下面以阴影为例,如何使用scss让我们的css看起来更简洁. 本人使用的I ...

  8. 【css】适配iphoneX

    /*适配iphoneX*/ @media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-d ...

  9. css iphonex适配

    /*  iphonex适配 */ @media only screen and (device-width:375px) and (-webkit-device-pixel-ratio: 3) { . ...

随机推荐

  1. 通过python-libvirt管理KVM虚拟机 代码实现

    初步代码 <span style="font-size:18px;">''''' Work with virtual machines managed by libvi ...

  2. django源码解析之 BooleanField (二)

    class BooleanField(Field): empty_strings_allowed = False default_error_messages = { 'invalid': _(u&q ...

  3. JAVA-数据库之加载JDBC驱动程序

    相关资料:<21天学通Java Web开发> 加载JDBC驱动程序 JiaZaiDemo.jsp <%@ page language="java" content ...

  4. 一个JS引发的血案

    转载一篇大师傅的文章: 原文链接:http://xn--i2r.ml/index.php/2017/08/05/39.html 又到了周末,闲来无聊,挖挖补天 找了个目标,发现一个站 查看源码发现一个 ...

  5. RecyclerView 输出的和排版的不一样

    遇到过这样一种情况,就是RecyclerView加载出来的和看到Android Studio里的不一样 原因是: @Override public ViewHolder onCreateViewHol ...

  6. java获取classpath以外的路径

    最近在使用以前写过的代码生成器(从表名可生成所有的代码)的时候,发现生成的文件都在classpath目录下,所有的文件都得自己拷到工程目录下,于是,想优化一下,取得classpath目录以外的路径,很 ...

  7. metrics 开发监控实现jdbc

    Metrics 主要有五大基本组件1:Counter  记录执行次数2:Gauge  获取某个值3:Meter  用来计算事件的速率4:Histogram  可以为数据流提供统计数据. 除了最大值,最 ...

  8. Extjs4.x Ext.tree.Panel 遍历当前节点下的所有子节点

    Ext.define('WMS.controller.Org', { extend: 'Ext.app.Controller', stores: ['OrgUser', 'OrgTree'], mod ...

  9. RavenDb学习(八)高级特性上半部分

    .事务支持 别的关系型数据库和RavenDb一起使用 using (var transaction = new TransactionScope()) { BlogPost entity = sess ...

  10. mysq在某一刻同时获取主从库的位置点

    在从库进行锁表操作flush table with read lock, 通过show slave status\G 获取对应主库的位置点: show slave status\G********** ...