amfe-flexible

1,424 阅读5分钟

概念

rem

相对长度单位,相对于根元素(即html元素)font-size计算值的倍数

Element.clientWidth

属性表示元素的内部宽度,以像素计。该属性包括内边距,但不包括垂直滚动条(如果有)、边框和外边距。

Document​.documentElement

是一个会返回文档对象(document)的根元素的只读属性(如HTML文档的 元素)。

window.devicePixelRatio

此属性返回当前显示设备的物理像素分辨率与CSS像素分辨率的比值。该值也可以被解释为像素大小的比例:即一个CSS像素的大小相对于一个物理像素的大小的比值。

视窗 viewport

简单的理解,viewport是严格等于浏览器的窗口。在桌面浏览器中,viewport就是浏览器窗口的宽度高度。但在移动端设备上就有点复杂。 移动端的viewport太窄,为了能更好为CSS布局服务,所以提供了两个viewport:虚拟 visualviewport和布局的layoutviewport

物理像素(physical pixel)

物理像素又被称为设备像素,他是显示设备中一个最微小的物理部件。每个像素可以根据操作系统设置自己的颜色和亮度。正是这些设备像素的微小距离欺骗了我们肉眼看到的图像效果

设备独立像素(density-independent pixel)

设备独立像素也称为密度无关像素,可以认为是计算机坐标系统中的一个点,这个点代表一个可以由程序使用的虚拟像素(比如说CSS像素),然后由相关系统转换为物理像素。

CSS像素==设备独立像素

CSS像素是一个抽像的单位,主要使用在浏览器上,用来精确度量Web页面上的内容。一般情况之下,CSS像素称为与设备无关的像素(device-independent pixel),简称DIPs。

Document: DOMContentLoaded 事件

当纯HTML被完全加载以及解析时,DOMContentLoaded 事件会被触发,而不必等待样式表,图片或者子框架完成加载。 在你的脚本有机会运行前,DOMContentLoaded可能就已经被触发。所以你在决定添加一个事件监听器前最好先检查一下。

function doSomething() {
  console.info('DOM loaded');
}

if (document.readyState === 'loading') {  // 此时加载尚未完成
  document.addEventListener('DOMContentLoaded', doSomething);
} else {  // 此时`DOMContentLoaded` 已经被触发
  doSomething();
}

document.readyState

一个document 的 Document.readyState 属性描述了文档的加载状态。

当该属性值发生变化时,会在document 对象上触发readystatechange事件。 一个文档的 readyState 可以是以下之一:

  • loading / 正在加载 document 仍在加载。
  • interactive / 可交互 文档已被解析,"正在加载"状态结束,但是诸如图像,样式表和框架之类的子资源仍在加载。
  • complete / 完成 文档和所有子资源已完成加载。表示 load 状态的事件即将被触发。 当这个属性的值变化时,document 对象上的readystatechange 事件将被触发。

pageshow

当一条会话历史记录被执行的时候将会触发页面显示(pageshow)事件。(这包括了后退/前进按钮操作,同时也会在onload 事件触发后初始化页面时触发)

window.addEventListener('pageshow', function(event) {
    console.log('pageshow:');
    console.log(event);
});

persisted

The persisted read-only property indicates if a webpage is loading from a cache.

设备像素比(device pixel ratio)

设备像素比简称为dpr,其定义了物理像素和设备独立像素的对应关系。它的值可以按下面的公式计算得到:

设备像素比 = 物理像素 / 设备独立像素

在JavaScript中,可以通过window.devicePixelRatio获取到当前设备的dpr。而在CSS中,可以通过-webkit-device-pixel-ratio,-webkit-min-device-pixel-ratio和 -webkit-max-device-pixel-ratio进行媒体查询,对不同dpr的设备,做一些样式适配(这里只针对webkit内核的浏览器和webview)。

众所周知,iPhone6的设备宽度和高度为375pt * 667pt,可以理解为设备的独立像素;而其dpr为2,根据上面公式,我们可以很轻松得知其物理像素为750pt * 1334pt

开始适配

首先通过设置meta,其主要作用的是width=device-width,使用这个之后,document.documentElement.clientWidth就等于设备独立像素的宽度。

<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">

然后给root元素设置fontSize为document.documentElement.clientWidth的十分之一,这样1rem就等于document.documentElement.clientWidth/10,以此做适配。

rem并非是完美的适配方案,使用了rem,最后渲染时还是转换成px,这时小数部分就四舍五入,有些结果并不是我们想要的。

代码:

  (function flexible(window, document) {
            var docEl = document.documentElement // 返回文档的root元素
            var dpr = window.devicePixelRatio || 1 // 获取设备的dpr,即当前设置下物理像素与虚拟像素的比值
            // adjust body font size
            // 调整body标签的fontSize,fontSize = (12 * dpr) + 'px'
            // 设置默认字体大小,默认的字体大小继承自body
            function setBodyFontSize() {
                if (document.body) {
                    document.body.style.fontSize = (12 * dpr) + 'px'
                }
                else {
                    document.addEventListener('DOMContentLoaded', setBodyFontSize)
                }
            }
            setBodyFontSize();

            // set 1rem = viewWidth / 10
            // 设置root元素的fontSize = 其clientWidth / 10 + ‘px’
            function setRemUnit() {
                var rem = docEl.clientWidth / 10
                docEl.style.fontSize = rem + 'px'
            }

            setRemUnit()

            // reset rem unit on page resize
             // 当页面展示或重新设置大小的时候,触发重新
            window.addEventListener('resize', setRemUnit)
            window.addEventListener('pageshow', function (e) {
                if (e.persisted) {
                    setRemUnit()
                }
            })
            
            // detect 0.5px supports
            // 检测0.5px的支持,支持则root元素的class中有hairlines
            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))

针对华为,小米的部分机型,微信内置浏览器产生的rem不能正确填充满的问题,产生这个情况的原因是因为给html附font-size时,附上的font-size和实际上html的font-size 大小并不一致

尝试找了多个问题机型,最终的比例都是1.25左右(1.24999),所以解决方案如下

~function () {
    let docEl = document.documentElement;
    // set 1rem = viewWidth / 10
    // 设置root元素的fontSize = 其clientWidth / 10 + ‘px’
    function setRemUnit() {
        var rem = docEl.clientWidth / 10
        let real_font=parseFloat(window.getComputedStyle(document.getElementsByTagName("html")[0]).fontSize.split('p')[0])
        //检测部分华为手机fontsize问题
        if(real_font*120*100<rem*10000){
            docEl.style.fontSize = rem*100*125/10000 + 'px'
        }
   
    }

    setRemUnit()

    // reset rem unit on page resize
    // 当页面展示或重新设置大小的时候,触发重新
    window.addEventListener('resize', setRemUnit)
    window.addEventListener('pageshow', function (e) {
        if (e.persisted) {
            setRemUnit()
        }
    })

}(window, document)