討論背景
衆所周知,fixed元素在IOS下的表現是糟糕的,fixed元素在滾動頁面中使用會出現各類奇怪的問題,在微信瀏覽器中使用就更甚(如:頁面滾動,fixed元素與頁面相互分離;頁面滾動,fixed元素消失等)。這些表現過於離奇,顯得沒有邏輯,一時間很難找到對應的解決方案。
因此筆者決定從一個簡單列表頁出發,把遇到的各類奇怪問題都羅列出來,並探究其出現的緣由。以便在開發中規避這些問題。html
假定咱們的需求是作一個列表頁,列表頁的頂部放置一些「其餘」信息,底部放置一個「建立」按鈕,中間顯示「項目」列表內容。
設計稿大概是這樣。web
實現方案
根據需求,咱們分別製做了三種解決方案。分別是瀏覽器
- 利用fixed定位,將「按鈕」放在滾動區「項目列表」外面,解決方案示例1。
- 利用fixed定位,將「按鈕」放在滾動區「項目列表」裏面,解決方案示例2。
- 利用absolute定位,將「按鈕」放在滾動區「項目列表」裏面,並用「項目列表」去填充它所佔的內容,解決方案示例3。
分別在PC和IOS瀏覽器中運行這幾個demo,咱們發現,這些demo在PC中的表現都是符合設計需求的。但在IOS瀏覽器中運行,就會各類出現各類的問題,分別對應這幾個現象。微信
- 解決方案示例1:從「其餘」內容區域開始觸碰屏幕,進行頁面滾動,「按鈕」'脫離'頁面內容區域。
- 解決方案示例2:從「其餘」內容區域開始觸碰屏幕,進行頁面滾動,「按鈕」消失了。
要解釋這幾個現象,咱們須要從顏色填充提及。佈局
滾動填充的顏色
-
顏色填充示例1。動畫
- 重點代碼:在這個示例裏面,咱們不對「項目列表」的高度進行限制,直接讓內容在body中進行滾動。而後將body的背景顏色設置爲橘紅色。
- 操做:進入頁面後直接向上拉動頁面,拉動到不可拖動爲止。
- 現象:咱們發現「項目列表」的綠色區域下面,顯示了body的背景顏色橘紅色。
- 說明:填充的顏色是能夠定製的。
- 疑問:這個顏色填充的區域會不會是body的延伸呢?
-
顏色填充示例2。spa
- 重點代碼:去除了Body的背景顏色,改爲body的背景圖片並進行平鋪。
- 操做:同上一個示例。
- 現象:咱們發現「項目列表」的綠色區域下面,填充的依然body的背景顏色,而不是body的背景圖片。
- 說明:填充的部分並不屬於Body標籤自己。
- 疑問:那若是咱們將body的背景顏色去掉,而在html加上呢?
-
顏色填充示例3。設計
- 重點代碼:將body的背景顏色去掉。
- 操做:同上一個示例。
- 現象:此次填充的顏色是html的背景顏色。
- 說明:這再次論證了填充的部分並非固定某元素的內容,不是某個元素的延伸。並且說明系統找顏色是從滾動區域逐級往上找的,直到找到爲止。
- 疑問:若是body和html的背景顏色都去掉,又會顯示什麼顏色呢?
-
顏色填充示例4。3d
- 重點代碼:body和html的背景顏色去掉。
- 操做:同上一個示例。
- 現象:能夠看到填充的是白色。
- 說明:默認的填充顏色是白色。
-
咱們再回到顏色填充示例1。code
- 重點代碼:與示例1相同
- 操做:此次咱們在微信中打開,並改變操做方式,先上拉顯示橘紅色填充內容,再下拉顯示微信的黑邊(即顯示頂部"此頁面由XXX提供"文案)。再從新上拉,到不可拖動爲止。
- 現象:本來下拉填充的橘紅色變成了黑色。並且不管再怎麼操做,都不會再從新顯示回橘紅色。
- 說明:微信內置瀏覽器修改了默認的顏色填充。
小結
- 滾動填充的顏色是可定製的。
- 滾動填充的內容並非標籤的延伸,只會填充純顏色。
- 滾動填充的顏色是滾動區域逐級往上找background-color肯定的。
- 滾動填充的顏色默認值爲白色。
- 微信會修改滾動填充的顏色值。
IOS滾動回彈機制
咱們知道IOS是有滾動回彈機制的(即進行滾動時,滾動到最頂部或者最底部顯示的一個回彈動畫。咱們上面講的滾動顏色填充就是這個機制的具體實現)前面解決方案示例1中遇到的問題(「按鈕」'脫離'頁面內容區域),就是因爲這個機制引發的。
如今咱們先來探究一下,這個滾動回彈機制具體的運行過程是怎麼樣的。如下操做均在解決方案示例1下進行。
重點操做以下過程:
- 先將示例代碼在IOS內置瀏覽器safari中打開。
-
用雙指捏起整個頁面(即相似於圖片的縮小操做)。
- 現象:咱們發現,在頁面是能夠被縮小的。頁面外部部分是純顏色。
- 說明:有一個容器包裹着咱們的頁面。這個容器一般用於窗口縮放的時候,填充顏色。
-
雙指重複緩慢地捏起,放鬆整個頁面。觀察頁面變化。
- 現象:當剛開始縮小頁面時,外部容器的顏色與滾動填充索引到的顏色(粉紅色)相同。
- 現象:當逐漸縮小頁面時,外部容器的顏色將從索引到的顏色漸變到白色(這個顏色和咱們上面探討到的默認填充顏色相同)。
- 先將示例代碼在微信內置瀏覽器中打開,重複上面操做。
-
用雙指捏起整個頁面(即相似於圖片的縮小操做)。
- 現象:外部容器的顏色變成了黑色,並且容器頂部出現了「此頁面由 XXX 提供」文案。
- 說明:在微信下,爲了顯示「此頁面由 XXX 提供」的提示語,微信本身重寫了這個機制中的顏色,設置爲黑色。
-
雙指重複緩慢地捏起,放鬆整個頁面。觀察頁面變化。
- 現象:外部容器的顏色在黑色和粉紅色之間閃動。
- 現象:越是放鬆頁面,閃動越頻繁。
- 說明:微信重設顏色的機制和原生滾動回彈中縮小頁面漸變顏色的機制相沖突。
- 說明:不管是縮小頁面仍是恢復頁面大小,微信都嘗試將容器背景顏色設置爲黑色。
這一系列的操做解釋了這幾個問題:
- 爲何在微信中,先往上滾再往下滾動頁面,顏色的填充會變成了黑色,而不是body的背景色。由於微信對外部容器的背景色進行了重載。
- 爲何解決方案示例1中,「按鈕」看起來'脫離'了頁面。
由於微信對外部容器的背景色設置成了黑色,因此滾動到底部進行回彈的時候,頁面內容和按鈕之間的區域(即顏色填充區域)變成了黑色。而黑色給讓一種"空"的感受,因此感受到「按鈕」脫離的頁面內容的錯覺。
- 在safari中並不會改爲黑色,即填充的顏色和Body的背景顏色一致。因此不會有黑色,不會產生微信上。「按鈕」脫離的頁面內容的錯覺。
fixed定位基準值問題
在剛纔的示例操做中,不知道你們有沒有發現一個奇怪的問題。
在頁面縮放過程當中,fixed元素與其餘元素是在不一樣的顯示層進行渲染了?
從新執行前面的操做過程。咱們發現:
- fixed元素的定位並非基於手機屏幕,由於縮放的過程當中,「按鈕」隨着縮放進行了上移。
- fixed元素的定位也不是基於body元素的,由於從回彈機制來講,「按鈕」早已經脫離了body區域(紅色框標記的顏色深粉紅色塊就是body的背景色)。
fixed元素的基準值,實際上是介於兩者之間的一個顯示窗口(相似於viewPort)。
這個顯示窗口在不縮放的狀況下,等於瀏覽器的窗口大小。
在縮放的狀況下,顯示窗口大概是這樣子。
body內容超出了顯示窗口就造成了回彈部分。
因此其實若是咱們往頁面的左右部分滑動,也是有回彈效果存在的。只是這個手勢操做被IOS寫爲頁面「前進」,「後退」這兩個功能罷了。
若是咱們將html的width設置爲110%,當心滑動,就能重現左右的回彈效果。左右回彈示例
因此在頁面縮放過程當中,「fixed元素與其餘元素是在不一樣的顯示層進行渲染」的假像,
只是因爲body相對於顯示窗口同時在橫座標方向和縱座標方向發現了位移,
造成了左右兩部分的回彈顏色填充。
而fixed元素基於顯示窗口固定,沒有發生位移。而造成的分層的假想。
這解釋了爲何fixed元素爲何一直在底部,而不是隨着body在回彈機制下滾動。
IOS下position顯示深度失效
最後這個問題最簡單,也最離奇,在IOS中,除了設置z-index外,元素只根據元素在代碼中出現的順序決定其顯示的深度。佈局格式並不能改變元素的顯示深度。
分別在PC端和IOS端運行佈局示例。
在PC中,因爲「按鈕」fixed定位和「其餘」absolute定位脫離文檔流。因此其顯示層級比「項目列表」高,因此覆蓋在「項目列表」外面。
在IOS中,元素只根據元素在代碼中出現的順序決定其顯示的深度。即:佈局格式並不能改變元素的顯示深度。
因此「項目列表」覆蓋了「其餘」和「按鈕」。
這解釋了最後一個問題(「其餘」區域消失不見)。這是因爲「項目列表」的padding-top將「其餘」區域遮蓋住了。至於爲何要這麼設計,後面會講到。
也解釋了第二個問題(頁面滾動,「按鈕」消失了)。
- 第二個問題是因爲不改變顯示深度,因此「按鈕」仍處於「項目列表」容器內。
- 而「按鈕」的定位是根據顯示容器,而不是body,因此滾動過程當中不會跟着body移動,必定停留在底部。
- 在滾動時,「按鈕」超出了「項目列表」的顯示區域,「項目列表」設置了overflow,不在其顯示區域的都不會被顯示。因此滾動過程當中,「按鈕」逐漸消失。
- 而多出來的部分是回彈機制填充的內容。因此產生了顏色遮蓋了「按鈕」的錯覺。
常規解決方案
講了這麼多,那到底有什麼方法能夠規避上面的問題呢?
- 既然fixed佈局這麼多問題,咱們改用absolute佈局吧。咱們改爲absolute示例1。但屢次滾動後,咱們發現了另外一個問題,滾動時,「項目列表」的滾動會很容易與外部body下的滾動衝突。特別是當觸碰到非「項目列表」區域的其餘內容時(如「其餘」或「按鈕」),將觸發的是body的滾動,沒法滾動「項目列表」,違背操做意願。
- 爲了減小body滾動的觸發概率,能夠用「項目列表」的padding值對「其餘」和「按鈕」區域進行佔位。獲得absolute示例2。這時,即便從「其餘」區域或「按鈕」區域進行觸摸滾動,滾動的依然是「項目列表」中的內容。不會觸發body的滾動回彈。這便是一個好事,也是一個錯誤現象。由於在這個時候,移動「按鈕」和「其餘」區域。也會滾動「項目列表」。這和咱們的設計是不符的。
並且以上兩個解決方案都還有另外一個操做的問題,若是當「項目列表」滾動到最頂部,或者對底部。中止1秒左右(即等待滾動趨勢結束),
再往相同方向強制滾動(例如,往下滾到最低,靜置1s,再往下滾)。這時,滾動回彈就不會在「項目列表」中進行,而是被放到了body區域上進行。這時在body的滾動趨勢尚未結束前,不管怎麼進行滾動。都會在body中觸發。
與用戶期待的滾動不符。
最終解決方案
上面的一切都是因爲頁面最外層的滾動回彈引發的。有沒有方案禁止頁面最外層的滾動回彈呢?很抱歉,筆者沒有找到。
可是!筆者找到了不顯示滾動回彈顏色填充內容的方法。
- 那就是fixed大法。直接將整個body設置成position:fixed。這時整個body都基於顯示窗口定位。不會再顯示滾動回彈內容。fixed示例1。然而這僅僅是不顯示顏色填充的內容。滾動回彈其實仍是存在的,只是被body擋住了顯示不出來。頁面依然會存在上面的兩個問題。最外部的滾動回彈仍是會與「項目列表」區相沖突。
- 怎麼解決衝突?去掉「項目列表」的-webkit-overflow-scrolling: touch;樣式,運行代碼。fixed示例2。
- 衝突解決。不過滾動變卡頓了。怎麼辦?換個順滑滾動的實現方案唄,例如用IScroll。fixed示例3