移動端 Web 開發踩坑之旅

發表於2017-04-24

前言

最近在一個移動端的 Web 專案中踩了很多的坑,感覺有必要把它們記錄下來,分享給即將踏入移動端 Web 開發大門的新人們。

一、從佈局說起

移動端的整體佈局一般來說可以分為上中下三個部分,分別為 header、main、footer,其中header、footer 是固定高度,分別固定在頁面頂部和頁面底部,而 main 是佔據頁面其餘位置,並且可以滾動。
移動端 Web 開發踩坑之旅

(上圖是使用純 CSS 實現,然後截圖,上傳到專欄有點失真,看官老爺們將就著看吧。)

頁面佈局如下:

根據頁面滾動的位置分為兩種佈局,一種是滾動 body,另一種是固定 body 的高度為100%,在 main 中滾動。

第一種佈局有個優點,就是頁面的位址列會隨著 body 的滾動隱藏起來,並且 Android 裝置中,滾動 body 會更加的流暢,如果專案中有類似需求可以考慮。

實現佈局的方式如下:

第一種情況比較適合長列表頁面,整個頁面除了 header 和 footer 之外都需要滾動,但很多時候,我們只希望頁面的某個元素滾動,這個時候,就採取第二種佈局方式。

這種頁面佈局有三種相對簡單的實現方式:

  1. fixed 定位
  2. absolute 定位
  3. flex 定位

最容易想到的實現方式是 fixed 定位,實現方式如下:

fixed 定位實現起來簡單,在大多數瀏覽器中也能正常顯示,但是 fixed 定位在移動端會有相容性問題,後面會提到,所以不建議這種實現方式。

absolute 定位和 fixed 定位類似,只要把 header 的 footer 的 position 改為 absolute 就可以了。

細心的小夥伴可能發現了,這裡的 main 沒有設定 overflow ,因為這裡有一個坑,不管是absolute 定位還是 fixed 定位都一樣,為了方便描述,以下只說 fixed 定位(在 absolute 定位也一樣成立)。在PC端沒有問題,但是在移動端,如果 main 設定了 overflow 為 true,header 會被 main 遮住,對,沒有錯,雖然是 fixed 定位,但是在移動端,如果 fixed 定位節點後面緊接跟著的兄弟節點是可滾動的(也就是設定了 overflow 為 true ),那麼 fixed 節點會被其後的兄弟節點遮住。

這個問題解決方式有很多,既然是 fixed 定位後面緊接著可滾動的兄弟節點才會有這個坑,只要讓他的條件有一個不成立就好了,有以下解決方案:

  1. 讓 fixed 定位節點後面不緊接著可滾動的節點
  2. 不讓 scroll 節點遮住 fixed 節點

第一種方方案有以下可選方法:

1. 把所有 fixed 節點放在 scroll 元素後面,即把 header 節點放在 main 節點後面

但這樣顯然不太符合一般人的思維習慣,程式碼可讀性降低。

2. 使 main 不可滾動,給 main 巢狀一層可滾動的子節點

第二種方案有以下可選方法:

1. 讓 scroll 節點不與 fixed 節點有重合

2. 給 fixed 節點設定 z-index

看到這裡可能會有小夥伴覺得,一個簡單的佈局,還要繞過這麼多坑,難道沒有簡單的方式嗎,答案當然是肯定的,那就是第三種實現方式,flex 佈局。flex 定位在移動端相容到了 iOS 7.1+,Android 4.4+,如果使用 autoprefixer 等工具還可以降級為舊版本的 flexbox ,可以相容到 iOS 3.2 和 Android 2.1。而且用 flex 實現起來相對簡單,在各個瀏覽器裡表現也相對一致。實現如下:

二、fixed 與 input

剛接觸移動端 Web 開發的小夥伴應該都會聽前輩們說過,不要在有 input 標籤的頁面使用 fixed 定位,因為這兩者在一起的時候,總是會有奇奇怪怪的問題。

在 iOS 上,當點選 input 標籤獲取焦點喚起軟鍵盤的時候,fixed 定位會暫時失效,或者可以理解為變成了 absolute 定位,在含有滾動的頁面,fixed 定位的節點和其他節點一起滾動。

其實這個問題也很好解決,只要保證 fixed 定位的節點的父節點不可滾動,那麼即使 fixed 定位失效,也不會和其他滾動節點一起滾動,影響介面。

但是除此之外,還有很多坑比較難以解決,例如 Android 軟鍵盤喚起後遮擋住 input 標籤,使用者沒法看到自己輸入的字串,iOS 則需要在輸入至少一個字元之後,才能將對應的 input 標籤滾動到合適的位置,所以為了避開這些難以解決的坑,在有表單輸入的頁面,儘量用absolute 或者 flex 替換 fixed。

三、input 的 compositionstart 和 compositionend 事件

在 Web 開發中,經常要對錶單元素的輸入進行限制,比如說不允許輸入特殊字元,標點。通常我們會監聽 input 事件:

這段程式碼在 Android 上是沒有問題的,但是在 iOS 中,input 事件會截斷非直接輸入,什麼是非直接輸入呢,在我們輸入漢字的時候,比如說「喜茶」,中間過程中會輸入拼音,每次輸入一個字母都會觸發 input 事件,然而在沒有點選候選字或者點選「選定」按鈕前,都屬於非直接輸入。

移動端 Web 開發踩坑之旅

所以輸入「喜茶」兩個字,會觸發6次 input 事件,如果把每次 input 的 value 列印出來,結果如下:

移動端 Web 開發踩坑之旅

這顯然不是我們想要的結果,我們希望在直接輸入之後才觸發 input 事件,這就需要引出我要說的兩個事件—— compositionstart 和 compositionend。

compositionstart 事件在使用者開始進行非直接輸入的時候觸發,而在非直接輸入結束,也即使用者點選候選詞或者點選「選定」按鈕之後,會觸發 compositionend 事件。

新增一個 inputLock 變數,當使用者未完成直接輸入前,inputLock 為 true,不觸發 input 事件中的邏輯,當使用者完成有效輸入之後,inputLock 設定為 false,觸發 input 事件的邏輯。這裡需要注意的一點是,compositionend 事件是在 input 事件後觸發的,所以在 compositionend事件觸發時,也要呼叫 input 事件處理邏輯。

四、iOS 1px border 實現

iOS裝置上,由於retina屏的原因,1px 的 border 會顯示成兩個物理畫素,所以看起來會感覺很粗,這是一個移動端開發常見的問題。解決方案有很多,但都有自己的優缺點。

0.5px border

從iOS 8開始,iOS 瀏覽器支援 0.5px 的 border,但是在 Android 上是不支援的,0.5px 會被認為是 0px,所以這種方法,相容性是很差的。

背景漸變

CSS3 有了漸變背景,可以通過漸變背景實現 1px 的 border,實現原理是設定 1px 的漸變背景,50% 有顏色,50% 是透明的。

這種方法雖然可行,但是沒有辦法實現圓角。

偽類 + transform

這類方法的實現原理是用偽元素的 box-shadow 或 border 實現 border,然後用 transform縮小到原來的一半。即使有圓角的需求也能很好的實現。

總結

以上的坑都是在專案裡頻繁遇到的,每一個都給出了對應的解決方式,但由於筆者也是初入坑移動 Web 開發的新人一枚,所以給出的方案未必是最合適的,做了點微小的工作,希望能為大家提供一點幫助,不足的地方也請大家多多指正。

相關文章