解決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;
  }
}
複製代碼
相關文章
相關標籤/搜索