Web移動端Fixed固定布局bug:移動端使用固定定位會出現(xiàn)很多莫名其妙的問題,特別是在ios上,當(dāng)軟鍵盤喚起時,fixed元素被擠到屏幕中央,導(dǎo)致布局錯亂的想象。
特別是最新的ios11的系統(tǒng),會出現(xiàn)fixed兼容的問題。
1、fixed在某些情況下可能導(dǎo)致容器內(nèi)的子元素的1px邊框線消失,即使使用z-index也無法解決。
解決方法:可以使用translateZ屬性來解決
2、fixed定位的容器內(nèi)不能帶有input,這是常見的bug。
解決方法: 在input聚焦的時候去掉fixed定位狀態(tài),改為absolute,絕對定位 然后通過JS 跟著屏幕滾動而滾動 達到固定定位的效果。
3、fixed+可滾動的容器內(nèi)會導(dǎo)致fixed定位的子元素在滾動時定位失效,滾動完成后才正?;氐絝ixed的位置。
解決方法:盡量不要在可滾動的容器內(nèi)包含fixed定位的子元素。
4、ios不支持onresize事件
PC端項目:
父級設(shè)置了transform: translate(0, 0);后,子級的固定定位會失效:
(解決辦法,去除父級的transform: translate(0, 0); 即可)
html:
<div class="guding">
<div class="fixed-box"></div>
</div>
.guding{
// transform: translate(0, 0);//這行代碼一旦打開,.fixed-box中設(shè)置的固定定位將失效。
}
//固定定位
.fixed-box{
position: fixed;//固定定位,常用作浮動的導(dǎo)航
width: 100px;
height: 100px;
background-color: lightgreen;
z-index: 1;//可以結(jié)合z-index提升層級
}
手機端項目:
只要父元素的 -webkit-overflow-scrolling 屬性值為 touch,子元素的 position: fixed 屬性就無效。:
css:
-webkit-overflow-scrolling: touch; /* 當(dāng)手指從觸摸屏上移開,會保持一段時間的滾動 */
-webkit-overflow-scrolling: auto; /* 當(dāng)手指從觸摸屏上移開,滾動會立即停止 */
解決方案
解決方案不是很優(yōu)雅,因為項目本身已經(jīng)比較穩(wěn)定,影響頁面眾多,只能暫時針對 iPhone X 進行修復(fù)。
當(dāng)動態(tài)向頁面增加 fixed 蒙層時,將主要內(nèi)容節(jié)點(一個很長的容器)改成position: absolute,計算內(nèi)容的scrollTop,作為marginTop賦值給它,以此實現(xiàn)用戶基本無感知后面的內(nèi)容頁面被改動了(實際上在不同 webview 內(nèi)會有不同程度的一個閃爍)。
更好的解決方案
更好的解決方案,即是如標(biāo)題所述,盡量避免在移動端開發(fā)中使用position: fixed,而是更多使用更現(xiàn)代的布局方式和position: absolute來實現(xiàn)類似需求。
讓我們先舉個栗子,最直觀的說明一下這個 BUG 的現(xiàn)象。 常規(guī)的 fixed 布局,可能使用如下布局(以下僅示意代碼):
<body class="layout-fixed"> <header> </header> <main> </main> <footer> <input type="text" placeholder="Footer..."/> <button class="submit">提交</button> </footer> </body>
|
對應(yīng)的樣式如下:
header, footer, main { display: block; }
header { position: fixed; height: 50px; left: 0; right: 0; top: 0; }
footer { position: fixed; height: 34px; left: 0; right: 0; bottom: 0; }
main { margin-top: 50px; margin-bottom: 34px; height: 2000px }
|
然后看起來就是下面這個樣子。拖動頁面時 header 和 footer 已經(jīng)定位在了對應(yīng)的位置,目測沒問題了。

fixed定位
但接下來問題就來了!如果底部輸入框軟鍵盤被喚起以后,再次滑動頁面,就會看到如下圖所示:

我們看到 fixed 定位好的元素跟隨頁面滾動了起來... fixed 屬性失效了!
這是為什么呢?簡單解釋下: > 軟鍵盤喚起后,頁面的 fixed 元素將失效(即無法浮動,也可以理解為變成了 absolute 定位),所以當(dāng)頁面超過一屏且滾動時,失效的 fixed 元素就會跟隨滾動了。
這便是 iOS 上 fixed 元素和輸入框的 bug 。其中不僅限于 type=text
的輸入框,凡是軟鍵盤(比如時間日期選擇、select 選擇等等)被喚起,都會遇到同樣地問題。
雖然 isScroll.js
可以很好的解決 fixed 定位滾動的問題,但是不在萬不得已的情況下,我們盡量嘗試一下不依賴第三方庫的布局方案,以簡化實現(xiàn)方式。這里拋磚引玉作為參考。
解決思路:
既然在 iOS 下由于軟鍵盤喚出后,頁面 fixed 元素會失效,導(dǎo)致跟隨頁面一起滾動,那么假如——頁面不會過長出現(xiàn)滾動,那么即便 fixed 元素失效,也無法跟隨頁面滾動,也就不會出現(xiàn)上面的問題了。
那么按照這個思路,如果使 fixed 元素的父級不出現(xiàn)滾動,而將原 body 滾動的區(qū)域域移到 main 內(nèi)部,而 header 和 footer 的樣式不變,代碼如下:
<body class="layout-scroll-fixed"> <header> </header> <main> <div class="content"> </div> </main> <footer> <input type="text" placeholder="Footer..."/> <button class="submit">提交</button> </footer> </body>
|
header, footer, main { display: block; }
header { position: fixed; height: 50px; left: 0; right: 0; top: 0; }
footer { position: fixed; height: 34px; left: 0; right: 0; bottom: 0; }
main { position: absolute; top: 50px; bottom: 34px; overflow-y: scroll; }
main .content { height: 2000px; }
|
這樣再來看一下:

