移動端佈局適配hotcss+postcss-pxtorem

背景

原本團隊是搞PC端開發的,前段時間架構調整,移動端人力不足,因此團隊把移動端的工做也一塊兒接過來了。css

不過因爲咱們團隊之前沒開發過移動端的東西,技術積累較少,所以花費了很多時間在基礎技術積累上面。前端

此次介紹一下咱們的移動端適配方案的改進過程。webpack

hotcss庫

最開始進行移動端開發時,因爲工期比較緊張,爲了快速構建整個項目,我最後選擇使用了hotcss 這個庫。 這個庫的優勢其實挺多的:git

  1. 使用方便
  2. 支持不一樣的設計稿尺寸
  3. 支持在js代碼中換算適配後的像素
  4. 有效解決1px問題

最開始選用這個庫的緣由其實也是由於使用方便,當初通過測試以後發現能夠比較好的還原設計稿的尺寸後,咱們就選擇使用這個方案了。github

不過因爲擔憂cdn訪問延遲和dns解析能問題致使hotcss.js資源加載失敗,再加上自己源碼也不長,咱們直接將源碼壓縮後內聯進模板裏面。web

不過使用一段時間以後,發現hotcss方案確實也有很多的坑。數組

1px/0.5px問題

通過咱們QA同窗的真機測試,發如今部分安卓4手機dpr=1的狀況下,1px0.5px的樣式通過px2rem的轉換後,仍是會有問題,在dpr=1的低端機上,跟標籤font-size會被計算成20px(仍是22.5px有點忘記了),在這種狀況下某些很小的像素如1px或者0.5px就有可能由於寬度小於了瀏覽器顯示的最小寬度,因此咱們爲了兼容1dpr的手機,不得不專門給這種小像素的樣式寫了一個dpr的選擇器的兼容方案(其餘css樣式同理):瀏覽器

@mixin border-bottom($width: 1) {
  border-bottom: px2rem($width) solid map-get($defaultColor, borderColor);
  [data-dpr='1'] & {
    border-bottom-width: $width + 'px';
  }
}
複製代碼

這個問題我的感受更多的仍是0.5px致使的,可是架不住UI就是喜歡用0.5px的分割線和border...sass

設計稿尺寸問題

其實這個問題並非hotcss庫自己的問題,應該讓UI同窗出設計稿的時候,給前端同窗一套統一的尺寸。架構

可是咱們最開始的UI同窗因爲我的習慣問題,有的給咱們的是iOS尺寸的設計稿,這種的尺寸是375px的,有的是安卓尺寸的設計稿360px的。而hotcss這個庫須要提早聲明$designWidth這個變量,以後使用px2rem()方法的時候纔會進行替換,因爲css沒有做用域的概念,因此須要使用sass和less。

sass文件編譯時候也是從上往下順序執行的,變量聲明能夠覆蓋,所以若是使用了sass或者less這種css擴展語言的話,這個地方應該是沒有問題的,不過要記得給每個sass和less文件都聲明$designWidth,不然可能會由於某些文件沒有聲明變量致使使用了其餘頁面聲明的尺寸而出現異常。

因此最佳的解決辦法仍是讓UI統一尺寸,。

書寫繁瑣

說實話雖然使用起來很方便,只須要引入一個官方的sdk就完成了移動端適配方案。可是當寫起業務css代碼的時候,仍是感受hotcss使用起來很繁瑣。

當UI稿中的尺寸是這樣的時候:

font-size: 16px;
font-height: 20px;
height: 50px;
width: 200px;
複製代碼

咱們就不能直接copy過來了,須要對其進行單位轉換:

// 這一塊代碼能夠放到common.scss中
@function px2rem($px) {
  @return $px * 320 / $designWidth/20 + rem;
}

$designWidth: 360;

font-size: px2rem(16);
font-height: px2rem(20);
height: px2rem(50);
width: px2rem(200);
複製代碼

總之當業務穩定以後,咱們慢慢才發現用這個方案仍是很是麻煩的,就常常被同事吐槽。

基於以上種種緣由,咱們團隊在升級打包工具webpack->razzle的過程當中,改進了適配方案,採用了hotcss+postcss-pxtorem結合的方案。

hotcss配合postcss-pxtorem

postcss-pxtorem這個工具庫的使用介紹網上也處處都有,這個庫的優勢在於有很強的轉換規則,能夠適配各類場景。咱們在這裏使用這個插件是爲了自動進行px->rem的轉化,其餘過程仍是由hotcss來進行。

postcss-px2rem的一些參數
require('postcss-pxtorem')({
  rootValue: 75,
  unitPrecision: 5,
  propList: ['*'],
  selectorBlackList: [],
  replace: true,
  mediaQuery: false,
  minPixelValue: 12
})
複製代碼
  1. rootValue是根標籤的font-size大小
  2. unitPrecision是轉換成rem後的小數位數
  3. propList是須要轉換的屬性列表
  4. selectorBlackList則是一個對css選擇器進行過濾的數組,好比你設置爲['fs'],那例如fs-xl類名,裏面有關px的樣式將不被轉換,這裏也支持正則寫法。
  5. minPixelValue能夠設置小於多少尺寸將不會進行轉換。
postcss.cofnig.js
const PostCssFlexBugFixes = require('postcss-flexbugs-fixes');
const autoprefixer = require('autoprefixer');
const px2rem = require('postcss-pxtorem');
const path = require('path');
const designWidth = 360; // 統一的視覺稿尺寸
const rootValue = (designWidth * 20) / 320; // root FontSize
module.exports = ctx => {
  const remInclude = [
    /\.rem\.less$/,
    /\.rem\.scss$/,
  ]; // 白名單,命中白名單則進行px2rem轉換,包括.rem.less、.rem.scss結尾的文件
  const useRem = remInclude.some(x =>
    x.test(path.join(ctx.file.dirname, ctx.file.basename))
  );
  return {
    plugins: [
      PostCssFlexBugFixes, //修復某些瀏覽器的flex bug
      autoprefixer({
        // autprefixer
        browsers: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9'],
        flexbox: 'no-2009'
      }),
      useRem &&
        px2rem({
          rootValue,
          propList: ['*'],
          minPixelValue: 1
        })
    ].filter(Boolean)
  };
};

複製代碼

postcss-pxtorem完成對rem的轉換,加上以前hotcssfont-size值的計算以及對viewport的初始化,咱們最終完成了移動端的適配方案。

其中設置白名單列表是爲了兼容之前老版本使用hotcss時候的寫法,在升級razzle完畢以後咱們後續的樣式所有統一使用.rem.scss做爲文件名後綴了,這樣在樣式文件中正常寫px單位便可,postcss會自動爲咱們進行轉換。

hotcss縮放scale的坑

最近使用一些第三方插件(如echarts和視頻播放器等)的時候,發現hotcss根據dpr給頁面設置的meta[viewport]和插件之間會有坑。

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

因爲第三方插件通常讀取過一次頁面的scale以後,後續scale的修改不會影響這些樣式。

hotcss會從新根據dpr從新設置scale,這種狀況下當css加載得比js慢的時候,就會致使頁面樣式會閃爍,而且第三方插件的樣式都是根據最開始的scale進行佈局的,所以在頁面放大以後會顯得很小。

不過這種狀況通常在本地開發的時候纔會出現,線上模式靜態資源通常放入cdn,訪問速度很快,在css加載速度快於js的時候就不會出現這個問題了。

不過爲了在本地開發的時候方便觀測樣式,咱們須要在客戶端執行js的時候從新將font-size設置回1.0。

document.documentElement.style.fontSize = `${(window.innerWidth * 20) /
    320 / window.devicePixelRatio}px`;
let viewportEl = document.querySelector('meta[name="viewport"]');
const content = `width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no`;
if (viewportEl) {
  viewportEl.setAttribute('content', content);
} else {
  viewportEl = document.createElement('meta');
  viewportEl.setAttribute('name', 'viewport');
  viewportEl.setAttribute('content', content);
  document.head.appendChild(viewportEl);
}
複製代碼

這樣整個頁面上的第三方插件在本地調試的時候也是正常大小了~

相關文章
相關標籤/搜索