前言: 个人总结, 结合各大神的文章, 如有不妥之处, 请指出~

前置概念

设备像素(Device Pixels)

设备屏幕的物理像素,表示屏幕上可以铺多少个点点,而不是一个绝对长度单位(例如in,mm); 单位是px,比如iPhone6的 (750 x 1334px)

分辨率(Resolution)

一个物理概念
对于屏幕,分辨率一般表示屏幕上显示的物理像素总和。比如,我们说iPhone6屏幕分辨率是(750 x 1334px)
对于图像,概念等同于图像尺寸、图像大小、像素尺寸等等。比如,我们说(20 x 20px)的icon

CSS像素(CSS Pixels)

是Web编程的概念,指的是CSS样式代码中使用的逻辑像素, 或者称为设备独立像素, 因为只与设备相关, 例如: iPhone6 375 x 667px
1个CSS像素在不同设备上可能对应不同的物理像素数,这个比值是设备的属性(Device Pixel Ratio,设备像素比)

通过 document.documentElement.clientWidth/clientHeight / document.documentElement.getBoundingClientRect().width 获取

在CSS规范中,长度单位可以分为绝对单位和相对单位。px是一个相对单位,相对的是设备像素(Device Pixels)

设备独立像素(device-independent pixels (DIP) / Density-independent Pixels (DP))

Android设备的特点是屏幕尺寸很多,因此为了显示能尽量和设备无关,提出了dip,参照的density是160。

// 当屏幕密度density为160(单位是ppi或者dpi,一个意思)时,px === dip
px = dip * density / 160 
// 所以
dip = px * 160 / dpi

注: 此处只针对于Android, windows 也有 DIP 概念, 含义不同, IOS貌似不存在

设备像素比 (Device Pixel Ratio (DPR))

Device pixel ratio, the ratio between physical pixels and logical pixels used by cascading style sheets (CSS): other names for it are “CSS Pixel Ratio” and “dppx”
表示1个CSS像素(宽度)等于几个物理像素(宽度)
DPR = 物理像素(设备像素) / 逻辑像素(css像素/设备独立像素) // [未缩放]

像素密度

像素密度也叫显示密度或者屏幕密度,缩写为DPI(Dots Per Inch)或者PPI(Pixel Per Inch)

// 屏幕对角线的像素尺寸 / 物理尺寸(inch 英寸)
Math.sqrt(750*750 + 1334*1334) / 4.7 = 326ppi

视口(viewport)

桌面上视口宽度等于浏览器宽度,但在手机上有所不同。

布局视口(layout viewport)

手机上为了容纳为桌面浏览器设计的网站,默认布局视口宽度远大于屏幕宽度,为了让用户看到网站全貌,它会缩小网站
document.documentElement.clientWidth

视觉视口(Visual viewport)

屏幕的可视区域,即物理像素尺寸, 可变, 与当前缩放值和设备的屏幕宽度有关
visual viewport宽度 = ideal viewport宽度 / 当前缩放值
可以通过window.innerWidth 来获取,但在Android 2, Oprea mini 和 UC 8中无法正确获取。

理想视口(ideal viewport)

ideal viewport是最适合移动设备的viewport,ideal viewport的宽度等于移动设备的屏幕宽度
在移动开发时, 在meta[name='viewport']中, 通过width = device-width把当前的viewport宽度设置为理想视口, 否则宽度将默认为布局视口
ideal viewport并没有一个固定的尺寸,不同的设备拥有有不同的ideal viewport。早期所有iPhone理想视口为320x480px

所以,在没有缩放的情况下,屏幕的CSS像素宽度其实是指理想视口的宽度,而meta标签:

<meta name="viewport" content="width=device-width, inital-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>

上面的meta指定了布局视口=理想视口,并且禁止缩放。所以添上width=device-width的viewport meta后页面变大了(一开始页面内容小得看不清),实际上是布局视口变小了
initial-scale=1 解决了 iphone、ipad 无论横竖屏都把宽度设为竖屏时 ideal viewport 的宽度
width=device-width 解决了IE 无论横竖屏都把宽度设为竖屏时 ideal viewport 的宽度

visual viewport宽度 vs ideal viewport宽度

visual viewport宽度 = ideal viewport宽度 / 当前缩放值
当前缩放值 = ideal viewport宽度 / visual viewport宽度

参考:
http://www.ayqy.net/blog/%e5%...
https://github.com/jawil/blog...

mate 中的 device-width / width:

  • device-width 是设备实际的宽度,只和设备的分辨率有关,一般是设备物理像素/设备像素比,且不会随着手机旋转而改变其值, 因此并不适合开发响应式网站 (css逻辑像素)
  • width 指的是可视区域的宽度,布局视口, 会和 viewport 的 scale 属性相关,为页面的可视区域的宽度

可以设置 <meta name="viewport" content="width=device-width"/>

适配方案

媒体查询

最常见的方式,通过屏幕宽度(用CSS像素描述的宽度)来区分各种设备
前提:

