解决flexible适配方案下部分安卓手机1px不可见的问题

概述

早期前端项目中使用了flexible解决移动端适配问题,而且经过px2rem将px进行转换rem。javascript

flexible中将页面分为10等份。每一份的大小即1rem的值。该值做为docEl的fontSize大小。在HTML文档中,docEl即html标签。css

// 已经通过了scale拉升处理
var width = docEl.getBoundingClientRect().width;
if (width / dpr > 540) {
    width = 540 * dpr;
}
var rem = width / 10;
docEl.style.fontSize = rem + 'px';
复制代码

在webpack中,经过配置插件,将css文件中的px转换成rem。html

{
  loader: 'postcss-loader',
  options: {
    plugins: [
      postcssAutoprefixer({
        browsers: autoprefix
      }),
      px2rem({
        remUnit: 75
      }),
    ],
    sourceMap: false,
  },
},
复制代码

remUnit设置为75,10rem是750px。即iPhone6物理像素。和设计稿一致。前端

设计稿中常出现出现1px的边框等,代码中这样写会被转换成0.013333rem。java

不考虑/* px */ 和 /* no */的状况,前者在dpr为3的手机上被转换成1.5px,四舍五入是2。和2px表现一致(大多数状况)。后者恒定的1px,也不够灵活。android

iPhone手机

以iPhone6(375 * 667)为例,devicePixelRadio的值为2,initial-scale为0.5,页面放大两倍,html宽度变为750px。经过flexible处理后,1rem是75px,那么0.013333rem刚好是1px,与设计稿一致。webpack

// 注意代码执行顺序很重要。先进行缩放,而后才是获取html的宽度。
// var width = docEl.getBoundingClientRect().width;
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);
      }
  }
复制代码

以iPhone 6/7/8 Plus(414 * 736)为例,devicePixelRadio的值是3,1rem的值是414 * 3 / 10 = 124.2px。那么0.013333rem的值是1.65左右,四舍五入是2。和2px表现一致(大多数状况)。因为Plus系列更宽,因此Plus上的2px和非Plus上的1px视觉上差距不大,能够接受。git

安卓手机

flexible的源码中,对安卓机直接设置dpr为1。也就是device-width。以个人一加6为例,device-width是412。因此1rem就是41.2px。css中写1px会被转换为0.013333 * 41.2获得的值是0.55px,四舍五入是1。在个人手机上还能够看得见,github

安卓手机上,这样可能会形成一个问题,当0.013333 * device-width / 10 < 0.5的时候,可能会在手机上看不到了(只是可能看不到)。当device-width的值小于375的时候,css中1px像素可能会看不到。web

我在vivo Y85中遇到过这个问题。

解决1px不可见问题

因此对flexible进行了改动,再也不进行区分iOS和安卓。代码以下。

if (!dpr && !scale) {
    var devicePixelRatio = win.devicePixelRatio;
    dpr = devicePixelRatio;
    scale = 1 / dpr;

    var dataDpr;
    if(devicePixelRatio >= 2.5 && (!dpr || dpr >= 2.5)) {
      dataDpr = 3;
    } else if (devicePixelRatio >= 1.5 && (!dpr || dpr >= 1.5)) {
      dataDpr = 2;
    } else {
      dataDpr = 1;
    }

    docEl.setAttribute('data-dpr', dataDpr);
}

if (!doc.querySelector('meta[name="viewport"]')) {
    metaEl = doc.createElement('meta');
    metaEl.setAttribute('name', 'viewport');
    // 顺便说一句,flexible这里是没有width=device-width配置的,改动后加上了。不加上可能会出现页面变形问题。
    metaEl.setAttribute('content', 'width=device-width, 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);
    }
}
复制代码

scale仍然根据设备devicePixelRatio进行缩放,让html的宽度等于设备的物理像素。好比个人一加6,device-width是412,devicePixelRadio的值是2.625,document.documentElement.getBoundingClientRect().width的值是1081,几乎就是412 * 2.625的值。据我测试过多台安卓机器,这两个值误差都在1之内。因此1rem的值就是108.1,css中1px转换的结果是0.013333 * 108.1 = 1.44,视觉上和1px一致。

理论上,若是让1px像素不可见,须要符合如下条件:

0.013333 * device-width * devicePixelRadio / 10 < 0.5
复制代码

须要device-width * devicePixelRadio < 375,因为如今设备devicePixelRadio几乎都大于2,想要找到符合该条件的几乎不存在。这只是理论上不可见的充分必要条件,实际中仍是根据机型而定。可是无论如何,这种写法比以前的写法更为稳妥。

html上设置data-dpr属性,该属性是将devicePixelRatio四舍五入获得的。对一些元素设置背景图像(或其余行为)时,根据该属性不一样设置不一样像素的背景图。

// scss的mixin语法
@mixin bg($url1,$url2){
  [data-dpr="1"] & {
    background: url($url1) no-repeat top center/750px 670px, #ff776e;
  }
  [data-dpr="2"] & {
    background: url($url1) no-repeat top center/750px 670px, #ff776e;
  }

  [data-dpr="3"] & {
    background: url($url2) no-repeat top center/1125px 1005px, #ff776e;
  }
}
复制代码
相关文章
相关标签/搜索