相比於PC項目只需要關注功能實現,H5項目兼容性似乎是前端開發和測試童鞋需要重點關注的問題。我做H5項目也有一段時間了,下面從自己項目中遇到的問題稍稍做一下覆盤,回顧一下踩坑和出坑的過程。
1 iphoneX系列手機適配問題
表現
頭部劉海兩側區域或者底部區域,出現劉海遮擋文字遮擋、點擊區域,或者呈現黑底或白底空白區域。
產生原因
iPhoneX及以上版本手機都採用了狀態欄、圓弧展示角、傳感器槽、主屏幕指示器和屏幕邊緣手勢(具體名詞註釋看下圖)。頭部底部側邊欄都需要做特殊處理,使得content儘可能的處於安全區域內,適配iPhoneX系列手機的特殊性。
解決方案
設置安全區域,填充危險區域,危險區域不做操作和內容展示。何爲安全區域(safe Area),顧名思義,安全區域即爲正常顯示內容的區域,但該區域不受狀態欄和其它內容影響。當界面顯示在屏幕上時,安全區域即爲導航欄、選項卡欄、工具欄和其他父視圖不覆蓋的屏幕視圖的一部分。
具體操作
Step1:viewport-fit
viewport-fit meta 標籤設置爲 cover,獲取所有區域填充。判斷設備是否屬於iPhone X,給頭部底部增加適配層 。viewport-fit 有 3 個值,分別爲:
- auto:此值不影響初始佈局視圖端口,並且整個web頁面都是可查看的。
- contain:視圖端口按比例縮放,以適合顯示內嵌的最大矩形。
- cover:視圖端口被縮放以填充設備顯示。強烈建議使用safe area inset變量,以確保重要內容不會出現在顯示之外。
viewport-fit meta標籤設置(cover時)
><meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
Step2:增加適配層
WebKit包含了新的CSS函數constant()和env(),以及一組四個預定義的常量:safe-area-inset-left, safe-area-inset-right, safe-area-inset-top和 safe-area-inset-bottom。當合並一起使用時,允許樣式引用每個方面的安全區域的大小。
當我們設置viewport-fit:contain,也就是默認的時候時;設置safe-area-inset-left, safe-area-inset-right, safe-area-inset-top和 safe-area-inset-bottom等參數時不起作用的。只有設爲cover纔可以用contant()和env()方法。
當我們設置viewport-fit:cover時,爲了達到向前兼容ios11.2以前的版本向後兼容ios11.2以後版本的瀏覽器,需要同時用contant()和env()。設置如下:
body {
padding-top: constant(safe-area-inset-top);
padding-top: env(safe-area-inset-top);//爲導航欄+狀態欄的高度 88px
padding-left: constant(safe-area-inset-left);
padding-left: env(safe-area-inset-left); //如果未豎屏時爲0
padding-right: constant(safe-area-inset-right);//如果未豎屏時爲0
padding-right: env(safe-area-inset-right);
padding-bottom: constant(safe-area-inset-bottom));//距離底部圓弧的距離34px
padding-bottom: env(safe-area-inset-bottom));
}
通過上述設置,可以開闢出適配iPhoneX系列手機的安全區域。
在實際應用中,爲了解決底部出現文字遮擋、fixed按鈕不可點擊,或者呈現黑底或白底空白區域的問題,同時適配不同的寬高比。結合媒體查詢分別適配X,XS MAX ,XR,給底部fixed的元素加一個適配底部小黑條和圓角的底部高度,如下面fixed-footer,會出現底部body超出底部fixed部分的問題,可以給body加一句<div class=“footer”></div>
,使得每個X的屏幕都有一個div塊,把內容頂上去,防止出現底部透傳現象。
//iphone X
@media only screen and (device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) {
.fixed-footer{
bottom: constant(safe-area-inset-bottom) ;
bottom: env(safe-area-inset-bottom);
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
height: constant(safe-area-inset-bottom)
height: env(safe-area-inset-bottom)
background-color: #fff;
}
}
//iphone Xs Max
@media only screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio:3) {
.fixed-footer{
bottom: constant(safe-area-inset-bottom)
bottom: env(safe-area-inset-bottom)
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
height: constant(safe-area-inset-bottom)
height: env(safe-area-inset-bottom)
background-color: #fff;
}
}
//iphone XR
@media only screen and (device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio:2) {
.fixed-footer{
bottom: constant(safe-area-inset-bottom)
bottom: env(safe-area-inset-bottom)
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
height: constant(safe-area-inset-bottom)
height: env(safe-area-inset-bottom)
background-color: #fff;
}
}
2 click點擊延遲與穿透問題
表現
延時:點擊某個滾動的動畫(如圖所示),交互中動畫會停止,出現下一步操作。但是在IOS系統中,點擊沒有反應,與Android效果差別很大。
穿透:點擊蒙層,蒙層消失後發現觸發了蒙層下層元素點擊事件。或者點擊頁內按鈕跳轉至新頁,發現新頁的對應位置的click事件被觸發了。
產生原因
爲什麼會出現click延時?
iOS 中的 safari,爲了實現雙擊縮放操作,在單擊300ms之後,如果未進行第二次點擊,則執行click單擊操作。也就是說來判斷用戶行爲是否爲雙擊縮放產生的。後來其他的瀏覽器都效仿safari,實現了雙擊縮放功能,導致在大部分app中無論是否需要雙擊縮放這種行爲,click單擊都會產生300ms延遲。
爲什麼會出現點擊透傳?
當點擊移動設備的屏幕時, 可以分解成多個事件,順序依次爲:touchstart — touchmove — touchend — click, 這些事件是按順序依次觸發的。雙層元素疊加時,在上層元素上綁定 touch 事件,下層元素綁定 click 事件。由於 click 發生在touch之後,點擊上層元素,元素消失,此時事件只進行到touchend,300ms後下層元素會觸發 click事件,由此產生了點擊穿透的效果。當然對於跨頁面點擊穿透問題,和上述原理差不多,同時滿足了touch,跳轉新頁面,click事件,三者缺一不可。
解決方案
解決click延時:
a. 禁止縮放
<meta name="viewport" content="width=device-width, user-scalable=no">// 關鍵是user-scalable=no
但是在iOS10下面及部分UC瀏覽器中爲了提高網站的輔助功能那個,屏蔽了Meta下的user-scalable=no功能。就算加上user-scalable=no,瀏覽器也能支持手動縮放。可以用js加監聽事件來阻止手動縮放。代碼如下:
window.onload=function () {
document.addEventListener('touchstart',function (event) {
if(event.touches.length>1){
event.preventDefault();
}
})
var lastTouchEnd=0;
document.addEventListener('touchend',function (event) {
var now=(new Date()).getTime();
if(now-lastTouchEnd<=300){
event.preventDefault();
}
lastTouchEnd=now;
},false)
}
b. 用touch事件替代click事件
原理就是:
- 當我們手指觸摸屏幕,記錄當前觸摸時間
- 當我們手指離開屏幕,用離開的時間減去觸摸的時間
- 如果時間小於150ms,並且沒有滑動過屏幕,那麼我們就定義爲點擊
//封裝tap,解決click 300ms延時
function tap(obj,callback){
var flag = false;
var startTime = 0; //記錄觸摸時候的時間變量
obj.addEventListener('touchstart',function(e){
startTime = Date.now()
});
obj.addEventListener('touchmove',function(e){
flag = true; //看看是否有滑動,有滑動算拖拽,不算點擊
});
obj.addEventListener('touchend',function(e){
if(!flag && (Date.now() - startTime) < 150){ //如果手指觸摸和離開時間小於150ms算點擊
callback && callback() //執行回調函數
}
flag = false; //取反,重置
stratTime = 0;
});
}
c. 引用faskclick插件庫
使用faskclick庫以後,click延時和穿透問題都可以解決了。至於faskclick插件庫的實現原理,以後再做總結。
d. vue項目可以安裝vue-tap插件
使用方法類vue的指令,在本次問題中用的就是這種方案解決的。
解決點擊穿透:
a. 經常用的就是不要混用touch和click,把所有的click事件替換成click事件,但是需要特別注意a標籤,a標籤的href也是click,需要去掉換成js控制的跳轉,或者直接改成span+tap控制跳轉。如果要求不高,不在乎滑走或者滑進來觸發事件的話,span+touchend就可以了,畢竟tap需要引入第三方庫。當然對交互要求不高的情況下可以全部用click事件,但是要想好這300ms的後果。
b. 應用pointer-events。常言道能用css解決的問題就不要用js。pointer-events是CSS3的一個屬性,支持的值非常多,其中大部分都是和SVG有關。對於點擊穿透了解一個none就可以了。
pointer-events: none;//讓鼠標點擊事件失效。
蒙層隱藏後,給按鈕下面元素添上pointer-events: none;樣式,讓click穿過去,300ms後去掉這個樣式,恢復響應即可。但是要注意蒙層消失後的的300ms內,用戶可以看到按鈕下面的元素點擊沒反應,如果用戶手速很快的話一定會發現,不推薦使用。
c. 比較推薦的方式如果不介意多加載幾KB的話,可以嘗試上述解決點擊延時的fastclick庫。這裏不多加解釋說明,具體可以去看fastclick庫源碼。
3 1px 問題
表現
在做H5頁面時,有時候UI稿會出現邊框寬度爲1px,如果簡單粗暴的寫border:1px solid #eee,UI在審查的時候也常常會覺得分割線或者邊框線太粗了,要更細一點,但是看代碼發現也寫了1px。這個時候想改成0.5px,會發現在很多IOS7及以下以及一些Android機型上不支持0.5px。爲了解決1px變粗問題,我們就要找到一種實現0.5px的方案。
產生原因
要知道問題的原因首先要了解一下幾個概念:
(1) 物理像素(physical pixel)
一個物理像素是顯示器(手機屏幕)上最小的物理顯示單元(像素顆粒),在操作系統的調度下,每一個設備像素都有自己的顏色值和亮度值。如:iPhone6上就有750*1334個物理像素顆粒。
(2) 設備獨立像素(density-independent pixel)
設備獨立像素,也叫密度無關像素,可以認爲是計算機座標系統中得一個點,這個點代表一個可以由程序使用的虛擬像素(比如:css像素),有時我們也說成是邏輯像素。然後由相關係統轉換爲物理像素。所以說,物理像素和設備獨立像素之間存在着一定的對應關係,這就是接下來要說的設備像素比。
(3) 設備像素比(device pixel ratio )
簡稱dpr設備像素比(簡稱dpr)定義了物理像素和設備獨立像素的對應關係。它的值可以按如下的公式的得到:
設備像素比(dpr)=物理像素/邏輯像素(px) // 在某一方向上,x方向或者y方向,下圖dpr=2
知道了設備像素比,我們就大概知道了1px線變粗的原因。簡單來說就是手機屏幕分辨率越來越高了,同樣大小的一個手機,它的實際物理像素數更多了。因爲不同的移動設備有不同的像素密度,所以我們所寫的1px在不同的移動設備上等於這個移動設備的1px。現在做移動端開發時一般都要加上一句話:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
這句話定義了本頁面的viewport的寬度爲設備寬度,初始縮放值和最大縮放值都爲1,並禁止了用戶縮放。
viewport的設置和屏幕物理分辨率是按比例而不是相同的,移動端window對象有個devicePixelRatio屬性,它表示設備物理像素和css像素的比例,在retina屏的iphone手機上,這個值爲2或3, css裏寫的1px長度映射到物理像素上就有2px或3px。通過設置viewport,可以改變css中的1px用多少物理像素來渲染,設置了不同的viewport,當然1px的線條看起來粗細不一致。
解決方案
a. 在公共樣式裏面定義一個類,使用僞元素+絕對定位+scale,優點:兼容性較好,缺點:input元素不支持僞元素
<div class="wrap">
內容區域
</div>
設置四周的邊框:
.wrap
height: 40px;
position: relative;
&::after
content: "";
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 200%;
transform-origin: 0 0; -webkit-transform-origin: 0 0; -moz-transform-origin: 0 0; -o-transform-origin: 0 0;
transform: scale(.5); -webkit-transform: scale(.5); -moz-transform: scale(.5); -o-transform: scale(.5);
border: 1px solid #ebebf0;
b.使用 rem 改進
使用rem作爲單位,這樣可以更好地去實現移動端的響應式像素以及Retina屏幕上的表現。優點是實現簡單,缺點是部分機型還是不兼容。
c. css中引入 svg 改進
具體思路是爲元素加上 background-image,然後把svg置爲圖片類型,因爲svg上的 1px 就是實實在在的只佔1個物理像素。實現很簡單,代碼如下:
input
{
background-image:url(
"data:image/svg+xml;base64,<svg xmlns='http://www.w3.org/2000/svg' width='100%' height='100%'><line x1='0' y1='100%' x2='100%' y2='100%' stroke='#dcdcdc' stroke-width='1'/></svg>");
}
4 position fixed和sticky兼容性
表現
在如下圖所示的圖中,當頁面滑動到搜索框下面,二手房tab會自動吸頂,但是在某些安卓機的原生瀏覽器中沒有吸頂這個動作。
產生原因
吸頂的動作是用position:sticky完成的,但是Caniuse上顯示sticky的兼容性如下:
Sticky的作用相當於relative和fixed的結合體,當修飾的目標節點再屏幕中時表現爲relative,當要超出的時候是fixed的形式展現。但是由於兼容性問題,在安卓端沒有很好地兼容。且它的活動範圍只能在父元素內,滾動超過父元素的話,它一樣不能吸頂。
解決方案
react解決方案:使用react-sticky,通過計算 <Sticky> 組件相對於<StickyContai ner>組件的位置進行工作,如果他出現在視口的外面,將其附加到屏幕的頂部所需要的樣式作爲參數傳遞給render callback,作爲child傳遞的函數。
JS解決方案:通過cssSupport判斷瀏覽器的支持情況,如果瀏覽器支持sticky,則不做處理,否則通過自定義滾動事件的監聽,根據top的改變來實現tab層fixed和absolute的轉換。
vue解決方案:可以直接使用vue-sticky組件,vue-sticky實現原理大致與JS解決方案差不多。
5 軟鍵盤將頁面頂起來、收起未回落問題
表現
在Android手機中,點擊input框時,鍵盤彈出,將頁面頂起來,導致頁面樣式錯亂。失去焦點時,鍵盤收起,鍵盤區域空白,未回落。
產生原因
我們在app佈局中會有個固定的底部。在Android一些版本中,輸入鍵盤彈出來,會將解壓absolute和fixed定位的元素。導致可視區域變小,佈局錯亂。
解決方案
軟鍵盤將頁面頂起來的解決方案,主要是通過監聽頁面高度變化,強制恢復成彈出前的高度。
// 記錄原有的視口高度
const originalHeight = document.body.clientHeight || document.documentElement.clientHeight;
window.onresize = function(){
var resizeHeight = document.documentElement.clientHeight || document.body.clientHeight;
if(resizeHeight < originalHeight ){
// 恢復內容區域高度
// const container = document.getElementById("container")
// 例如 container.style.height = originalHeight;
}
}
鍵盤不能回落問題出現在iOS12+和wechat6.7.4+中,而在微信H5開發中是比較常見的 Bug。兼容原理:1.判斷版本類型 2.更改滾動的可視區域。
const isWechat = window.navigator.userAgent.match(/MicroMessenger\/([\d\.]+)/i);
if (!isWechat) return;
const wechatVersion = wechatInfo[1];
const version = (navigator.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
// 如果設備類型爲iOS12+和wechat6.7.4+,恢復成原來的視口
if (+wechatVersion.replace(/\./g, '') >= 674 && +version[1] >= 12) {
window.scrollTo(0, Math.max(document.body.clientHeight, document.documentElement.clientHeight));
}
window.scrollTo(x-coord, y-coord),其中window.scrollTo(0, clientHeight)恢復成原來的視口
6 總結
H5項目有的坑遠不止這些,出坑解決方案更是個人有個人的偏好。後續會持續輸出相關踩坑出坑方案。生命不息,踩坑不止…
7 推薦文章
-
喫透移動端 H5 與 Hybrid|實踐踩坑12種問題彙總
-
基於Vue的移動端h5項目總結
-
App適配IPhoneX----Safe Area (安全區域)
-
移動端常見的問題–click點擊延時解決方案
https://blog.csdn.net/weixin_46113485/article/details/104567528
-
移動端click事件延遲300ms解決方案
-
解決iOS10的Safari下Meta設置user-scalable=no無效的方法
-
聊聊移動端的適配問題
本文轉載自公衆號(ID:)。
原文鏈接: