移動端採坑系列

技術選型

採用react框架開發移動端的話,通常的架構是Preact + antd-mobile + react-hammerjs + iscroll。css

Preact

Preact是React的3kb輕量化方案,擁有一樣的ES6 API,其擁有如下優勢:
1. 體積小,React V16.0有34.8kb,而Preact官網聲稱只有3kb
2. 性能高,是最快的虛擬DOM框架之一
3. 生態好,官方提供preact-compat,能夠無縫使用React生態系統中的各種組件

遷移指南

1:安裝preactpreact-compatpreact-compat會讓你編譯後的代碼量增長2kb左右,可是它勝在支持npm倉庫中的絕大多數的React模塊,另外,preact-compat包在Preact 的基礎上提供必要的適配,讓它表現的跟react和react-dom同樣。html

npm i -S preact preact-compat

2:在webpack配置resolve.alias中,將reactreact-dom的路徑指向到preact-compatreact

{
  "resolve": {
    "alias": {
      "react": "preact-compat",
      "react-dom": "preact-compat"
    }
  }
}

頁面終端適配

flexible方案

關於移動端的適配佈局有不少解決方案,其中手機淘寶使用Flexible的佈局方案,在項目中只須要引入lib-flexible庫便可。webpack

<script src="http://g.tbcdn.cn/mtb/lib-flexible/{{version}}/??flexible_css.js,flexible.js"></script>

flexible的實質是JS動態修改meta的viewport,其主要作了如下幾件事:git

  1. JS動態修改meta標籤
  2. 給html元素添加data-dpr屬性,而且動態修改data-dpr的值
  3. 給html元素添加font-size屬性,而且動態修改font-size的值

其核心代碼以下:github

(function(){
    var metaEl = document.querySelector('meta[name="viewport"]')
    var dpr = 0
    var scale = 0
    if(!dpr && !scale){
        var isIPhone = window.navigator.appVersion.match(/[iphone|ipad]/gi)
        var devicePixelRatio = window.devicePixelRatio
        if(isIPhone) {
            dpr = devicePixelRatio
        } else {
           dpr = 1
        }
    }
    docEl.setAttribute('data-dpr', dpr)
    if (!metaEl) {
        metaEl = document.createElement('meta');
        metaEl.setAttribute('name', 'viewport');
        metaEl.setAttribute('content', 'width=device-width, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
        document.documentElement.firstElementChild.appendChild(metaEl);
  } else {
        metaEl.setAttribute('content', 'width=device-width, initial-scale=' + scale + ', maximum-scale=' + scale + ', minimum-scale=' + scale + ', user-scalable=no');
  }
})()

設置動態縮放視口後,在iphone6上,CSS像素的2px顯示效果爲1px,而在dpr=1的設備上,CSS像素的1px顯示爲1px,爲了讓字體在不一樣手機上顯示效果一致,則須要動態修改html的font-size,且頁面中使用rem做爲單位。web

/** 設置根元素font-size
    * 當設備寬度爲375(iPhone6)時,根元素font-size=16px
    */
var clientWidth = win.innerWidth
    || doc.documentElement.clientWidth
    || doc.body.clientWidth
if (!clientWidth) return
var fz = 16 * clientWidth / 375
document.style.fontSize = fz + 'px'

在開發頁面時,能夠藉助less或者sass寫一個函數,能夠將px數值轉化爲rem數值,這樣就能很方便的使用px。npm

viewport方案

vw(view-width),vh(view-height)是CSS3新增的兩個單位,表示視區寬度/高度,視區總寬度爲100vw,總高度爲100vh,隨着viewport單位愈來愈受到總多瀏覽器的支持,vw就能夠直接運用因而適配佈局中。canvas

vw: 1vw等於window.innerWidth的1%
vh: 1vh等於window.innerHeight的1%
vmin: vmin的值是當前vw和vh中較小的值
vmax: vmax的值是當前vw和vh中較大的值

在該方案中,可使用postcss-px-to-viewport插件,將css中的px直接轉化爲vw,vh,該插件參數配置以下:瀏覽器

"postcss-px-to-viewport": { 
    viewportWidth: 750, 
    viewportHeight: 1334, 
    unitPrecision: 5, 
    viewportUnit: 'vw', 
    selectorBlackList: [], 
    minPixelValue: 1,  
    mediaQuery: false 
}

兩種方案的對比

  1. vw/vh能夠直接使用,不須要藉助第三方的依賴
  2. postcss-px-to-viewport插件會將css中的px都轉化成vw/vh單位,尤爲是第三方的UI框架,這實際上是咱們所不指望的,對於不但願轉換的px樣式需額外添加.ignore類名
  3. 將px轉換爲vw/vh後,在瀏覽器上調試很不方便
  4. postcss-px-to-viewport並不能將內嵌樣式中的px轉換成vw/vh
  5. 須要額外處理1px邊框的問題

1px邊框問題

flexible

flexible下1px邊框是最容易處理的,由於flexible動態修改了viewport視口縮放比例,因此直接設置border:solid 1px #ddd;便可,可是flexible對Android機並無作任何處理,因此在Android機上線條很是粗,且因爲選用了vw/vh方案適配頁面,因此不得不考慮其餘方案處理1px的問題。

border-image屬性

可使用postcss-write-svg插件,代碼以下:

@svg square { 
    height: 2px;
    @rect { 
        fill: var(--color, black); 
        width: 100%; 
        height: 50%; 
    }
} 
#example { 
    border: 1px solid transparent;
    border-image: svg(square param(--color #00b1ff)) 2 2 stretch;
}

postcss-write-svg插件會將上面的代碼編譯成以下代碼:

#example { 
    border: 1px solid transparent; 
    border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' height='2px'%3E%3Crect fill='%2300b1ff' width='100%25' height='50%25'/%3E%3C/svg%3E") 2 2 stretch;
}

該方案簡單易用,比修改圖片要簡單快捷得多,具體請參考 postcss-write-svg
在添加postcss-write-svg插件時,若是同時使用了postcss-px-to-viewport插件,必定要將postcss-write-svg插件放置在前面,且在safari瀏覽器中,border: 1px solid transparent; 屬性會看不見線條,須要將transparent去掉。

after僞類 + scale

在所需元素上添加:after僞類,並進行縮放,代碼以下:

#example {
    position: relative;
}
// 添加1px的下邊界
#example:after {
    content: '',
    position: absolute;
    border-bottom: solid 1px #ddd;
    bottom: 0;
    left:0;
    width: 100%;
    transform: scale(1 , 1 / 2)
}

這裏的縮放的倍數是所在移動端1/dpr,因此這裏也須要藉助媒體查詢來在不一樣dpr下縮放不一樣倍數:

@media screen and ( -webkit-min-device-pixel-ratio : 2 ),       
    ( min--moz-device-pixel-ratio : 2 ),      
    ( min-resolution: 2dppx ) {
    #example:after {
        transform: scale(1 , 1 / 2)
    }
}
@media screen and ( -webkit-min-device-pixel-ratio : 3 ),       
    ( min--moz-device-pixel-ratio : 3 ),      
    ( min-resolution: 3dppx ) {
    #example:after {
        transform: scale(1 , 1 / 3)
    }
}

該方案的缺點:

  1. 很難添加四個方向的1px邊框,須要嵌套多層元素才能實現
  2. 在添加下邊界1px邊框線條時,線條並非在元素最下邊,這是因爲縮放後,最下邊爲空白區域,若是將bottom: -1px時,元素若是有overflow: hidden;屬性,那麼邊框線條也就看不見了。

點擊延遲

,移動端在派發點擊事件時,一般會出現300ms的延遲,這是因爲移動端瀏覽器爲了判斷用戶是否雙擊致使的,當用戶第一次點擊後,在接下來一段時間內用戶未進行下一次點擊,瀏覽器纔會當作單擊事件處理。

解決方法

禁止縮放

<meta name="viewport" content="width=device-width user-scalable= 'no'">

當頁面存在須要放大一張圖片和一段字體很小的文本時,該方法就不可取。

指針事件

指針事件由微軟提出,現已經進入W3C規範的候選推薦標準階段,它的主旨是對全部的輸入類型,進行統一的處理,例如:只須要監聽一個元素的pointerdown事件,無需分別監聽其touchstart和mousedown事件。

指針事件新增了一個CSS屬性touch-action,當在body元素上設置touch-action: none;,完全禁止了雙擊縮放,也就解決了點擊延遲的問題,不過目前只有IE實現了指針事件。

指針事件的 polyfill:polyfill是在非IE瀏覽器中模擬指針事件,不過若是隻是爲了解決點擊延遲的問題,該方案就顯得有點過了。

FastClick

原理: FastClick是在檢測到touchend事件時,經過DOM自定義事件當即觸發一個模擬click事件,並把瀏覽器在300ms後真正觸發的click事件阻止掉。

使用方法:

<script src="https://as.alipayobjects.com/g/component/fastclick/1.0.6/fastclick.js"></script>
<script>
    if ('addEventListener' in document) {
        document.addEventListener('DOMContentLoaded', function() {
            FastClick.attach(document.body);
        }, false);
    }
</script>

就目前而言,FastClick很是實際地解決了300ms點擊延遲的問題。

滾動問題

總結移動端技術選型

目前比較好的方案,推薦使用Preact做爲其基礎框架,使用CSS3新增的vw/vh單位解決頁面適配等問題,經過postcss插件postcss-px-to-viewport自動將css的px單位轉換爲vw/vh單位,1px邊框採用border-image屬性,藉助postcss-write-svg插件減小對邊框圖片的操做,再使用react-hammerjs監聽操做手勢,使用iScroll模擬移動端滑動。

echarts移動端的bug

本文是採用下面的方式直接引入echarts,並無使用echarts-for-react等第三方庫。

import echarts from 'echarts/lib/echarts'
import 'echarts/lib/chart/line'
import 'echarts/lib/component/tooltip'

safari非首屏echarts圖表不渲染

在iphone手機端safari瀏覽器上,屏幕可見區域的echarts圖表正常渲染,滾動區域下面的DOM元素是正常渲染,echarts圖表不渲染,可是當手動點擊圖表區域後,echarts又正常渲染出來。以下圖所示:

圖片描述

解決方法:在echarts圖表的父元素及祖先元素上,加上transform: translateZ(0)的樣式。具體緣由還清楚。

echarts不能正常初始化

問題場景:當echarts圖表所在組件正常移除後,從新建立組件並初始化echarts,發現echarts容器DOM並無插入canvas標籤,echarts圖表沒有初始化出來。

console.log(this.echarts)   // echarts容器存在,且具備寬高
const echartsObj = echarts.init(this.echarts)
console.log(echartsObj)    // echarts初始化對象也存在

解放方法:在組件將要移除時,手動刪除echarts初始化的DOM容器。

componentWillUnmount(){
    this.echarts.parentNode.removeChild(this.echarts)
}

其緣由是因爲react組件在移除後,react組件裏面的DOM節點被緩存了下來,當react組件從新建立時,echarts的初始化函數init檢測到容器DOM相同,echarts不能在單個容器上初始化多個 ECharts 實例,因此其容器DOM裏就不會再插入canvas標籤。

相關文章
相關標籤/搜索