fixed定位
在原始輸入法下, fixed 元素可以定位在頁面的正確位置。滾動頁面時,由于滾動的是 main 內(nèi)部的 div,因此 footer 沒有跟隨頁面滾動。
上面貌似解決了問題,但是如果在手機上實際測試一下,會發(fā)現(xiàn) main 元素內(nèi)的滾動非常不流暢,滑動的手指松開后,滾動立刻停止,失去了原本的流暢滾動特性。百度一下彈性滾動的問題,發(fā)現(xiàn)在 webkit
中,下面的屬性可以恢復(fù)彈性滾動。
-webkit-overflow-scrolling: touch;
在 main 元素上加上該屬性,嗯,絲般順滑的感覺又回來了!
main { position: absolute; top: 50px; bottom: 34px; overflow-y: scroll; -webkit-overflow-scrolling: touch; }
|
另外,這里的 header 和 footer 使用的是 fixed 定位,如果考慮到更老一些的 iOS 系統(tǒng)不支持 fixed 元素,完全可以把 fixed 替換成 absolute 。測試后效果是一樣的。
至此一個不依賴第三方庫的 fixed 布局就完成了。
Android 下布局
談到了 iOS ,也來簡單說一下 Android 下的布局吧。
在 Android2.3+ 中,因為不支持 overflow-scrolling ,因此部分瀏覽器內(nèi)滾動會有不流暢的卡頓。但是目前發(fā)現(xiàn)在 body 上的滾動還是很流暢的,因此使用第一種在 iOS 出現(xiàn)問題的 fixed 定位的布局就可以了。
如果需要考慮 Android2.3 以下系統(tǒng),因為不支持 fixed 元素,所以依然要需要考慮使用 isScroll.js
來實現(xiàn)內(nèi)部滾動。
其實在 fixed 和輸入框的問題上,基本思路就是: > 由于 fixed 在軟鍵盤喚起后會失效,導(dǎo)致在頁面可以滾動時,會跟隨頁面一起滾動。因此如果頁面無法滾動,那么 fixed 元素即使失效,也不會滾動,也就不會出現(xiàn) bug 了。
所以可以在這個方面去考慮解決問題。
其他的一些細節(jié)處理
在細節(jié)處理上,其實還有很多要注意的,挑幾個實際遇到比較大的問題來說一下:
有時候輸入框 focus 以后,會出現(xiàn)軟鍵盤遮擋輸入框的情況,這時候可以嘗試 input 元素的 scrollIntoView 進行修復(fù)。
在 iOS 下使用第三方輸入法時,輸入法在喚起經(jīng)常會蓋住輸入框,只有在輸入了一條文字后,輸入框才會浮出。目前也不知道有什么好的辦法能讓喚起輸入框時正確顯示。這暫時算是 iOS 下的一個坑吧。
有些第三方瀏覽器底部的工具欄是浮在頁面之上的,因此底部 fixed 定位會被工具欄遮擋。解決辦法也比較簡單粗暴——適配不同的瀏覽器,調(diào)整 fixed 元素距離底部的距離。
最好將 header 和 footer 元素的 touchmove 事件禁止,以防止?jié)L動在上面觸發(fā)了部分瀏覽器全屏模式切換,而導(dǎo)致頂部地址欄和底部工具欄遮擋住 header 和 footer 元素。
在頁面滾動到上下邊緣的時候,如果繼續(xù)拖拽會將整個 View 一起拖拽走,導(dǎo)致頁面的"露底"。

fixed定位
為了防止頁面露底,可以在頁面拖拽到邊緣的時候,通過判斷拖拽方向以及是否為邊緣來阻止 touchmove 事件,防止頁面繼續(xù)拖拽。
以上面內(nèi)滾動 layout-scroll-fixed
布局為例,給出一段代碼作為參考:
var content = document.querySelector('main'); var startY;
content.addEventListener('touchstart', function (e) { startY = e.touches[0].clientY; });
content.addEventListener('touchmove', function (e) { var status = '11'; var ele = this;
var currentY = e.touches[0].clientY;
if (ele.scrollTop === 0) { status = ele.offsetHeight >= ele.scrollHeight ? '00' : '01'; } else if (ele.scrollTop + ele.offsetHeight >= ele.scrollHeight) { status = '10'; }
if (status != '11') { var direction = currentY - startY > 0 ? '10' : '01'; if (!(parseInt(status, 2) & parseInt(direction, 2))) { stopEvent(e); } } });
|