<meta name="viewport" content="width=device-width"/>
// 响应式网站
@media (min-width:320px) { /* smartphones, portrait iPhone, portrait 480x320 phones (Android) */ }
@media (min-width:480px) { /* smartphones, Android phones, landscape iPhone */ }
@media (min-width:600px) { /* portrait tablets, portrait iPad, e-readers (Nook/Kindle), landscape 800x480 phones (Android) */ }
@media (min-width:801px) { /* tablet, landscape iPad, lo-res laptops ands desktops */ }
@media (min-width:1025px) { /* big landscape tablets, laptops, and desktops */ }
@media (min-width:1281px) { /* hi-res laptops and desktops */ }

/* 另外 */
min-width: 480px: Will target mobile devices in landscape mode and up
// 移动端
@media screen and (min-width: 320px) {
    html {
        font-size: 50px;
    }
}
@media screen and (min-width: 360px) {
    html {
        font-size: 56px;
    }
}
@media screen and (min-width: 414px) {
    html {
        font-size: 63px;
    }
}

// 或者, 根据具体需求确定
@media screen and (max-width: 320px) {
  font-size: 32px;
}
@media screen and (min-width: 540px) {
  font-size: 54px;
}

缺点

难以覆盖所有的尺寸, 写起来比较繁琐

优点

兼容性良好

rem布局

rem: 根元素(html)的字体大小. 即 1rem = html中设置的 font-size

获取设备宽度
document.documentElement.getBoundingClientRect().width / document.documentElement.clientWidth

使用

原理: 比例问题, 根据设计图比例计算出固定 rem 值
实现: 主要通过修改 html 的 fontSize
实现一 直接引入 参考

   (function (doc, win) {
        var docEl = doc.documentElement,
            resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
            recalc = function () {
                var clientWidth = docEl.clientWidth;
                if (!clientWidth) return;
                if(clientWidth>=640){
                    docEl.style.fontSize = '100px';
                }else{
                    docEl.style.fontSize = 100 * (clientWidth / 640) + 'px'; // 640: 可根据设计图来定
                }
            };

        if (!doc.addEventListener) return;
        win.addEventListener(resizeEvt, recalc, false);
        doc.addEventListener('DOMContentLoaded', recalc, false);
    })(document, window);

实现二 使用flexible.js
flexible,js
lib-flexible 手淘rem方案
移动端适配与dpr无关, 与dpr关联是为了处理1px兼容问题, 具体参考: 真的,移动端尺寸自适应与dpr无关, 从网易适配方案和下面手淘最新版适配方案也可以看出

1, 旧版本
旧版本#17 代码

处理过程如下:

1.先取 dpr (dpr = window.devicePixelRatio)
2.然后设置 scale = 1/dpr
3.然后就有 innerWidth 了, innerWidth = 375 / scale = 750px (device-width = document.documentElement.clientWidth)
4.然后将 innerWidth 分成 10rem, font-size = innerWidth / 10 = 75px
;(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) {
            // 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;
    }

    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'] = {}));

2, 新版本
新版本 2.0代码

meta 标签固定为
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no”>

代码逻辑为:
取 width 分为 10rem, font-size = width / 10

注: 手机淘宝首页 2019-01-16, 也是这个方案, 但是是 width = 3.75rem, font-size = 100px, 估计是为了方便除

代码,添加部分注释

(function flexible (window, document) {
  var docEl = document.documentElement
  var dpr = window.devicePixelRatio || 1

  // adjust body font size
  function setBodyFontSize () {
    if (document.body) {
      document.body.style.fontSize = (12 * dpr) + 'px' 
    }
    else {
      document.addEventListener('DOMContentLoaded', setBodyFontSize)
    }
  }
  setBodyFontSize();

  // set 1rem = viewWidth / 3.6
  function setRemUnit () {
    // var rem = docEl.clientWidth / 10 // 原始代码
    var rem = docEl.clientWidth / 3.6  // 修改后代码
    docEl.style.fontSize = rem + 'px'
  }

  setRemUnit()

  // reset rem unit on page resize
  window.addEventListener('resize', setRemUnit)
  window.addEventListener('pageshow', function (e) {
    // 参考: https://juejin.im/post/5c807802f265da2de33f4e1f
    // 页面从浏览器的缓存中读取该属性返回 ture, 优化方案 window.performance.navigation.type
    // if (e.persisted) {  // 原始代码
    //   setRemUnit()
    // }
    if (e.persisted || (window.performance && window.performance.navigation.type === 2)) { // 修改后代码
      setRemUnit()
    }
  })

  // detect 0.5px supports 检测是否支持0.5px, 用于1px问题
  if (dpr >= 2) {
    var fakeBody = document.createElement('body')
    var testElement = document.createElement('div')
    testElement.style.border = '.5px solid transparent'
    fakeBody.appendChild(testElement)
    docEl.appendChild(fakeBody)
    if (testElement.offsetHeight === 1) {
      docEl.classList.add('hairlines')
    }
    docEl.removeChild(fakeBody)
  }
}(window, document))

缺点:

1、iframe 问题
2、富文本问题

  1. 高清方案
  2. 部分android机型不兼容

