移動端 H5 相關(guān)問題匯總:
- 1px 問題
- 響應(yīng)式布局
- iOS 滑動不流暢
- iOS 上拉邊界下拉出現(xiàn)白色空白
- 頁面件放大或縮小不確定性行為
- click 點擊穿透與延遲
- 軟鍵盤彈出將頁面頂起來、收起未回落問題
- iPhone X 底部欄適配問題
- 保存頁面為圖片和二維碼問題和解決方案
- 微信公眾號 H5 分享問題
- H5 調(diào)用 SDK 相關(guān)問題及解決方案
- H5 調(diào)試相關(guān)方案與策略
移動端 H5 相關(guān)基礎(chǔ)技術(shù)概覽
iOS 滑動不流暢
表現(xiàn)
上下滑動頁面會產(chǎn)生卡頓,手指離開頁面现横,頁面立即停止運動。整體表現(xiàn)就是滑動不流暢,沒有滑動慣性为障。
產(chǎn)生原因
為什么 iOS 的 webview 中 滑動不流暢齐莲,它是如何定義的?
最終我在 safari 文檔里面尋找到了答案(文檔鏈接在參考資料項)允蜈。
原來在 iOS 5.0 以及之后的版本冤吨,滑動有定義有兩個值 auto 和 touch,默認值為auto饶套。
-webkit-overflow-scrolling: touch; /* 當手指從觸摸屏上移開漩蟆,會保持一段時間的滾動 */
-webkit-overflow-scrolling: auto; /* 當手指從觸摸屏上移開,滾動會立即停止 */
解決方案
1.在滾動容器上增加滾動 touch 方法
將-webkit-overflow-scrolling 值設(shè)置為 touch
.wrapper {
-webkit-overflow-scrolling: touch;
}
設(shè)置滾動條隱藏: .container ::-webkit-scrollbar {display: none;}
可能會導(dǎo)致使用position:fixed; 固定定位的元素妓蛮,隨著頁面一起滾動
2.設(shè)置 overflow
設(shè)置外部 overflow 為 hidden,設(shè)置內(nèi)容元素 overflow 為 auto怠李。內(nèi)部元素超出 body 即產(chǎn)生滾動,超出的部分 body 隱藏。
body {
overflow-y: hidden;
}
.wrapper {
overflow-y: auto;
}
兩者結(jié)合使用更佳捺癞!
iOS 上拉邊界下拉出現(xiàn)白色空白
表現(xiàn)
手指按住屏幕下拉夷蚊,屏幕頂部會多出一塊白色區(qū)域。手指按住屏幕上拉翘簇,底部多出一塊白色區(qū)域撬码。
產(chǎn)生原因
在 iOS 中,手指按住屏幕上下拖動版保,會觸發(fā) touchmove 事件呜笑。這個事件觸發(fā)的對象是整個 webview 容器,容器自然會被拖動彻犁,剩下的部分會成空白叫胁。
解決方案
1. 監(jiān)聽事件禁止滑動
移動端觸摸事件有三個,分別定義為
1\. touchstart :手指放在一個DOM元素上汞幢。
2\. touchmove :手指拖曳一個DOM元素驼鹅。
3\. touchend :手指從一個DOM元素上移開。
顯然我們需要控制的是 touchmove 事件森篷,由此我在 W3C 文檔中找到了這樣一段話
Note that the rate at which the user agent sends touchmove events is implementation-defined, and may depend on hardware capabilities and other implementation details.
If the preventDefault method is called on the first touchmove event of an active touch point, it should prevent any default action caused by any touchmove event associated with the same active touch point, such as scrolling.
touchmove 事件的速度是可以實現(xiàn)定義的输钩,取決于硬件性能和其他實現(xiàn)細節(jié)
preventDefault 方法,阻止同一觸點上所有默認行為仲智,比如滾動买乃。
由此我們找到解決方案,通過監(jiān)聽 touchmove钓辆,讓需要滑動的地方滑動剪验,不需要滑動的地方禁止滑動。
值得注意的是我們要過濾掉具有滾動容器的元素前联。
實現(xiàn)如下:
document.body.addEventListener('touchmove', function(e) {
if(e._isScroller) return;
// 阻止默認事件
e.preventDefault();
}, {
passive: false
});
2. 滾動妥協(xié)填充空白功戚,裝飾成其他功能
在很多時候,我們可以不去解決這個問題似嗤,換一直思路啸臀。根據(jù)場景,我們可以將下拉作為一個功能性的操作烁落。
比如:下拉后刷新頁面
頁面放大或縮小不確定性行為
表現(xiàn)
雙擊或者雙指張開手指頁面元素壳咕,頁面會放大或縮小。
產(chǎn)生原因
HTML 本身會產(chǎn)生放大或縮小的行為顽馋,比如在 PC 瀏覽器上,可以自由控制頁面的放大縮小幌羞。但是在移動端寸谜,我們是不需要這個行為的。所以属桦,我們需要禁止該不確定性行為熊痴,來提升用戶體驗他爸。
原理與解決方案
HTML meta 元標簽標準中有個 中 viewport 屬性,用來控制頁面的縮放果善,一般用于移動端诊笤。如下圖 MDN 中介紹
移動端常規(guī)寫法
<meta name="viewport" content="width=device-width, initial-scale=1.0">
因此我們可以設(shè)置 maximum-scale、minimum-scale 與 user-scalable=no 用來避免這個問題
<meta name=viewport
content="width=device-width, initial-scale=1.0, minimum-scale=1.0 maximum-scale=1.0, user-scalable=no">
click 點擊事件延時與穿透
表現(xiàn)
監(jiān)聽元素 click 事件巾陕,點擊元素觸發(fā)時間延遲約 300ms讨跟。
點擊蒙層,蒙層消失后鄙煤,下層元素點擊觸發(fā)晾匠。
產(chǎn)生原因
為什么會產(chǎn)生 click 延時?
iOS 中的 safari梯刚,為了實現(xiàn)雙擊縮放操作凉馆,在單擊 300ms 之后,如果未進行第二次點擊亡资,則執(zhí)行 click 單擊操作澜共。也就是說來判斷用戶行為是否為雙擊產(chǎn)生的。但是锥腻,在 App 中嗦董,無論是否需要雙擊縮放這種行為,click 單擊都會產(chǎn)生 300ms 延遲旷太。
為什么會產(chǎn)生 click 點擊穿透展懈?
雙層元素疊加時,在上層元素上綁定 touch 事件供璧,下層元素綁定 click 事件存崖。由于click 發(fā)生在 touch 之后,點擊上層元素睡毒,元素消失来惧,下層元素會觸發(fā) click 事件,由此產(chǎn)生了點擊穿透的效果演顾。
原理與解決方案
解決方案一:使用 touchstart 替換 click
前面已經(jīng)介紹了供搀,移動設(shè)備不僅支持點擊,還支持幾個觸摸事件钠至。那么我們現(xiàn)在基本思路就是用 touch 事件代替click 事件葛虐。
將 click 替換成 touchstart 不僅解決了 click 事件都延時問題,還解決了穿透問題棉钧。因為穿透問題是在 touch 和 click 混用時產(chǎn)生屿脐。
在原生中使用
el.addEventListener("touchstart", () => { console.log("ok"); }, false);
在 vue 中使用
<button @touchstart="handleTouchstart()">點擊</button>
開源解決方案中,也是既提供了 click 事件,又提供了touchstart 事件的诵。如 vant 中的button 組件
[圖片上傳失敗...(image-6768b2-1616939475173)]
那么万栅,是否可以將 click 事件全部替換成 touchstart 呢?為什么開源框架還會給出 click 事件呢西疤?
我們想象一種情景烦粒,同時需要點擊和滑動的場景下。如果將 click 替換成 touchstart 會怎樣代赁?
事件觸發(fā)順序: touchstart, touchmove, touchend, click扰她。
很容易想象,在我需要touchmove滑動時候管跺,優(yōu)先觸發(fā)了touchstart的點擊事件义黎,是不是已經(jīng)產(chǎn)生了沖突呢?
所以呢豁跑,在具有滾動的情況下廉涕,還是建議使用 click 處理。
在接下來的fastclick開源庫中也做了如下處理艇拍。針對 touchstart 和 touchend狐蜕,截取了部分源碼。
// If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:
// 1) the user does a fling scroll on the scrollable layer
// 2) the user stops the fling scroll with another tap
// then the event.target of the last 'touchend' event will be the element that was under the user's finger
// when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check
// is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).
this.updateScrollParent(targetElement);
// Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled
// and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42).
scrollParent = targetElement.fastClickScrollParent;
if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
return true;
}
主要目的就是卸夕,在使用 touchstart 合成 click 事件時层释,保證其不在滾動的父元素之下。
解決方案二:使用 fastclick 庫
使用 npm/yarn 安裝后使用
import FastClick from 'fastclick';
FastClick.attach(document.body, options);
同樣快集,使用fastclick庫后贡羔,click 延時和穿透問題都沒了
按照我的慣例,只要涉及開源庫个初,那么我們一定要去了解它實現(xiàn)的原理乖寒。主要是將現(xiàn)有的原生事件集合封裝合成一個兼容性較強的事件集合。
fastclick源碼 核心代碼不長院溺, 1000 行不到楣嘁。有興趣可以了解一下!
軟鍵盤將頁面頂起來、收起未回落問題
表現(xiàn)
Android 手機中珍逸,點擊 input 框時逐虚,鍵盤彈出,將頁面頂起來谆膳,導(dǎo)致頁面樣式錯亂叭爱。
移開焦點時,鍵盤收起漱病,鍵盤區(qū)域空白涤伐,未回落馒胆。
產(chǎn)生原因
我們在app 布局中會有個固定的底部。安卓一些版本中凝果,輸入彈窗出來,會將解壓absolute 和 fixed 定位的元素睦尽。導(dǎo)致可視區(qū)域變小器净,布局錯亂。
原理與解決方案
軟鍵盤將頁面頂起來的解決方案当凡,主要是通過監(jiān)聽頁面高度變化山害,強制恢復(fù)成彈出前的高度。
// 記錄原有的視口高度
const originalHeight = document.body.clientHeight || document.documentElement.clientHeight;
window.onresize = function(){
var resizeHeight = document.documentElement.clientHeight || document.body.clientHeight;
if(resizeHeight < originalHeight ){
// 恢復(fù)內(nèi)容區(qū)域高度
// const container = document.getElementById("container")
// 例如 container.style.height = originalHeight;
}
}
鍵盤不能回落問題出現(xiàn)在 iOS 12+ 和 wechat 6.7.4+ 中沿量,而在微信 H5 開發(fā)中是比較常見的 Bug浪慌。
兼容原理,1.判斷版本類型 2.更改滾動的可視區(qū)域
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+)?/);
// 如果設(shè)備類型為iOS 12+ 和wechat 6.7.4+朴则,恢復(fù)成原來的視口
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)恢復(fù)成原來的視口
iPhone X系列安全區(qū)域適配問題
表現(xiàn)
頭部劉海兩側(cè)區(qū)域或者底部區(qū)域,出現(xiàn)劉海遮擋文字乌妒,或者呈現(xiàn)黑底或白底空白區(qū)域汹想。
產(chǎn)生原因
iPhone X 以及它以上的系列,都采用劉海屏設(shè)計和全面屏手勢撤蚊。頭部古掏、底部、側(cè)邊都需要做特殊處理侦啸。才能適配 iPhone X 的特殊情況槽唾。
解決方案
設(shè)置安全區(qū)域,填充危險區(qū)域光涂,危險區(qū)域不做操作和內(nèi)容展示庞萍。
危險區(qū)域指頭部不規(guī)則區(qū)域,底部橫條區(qū)域顶捷,左右觸發(fā)區(qū)域挂绰。
具體操作為:viewport-fit meta 標簽設(shè)置為 cover,獲取所有區(qū)域填充服赎。判斷設(shè)備是否屬于 iPhone X葵蒂,給頭部底部增加適配層
viewport-fit 有 3 個值分別為:
auto:此值不影響初始布局視圖端口,并且整個web頁面都是可查看的重虑。
contain:視圖端口按比例縮放践付,以適合顯示內(nèi)嵌的最大矩形。
cover:視圖端口被縮放以填充設(shè)備顯示缺厉。強烈建議使用 safe area inset 變量永高,以確保重要內(nèi)容不會出現(xiàn)在顯示之外隧土。
設(shè)置 viewport-fit 為 cover
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes, viewport-fit=cover">
增加適配層
使用 safe area inset 變量
/* 適配 iPhone X 頂部填充*/
@supports (top: env(safe-area-inset-top)){
body,
.header{
padding-top: constant(safe-area-inset-top, 40px);
padding-top: env(safe-area-inset-top, 40px);
padding-top: var(safe-area-inset-top, 40px);
}
}
/* 判斷iPhoneX 將 footer 的 padding-bottom 填充到最底部 */
@supports (bottom: env(safe-area-inset-bottom)){
body,
.footer{
padding-bottom: constant(safe-area-inset-bottom, 20px);
padding-bottom: env(safe-area-inset-bottom, 20px);
padding-top: var(safe-area-inset-bottom, 20px);
}
}
safe-area-inset-top, safe-area-inset-right, safe-area-inset-bottom, safe-area-inset-left safe-area-inset-*由四個定義了視口邊緣內(nèi)矩形的 top, right,bottom 和 left 的環(huán)境變量組成,這樣可以安全地放入內(nèi)容命爬,而不會有被非矩形的顯示切斷的風(fēng)險曹傀。對于矩形視口,例如普通的筆記本電腦顯示器饲宛,其值等于零皆愉。對于非矩形顯示器(如圓形表盤,iPhoneX 屏幕)艇抠,在用戶代理設(shè)置的四個值形成的矩形內(nèi)幕庐,所有內(nèi)容均可見。
其中 env() 用法為 env( <custom-ident> , <declaration-value>? )家淤,第一個參數(shù)為自定義的區(qū)域异剥,第二個為備用值。
其中 var() 用法為 var( <custom-property-name> , <declaration-value>? )絮重,作用是在 env() 不生效的情況下冤寿,給出一個備用值。
constant() 被 css 2017-2018 年為草稿階段绿鸣,是否已被標準化未知疚沐。而其他iOS 瀏覽器版本中是否有此函數(shù)未知,作為兼容處理而添加進去潮模。
詳情請查看文章末尾的參考資料亮蛔。
兼容性
頁面生成為圖片和二維碼問題
表現(xiàn)
在工作中有需要將頁面生成圖片或者二維碼的需求∏嫦幔可能我們第一想到的究流,交給后端來生成更簡單。但是這樣我們需要把頁面代碼全部傳給后端动遭,網(wǎng)絡(luò)性能消耗太大芬探。
解決方案
生成二維碼
使用 QRCode 生成二維碼
import QRCode from 'qrcode';
// 使用 async 生成圖片
const options = {};
const url = window.location.href;
async url => {
try {
console.log(await QRCode.toDataURL(url, options))
} catch (err) {
console.error(err);
}
}
將 await QRCode.toDataURL(url, options) 賦值給 圖片 url 即可
生成圖片
主要是使用 htmlToCanvas 生成 canvas 畫布
import html2canvas from 'html2canvas';
html2canvas(document.body).then(function(canvas) {
document.body.appendChild(canvas);
});
但是不單單在此處就完了,由于是 canvas 的原因厘惦。移動端生成出來的圖片比較模糊偷仿。
我們使用一個新的 canvas 方法多倍生成,放入一倍容器里面宵蕉,達到更加清晰的效果酝静,通過超鏈接下載圖片 下載文件簡單實現(xiàn),更完整的實現(xiàn)方式之后更新
const scaleSize = 2;
const newCanvas = document.createElement("canvas");
const target = document.querySelector('div');
const width = parseInt(window.getComputedStyle(target).width);
const height = parseInt(window.getComputedStyle(target).height);
newCanvas.width = width * scaleSize;
newCanvas.height = widthh * scaleSize;
newCanvas.style.width = width + "px";
newCanvas.style.height =width + "px";
const context = newCanvas.getContext("2d");
context.scale(scaleSize, scaleSize);
html2canvas(document.querySelector('.demo'), { canvas: newCanvas }).then(function(canvas) {
// 簡單的通過超鏈接設(shè)置下載功能
document.querySelector(".btn").setAttribute('href', canvas.toDataURL());
}
根據(jù)需要設(shè)置 scaleSize 大小
微信公眾號分享問題
表現(xiàn)
在微信公眾號 H5 開發(fā)中羡玛,頁面內(nèi)部點擊分享按鈕調(diào)用 SDK别智,方法不生效。
解決方案
解決方法:添加一層蒙層稼稿,做分享引導(dǎo)薄榛。
因為頁面內(nèi)部點擊分享按鈕無法直接調(diào)用讳窟,而分享功能需要點擊右上角更多來操作。
然后用戶可能不知道通過右上角小標里面的功能分享敞恋。又想引導(dǎo)用戶分享丽啡,這時應(yīng)該怎么做呢?
技術(shù)無法實現(xiàn)的耳舅,從產(chǎn)品出發(fā)碌上。
如果技術(shù)上實現(xiàn)復(fù)雜,或者直接不能實現(xiàn)浦徊。不要強行鉆牛角尖哦,學(xué)會懟產(chǎn)品天梧,也是程序員必備的能力之一盔性。
H5 調(diào)用 SDK 相關(guān)解決方案
產(chǎn)生原因
在 Hybrid App 中使用 H5 是最常見的不過了,剛接觸的呢岗,肯定會很生疏模糊冕香。不知道 H5 和 Hybrid 是怎么交互的。怎樣同時支持 iOS 和 Android 呢后豫?現(xiàn)在來談?wù)?Hybrid 技術(shù)要點悉尾,原生與 H5 的通信。
解決方案
使用 DSBridge 同時支持 iOS 與 Android
文檔見參考資料
SDK小組 提供方法
- 注冊方法 bridge.register
bridge.register('enterApp', function() {
broadcast.emit('ENTER_APP')
})
- 回調(diào)方法 bridge.call
export const getSDKVersion = () => bridge.call('BLT.getSDKVersion')
事件監(jiān)聽與觸發(fā)法
const broadcast = {
on: function(name, fn, pluralable) {
this._on(name, fn, pluralable, false)
},
once: function(name, fn, pluralable) {
this._on(name, fn, pluralable, true)
},
_on: function(name, fn, pluralable, once) {
let eventData = broadcast.data
let fnObj = { fn: fn, once: once }
if (pluralable && Object.prototype.hasOwnProperty.call(eventData, 'name')) {
eventData[name].push(fnObj)
} else {
eventData[name] = [fnObj]
}
return this
},
emit: function(name, data, thisArg) {
let fn, fnList, i, len
thisArg = thisArg || null
fnList = broadcast.data[name] || []
for (i = 0, len = fnList.length; i < len; i++) {
fn = fnList[i].fn
fn.apply(thisArg, [data, name])
if (fnList[i].once) {
fnList.splice(i, 1)
i--
len--
}
}
return this
},
data: {}
}
export default broadcast
踩坑注意
方法調(diào)用前挫酿,一定要判斷 SDK 是否提供該方法 如果 Android 提供該方法构眯,iOS 上調(diào)用就會出現(xiàn)一個方法調(diào)用失敗等彈窗。怎么解決呢早龟?
提供一個判斷是否 Android惫霸、iOS。根據(jù)設(shè)備進行判斷
export const hasNativeMethod = (name) =>
return bridge.hasNativeMethod('BYJ.' + name)
}
export const getSDKVersion = function() {
if (hasNativeMethod('getSDKVersion')) {
bridge.call('BYJ.getSDKVersion')
}
}
同一功能需要iOS葱弟,Android方法名相同壹店,這樣更好處理哦
H5 調(diào)試相關(guān)方案策略
表現(xiàn)
調(diào)試代碼一般就是為了查看數(shù)據(jù)和定位 bug。分為兩種場景芝加,一種是開發(fā)和測試時調(diào)試硅卢,一種是生產(chǎn)環(huán)境上調(diào)試。
為什么有生產(chǎn)環(huán)境上調(diào)試呢藏杖?有些時候測試環(huán)境上沒法復(fù)現(xiàn)這個 bug将塑,測試環(huán)境和生產(chǎn)環(huán)境不一致,此時就需要緊急生產(chǎn)調(diào)試制市。
在 PC 端開發(fā)時抬旺,我們可以直接掉出控制臺,使用瀏覽器提供的工具操作devtools或者查看日志祥楣。但是在 App 內(nèi)部我們怎么做呢开财?
原理與解決方案
1. vconsole 控制臺插件
使用方法也很簡單
import Vconsole from 'vconsole'
new Vconsole()
有興趣看看它實現(xiàn)的基本原理汉柒,我們關(guān)注的點應(yīng)該在 vsconsole 如何打印出我們所有 log 的 騰訊開源vconsole
上述方法僅用于開發(fā)和測試。生產(chǎn)環(huán)境中不允許出現(xiàn)责鳍,所以碾褂,使用時需要對環(huán)境進行判斷。
import Vconsole from 'vconsole'
if (process.env.NODE_ENV !== 'production') {
new Vconsole()
}
2. 代理 + spy-debugger
操作稍微有點麻煩历葛,不過我會詳細寫出正塌,大致分為 4 個步驟
- 安裝插件(全局安裝)
sudo npm install spy-debugger -g
- 手機與電腦置于同一 wifi 下,手機設(shè)置代理
設(shè)置手機的 HTTP 代理恤溶,代理 IP 地址設(shè)置為 PC 的 IP 地址乓诽,端口為spy-debugger的啟動端口
spy-debugger 默認端口:9888
Android :設(shè)置 - WLAN - 長按選中網(wǎng)絡(luò) - 修改網(wǎng)絡(luò) - 高級 - 代理設(shè)置 - 手動
IOS :設(shè)置 - Wi-Fi - 選中網(wǎng)絡(luò), 點擊感嘆號, HTTP 代理手動
- 手機打開瀏覽器或者 app 中 H5 頁面
- 打開桌面日志網(wǎng)站進行調(diào)試,點擊 npm 控制臺監(jiān)聽地址咒程。查看抓包和 H5 頁面結(jié)構(gòu)
這種方式可以調(diào)試生成環(huán)境的頁面鸠天,不需要修改代碼,可以應(yīng)付大多數(shù)調(diào)試需求