5、系统字体缩放时, 发生变化, 因为和根元素紧密联系 - 解决方案: 缩放还原, 具体如下:
1> https://juejin.im/post/59f678...
2> 基于 flexible.js 添加以下代码: https://juejin.im/post/5b9cb9...

var root = window.document.documentElement;
var fontSize = parseFloat(root.style.fontSize);
var finalFontSize = parseFloat(window.getComputedStyle(root).getPropertyValue("font-size"));
if(finalFontSize !== fontSize) {
    root.style.fontSize = fontSize * fontSize / finalFontSize + "px";
}

注:
window.getComputedStyle(docEl).getPropertyValue('font-size') // html最终的font-size大小

getComputedStyle

Window.getComputedStyle()方法返回一个对象,该对象在应用活动样式表并解析这些值可能包含的任何基本计算后报告元素的所有CSS属性的值。 私有的CSS属性值可以通过对象提供的API或通过简单地使用CSS属性名称进行索引来访问。

getPropertyValue

此 CSSStyleDeclaration.getPropertyValue() 接口会返回一个 DOMString ,这个返回值将会包含预请求的CSS属性信息。

vw/vh (css3新增属性)

1vw: 可视窗口宽度1%
1vh: 可视窗口高度1%
vmin/vmax: vw和vh中最小/最大的那个

实现:

<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />
设计图尺寸:设计图元素尺寸 = 100vw:Y // Y 表示css样式中设计图某元素的大小, 单位vw

缺点:
1、没有最大/最小限制
2、兼容性: ios8/andorid4,4 以上

vw + rem

优点:

  1. 实现简单
    不依赖插件及第三方库
    几行css代码 就可以实现
  2. 开发方便, 方便换算[100vw === (设计稿 / 100) rem === document.documentElement.clientWidth]
  3. 不影响px使用,完美兼容第三方组件库
  4. 不存在富文本和iframe等兼容问题

实现

(以下实现以[rem与vw关系: rem:vw = 10:1 或者 1rem = 10vw]为依据)
前提: <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" />

1、 html 的 font-size 等于 1/10 的视口宽度 (即: 1rem = 1 / 10 * 100vw => 等同于lib-flexible 中 document.documentElement.style.fontSize = clientWidth / 10) [此处取1/10, 因为在淘宝方案也是取这个值, 为了更好计算可以去其他值]

// $vw_design: 设计图尺寸
// $vw_fontsize: 设计图尺寸 / 10  假设把设计图分为10份, 每份的大小(设计图的1rem), 并以此为基数
html {
  font-size: ($vw_fontsize / $vw_design) * 100vw; // 直接写 10vw

  // 同时,通过Media Queries 限制根元素最大最小值
  @media screen and (max-width: 320px) {
    font-size: 32px;
  }
  @media screen and (min-width: 540px) {
    font-size: 54px;
  }
}

2、计算使用scss函数: 设计图元素尺寸 / (设计图尺寸 / 10) * 1rem

// $basesize: 设计图元素尺寸
@function rem($basesize) {
  @return ($basesize / $vw_fontsize) * 1rem; 
}

//简化
@function rem($basesize) {
  @return ($basesize / $vw_design) * 10rem; 
}

计算原理:

假设设计图元素所占视口大小为 X, 单位为: vw

设计图元素尺寸 / 设计图尺寸 = (Y)rem / 100vw 
=> (X)vw = (设计图元素尺寸 / 设计图尺寸) * 100vw 
// 转化为以rem单位的数值Y
=> (Y)rem = (X)vw / 10 

参考: https://juejin.im/post/5c0357...

测试

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <!-- <meta name="viewport" content="width=640, user-scalable=no"> -->
    <!-- <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no" /> -->
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Test</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }
        html {
            /* font-size: 13.33vw; */
        }
        #box {
            width: 2.667rem;
            height: 2.667rem;
            /* width: 1rem;
            height: 1rem; */
            /* width: 13.33vw;
            height: 13.33vw; */
            background: #ccc;
            border: 1px solid #000;
        }
    </style>
  </head>
  <body>
  
    <script>
        (function () {
            var dpr = window.devicePixelRatio;
            var meta = document.createElement('meta');
            var scale = 1 / dpr;
            meta.setAttribute('name', 'viewport');
            meta.setAttribute('content', 'width=device-width, user-scalable=no, initial-scale=' + scale +
            ', maximum-scale=' + scale + ', minimum-scale=' + scale);
            document.getElementsByTagName('head')[0].appendChild(meta);
            // 动态设置的缩放大小会影响布局视口的尺寸
            function resize() {
                var deviceWidth  = document.documentElement.clientWidth; // 不设置 meta[name='viewport'], 等于980px
                document.documentElement.style.fontSize = (deviceWidth / 10) +'px';
            }
            resize();
            window.onresize = resize;
        })()
        // dpr = 2
        // 设备逻辑像素 = 设备物理像素(固定) / (dpr * scale) = 750 / (2 * 0.5) = 750 // 视口放大一倍
        // 当前缩放比 = ideal viewport / visual viewport(设备逻辑像素) = 375 / 750 = 0.5 // 页面缩小一倍
    </script>
    <div id="box">
    </div>
  </body>
</html>

adaxxl
72 声望0 粉丝