H5常見問題及解決方法


前言

做爲一個開發了多個 H5 項目的前端工程師,在開發過程當中不免會遇到一些兼容性等 爬過坑的問題。如今我將這些問題一一彙總一下,並在後面給出 坑產生的原理,和 現階段常規的填坑方案。由此來作一個階段性的總結。

問題

下面列舉了我遇到的一些常規問題,若有遇到其餘問題請在評論區補充,以後我也會實踐後加以補充,感謝!css

移動端 H5 相關問題彙總:


  • 1px 問題
  • 響應式佈局
  • iOS 滑動不流暢
  • iOS 上拉邊界下拉出現白色空白
  • 頁面件放大或縮小不肯定性行爲
  • click 點擊穿透與延遲
  • 軟鍵盤彈出將頁面頂起來、收起未回落問題
  • iPhone X 底部欄適配問題
  • 保存頁面爲圖片和二維碼問題和解決方案
  • 微信公衆號 H5 分享問題
  • H5 調用 SDK 相關問題及解決方案
  • H5 調試相關方案與策略

移動端 H5 相關基礎技術概覽


原理與實踐

以前發過兩篇文章已經詳細的論述了1px 問題移動端適配,並給出了原理和方案。html

接下來呢,咱們看看其餘問題的原理和解決方案吧。前端

iOS 滑動不流暢


表現

上下滑動頁面會產生卡頓,手指離開頁面,頁面當即中止運動。總體表現就是滑動不流暢,沒有滑動慣性。vue

產生緣由

爲何 iOS 的 webview 中 滑動不流暢,它是如何定義的?程序員

最終我在 safari 文檔裏面尋找到了答案(文檔連接在參考資料項)。web

原來在 iOS 5.0 以及以後的版本,滑動有定義有兩個值 autotouch,默認值爲 autonpm

-webkit-overflow-scrolling: touch; /* 當手指從觸摸屏上移開,會保持一段時間的滾動 */-webkit-overflow-scrolling: auto; /* 當手指從觸摸屏上移開,滾動會當即中止 */複製代碼

解決方案

1.在滾動容器上增長滾動 touch 方法

-webkit-overflow-scrolling 值設置爲 touchcanvas

.wrapper {    -webkit-overflow-scrolling: touch;}複製代碼

設置滾動條隱藏: .container ::-webkit-scrollbar {display: none;}後端

可能會致使使用position:fixed; 固定定位的元素,隨着頁面一塊兒滾動瀏覽器

2.設置 overflow

設置外部 overflowhidden,設置內容元素 overflowauto。內部元素超出 body 即產生滾動,超出的部分 body 隱藏。

body {    overflow-y: hidden;}.wrapper {    overflow-y: auto;}複製代碼

二者結合使用更佳!

iOS 上拉邊界下拉出現白色空白


表現

手指按住屏幕下拉,屏幕頂部會多出一塊白色區域。手指按住屏幕上拉,底部多出一塊白色區域。

產生緣由

在 iOS 中,手指按住屏幕上下拖動,會觸發 touchmove 事件。這個事件觸發的對象是整個 webview 容器,容器天然會被拖動,剩下的部分會成空白。

解決方案

1. 監聽事件禁止滑動

移動端觸摸事件有三個,分別定義爲

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 事件的速度是能夠實現定義的,取決於硬件性能和其餘實現細節

preventDefault 方法,阻止同一觸點上全部默認行爲,好比滾動。

由此咱們找到解決方案,經過監聽 touchmove,讓須要滑動的地方滑動,不須要滑動的地方禁止滑動。

值得注意的是咱們要過濾掉具備滾動容器的元素。

實現以下:

document.body.addEventListener('touchmove', function(e) {    if(e._isScroller) return;    // 阻止默認事件    e.preventDefault();}, {    passive: false});複製代碼

2. 滾動妥協填充空白,裝飾成其餘功能

在不少時候,咱們能夠不去解決這個問題,換一直思路。根據場景,咱們能夠將下拉做爲一個功能性的操做

好比:下拉後刷新頁面

頁面放大或縮小不肯定性行爲


表現

雙擊或者雙指張開手指頁面元素,頁面會放大或縮小。

產生緣由

HTML 自己會產生放大或縮小的行爲,好比在 PC 瀏覽器上,能夠自由控制頁面的放大縮小。可是在移動端,咱們是不須要這個行爲的。因此,咱們須要禁止該不肯定性行爲,來提高用戶體驗。

原理與解決方案

HTML meta 元標籤標準中有個 中 viewport 屬性,用來控制頁面的縮放,通常用於移動端。以下圖 MDN 中介紹

移動端常規寫法

<meta name="viewport" content="width=device-width, initial-scale=1.0">複製代碼

所以咱們能夠設置 maximum-scaleminimum-scaleuser-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 點擊事件延時與穿透


表現

監聽元素 click 事件,點擊元素觸發時間延遲約 300ms

點擊蒙層,蒙層消失後,下層元素點擊觸發。

產生緣由

爲何會產生 click 延時?

iOS 中的 safari,爲了實現雙擊縮放操做,在單擊 300ms 以後,若是未進行第二次點擊,則執行 click 單擊操做。也就是說來判斷用戶行爲是否爲雙擊產生的。可是,在 App 中,不管是否須要雙擊縮放這種行爲,click 單擊都會產生 300ms 延遲。

爲何會產生 click 點擊穿透?

雙層元素疊加時,在上層元素上綁定 touch 事件,下層元素綁定 click 事件。因爲 click 發生在 touch 以後,點擊上層元素,元素消失,下層元素會觸發 click 事件,由此產生了點擊穿透的效果。

原理與解決方案

解決方案一:使用 touchstart 替換 click

前面已經介紹了,移動設備不只支持點擊,還支持幾個觸摸事件。那麼咱們如今基本思路就是用 touch 事件代替click 事件。

click 替換成 touchstart 不只解決了 click 事件都延時問題,還解決了穿透問題。由於穿透問題是在 touchclick 混用時產生。

在原生中使用

el.addEventListener("touchstart", () => { console.log("ok"); }, false);複製代碼

在 vue 中使用

<button @touchstart="handleTouchstart()">點擊</button>複製代碼

開源解決方案中,也是既提供了 click 事件,又提供了touchstart 事件。如 vant 中的 button 組件

那麼,是否能夠將 click 事件所有替換成 touchstart 呢?爲何開源框架還會給出 click 事件呢?

咱們想象一種情景,同時須要點擊和滑動的場景下。若是將 click 替換成 touchstart 會怎樣?

事件觸發順序: touchstart, touchmove, touchend, click

很容易想象,在我須要touchmove滑動時候,優先觸發了touchstart的點擊事件,是否是已經產生了衝突呢?

因此呢,在具備滾動的狀況下,仍是建議使用 click 處理。

在接下來的fastclick開源庫中也作了以下處理。針對 touchstarttouchend,截取了部分源碼。

// 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 延時和穿透問題都沒了

按照個人慣例,只要涉及開源庫,那麼咱們必定要去了解它實現的原理。主要是將現有的原生事件集合封裝合成一個兼容性較強的事件集合。

fastclick源碼 核心代碼不長, 1000 行不到。有興趣能夠了解一下!

軟鍵盤將頁面頂起來、收起未回落問題


表現

Android 手機中,點擊 input 框時,鍵盤彈出,將頁面頂起來,致使頁面樣式錯亂。

移開焦點時,鍵盤收起,鍵盤區域空白,未回落。

產生緣由

咱們在app 佈局中會有個固定的底部。安卓一些版本中,輸入彈窗出來,會將解壓 absolutefixed 定位的元素。致使可視區域變小,佈局錯亂。

原理與解決方案

軟鍵盤將頁面頂起來的解決方案,主要是經過監聽頁面高度變化,強制恢復成彈出前的高度。

// 記錄原有的視口高度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;  }}複製代碼

鍵盤不能回落問題出如今 iOS 12+ 和 wechat 6.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+)?/);  // 若是設備類型爲iOS 12+ 和wechat 6.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)恢復成原來的視口

iPhone X系列安全區域適配問題


表現

頭部劉海兩側區域或者底部區域,出現劉海遮擋文字,或者呈現黑底或白底空白區域。

產生緣由

iPhone X 以及它以上的系列,都採用劉海屏設計全面屏手勢。頭部、底部、側邊都須要作特殊處理。才能適配 iPhone X 的特殊狀況。

解決方案

設置安全區域,填充危險區域,危險區域不作操做和內容展現。

危險區域指頭部不規則區域,底部橫條區域,左右觸發區域。

具體操做爲:viewport-fit meta 標籤設置爲 cover,獲取全部區域填充。判斷設備是否屬於 iPhone X,給頭部底部增長適配層

viewport-fit 有 3 個值分別爲:

  • auto:此值不影響初始佈局視圖端口,而且整個web頁面都是可查看的。
  • contain:視圖端口按比例縮放,以適合顯示內嵌的最大矩形。
  • cover:視圖端口被縮放以填充設備顯示。強烈建議使用 safe area inset 變量,以確保重要內容不會出如今顯示以外。

設置 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-*由四個定義了視口邊緣內矩形的 top, right, bottomleft 的環境變量組成,這樣能夠安全地放入內容,而不會有被非矩形的顯示切斷的風險。對於矩形視口,例如普通的筆記本電腦顯示器,其值等於零。對於非矩形顯示器(如圓形錶盤,iPhoneX 屏幕),在用戶代理設置的四個值造成的矩形內,全部內容都可見。

其中 env() 用法爲 env( <custom-ident> , <declaration-value>? ),第一個參數爲自定義的區域,第二個爲備用值。

其中 var() 用法爲 var( <custom-property-name> , <declaration-value>? ),做用是在 env() 不生效的狀況下,給出一個備用值。

constant()css 2017-2018 年爲草稿階段,是否已被標準化未知。而其餘iOS 瀏覽器版本中是否有此函數未知,做爲兼容處理而添加進去。

詳情請查看文章末尾的參考資料。

兼容性

頁面生成爲圖片和二維碼問題


表現

在工做中有須要將頁面生成圖片或者二維碼的需求。可能咱們第一想到的,交給後端來生成更簡單。可是這樣咱們須要把頁面代碼所有傳給後端,網絡性能消耗太大。

解決方案

生成二維碼

使用 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 方法多倍生成,放入一倍容器裏面,達到更加清晰的效果,經過超連接下載圖片 下載文件簡單實現,更完整的實現方式以後更新

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) {  // 簡單的經過超連接設置下載功能  document.querySelector(".btn").setAttribute('href', canvas.toDataURL());}複製代碼

根據須要設置 scaleSize 大小

微信公衆號分享問題


表現

在微信公衆號 H5 開發中,頁面內部點擊分享按鈕調用 SDK,方法不生效。

解決方案

解決方法:添加一層蒙層,作分享引導。

由於頁面內部點擊分享按鈕沒法直接調用,而分享功能須要點擊右上角更多來操做。

而後用戶可能不知道經過右上角小標裏面的功能分享。又想引導用戶分享,這時應該怎麼作呢?

技術沒法實現的,從產品出發。

若是技術上實現複雜,或者直接不能實現。不要強行鑽牛角尖哦,學會懟產品,也是程序員必備的能力之一。

H5 調用 SDK 相關解決方案


產生緣由

在 Hybrid App 中使用 H5 是最多見的不過了,剛接觸的,確定會很生疏模糊。不知道 H5 和 Hybrid 是怎麼交互的。怎樣同時支持 iOS 和 Android 呢?如今來談談 Hybrid 技術要點,原生與 H5 的通訊

解決方案

使用 DSBridge 同時支持 iOS 與 Android

文檔見參考資料

SDK小組 提供方法

  1. 註冊方法 bridge.register
bridge.register('enterApp', function() {  broadcast.emit('ENTER_APP')})複製代碼
  1. 回調方法 bridge.call
export const getSDKVersion = () => bridge.call('BLT.getSDKVersion')複製代碼

事件監聽與觸發法

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複製代碼

踩坑注意

方法調用前,必定要判斷 SDK 是否提供該方法 若是 Android 提供該方法,iOS 上調用就會出現一個方法調用失敗等彈窗。怎麼解決呢?

提供一個判斷是否 Android、iOS。根據設備進行判斷

export const hasNativeMethod = (name) =>  return bridge.hasNativeMethod('BYJ.' + name)}export const getSDKVersion = function() {  if (hasNativeMethod('getSDKVersion')) {    bridge.call('BYJ.getSDKVersion')  }}複製代碼

同一功能須要iOS,Android方法名相同,這樣更好處理哦

H5 調試相關方案策略


表現

調試代碼通常就是爲了查看數據定位 bug。分爲兩種場景,一種是開發和測試時調試,一種是生產環境上調試。

爲何有生產環境上調試呢?有些時候測試環境上無法復現這個 bug,測試環境和生產環境不一致,此時就須要緊急生產調試。

在 PC 端開發時,咱們能夠直接掉出控制檯,使用瀏覽器提供的工具操做devtools或者查看日誌。可是在 App 內部咱們怎麼作呢?

原理與解決方案

1. vconsole 控制檯插件

使用方法也很簡單

import Vconsole from 'vconsole'new Vconsole()複製代碼

有興趣看看它實現的基本原理,咱們關注的點應該在 vsconsole 如何打印出咱們全部 log 的 騰訊開源vconsole

上述方法僅用於開發和測試。生產環境中不容許出現,因此,使用時須要對環境進行判斷。

import Vconsole from 'vconsole'if (process.env.NODE_ENV !== 'production') {    new Vconsole()}複製代碼

2. 代理 + spy-debugger

操做稍微有點麻煩,不過我會詳細寫出,大體分爲 4 個步驟

  1. 安裝插件(全局安裝)
sudo npm install spy-debugger -g複製代碼
  1. 手機與電腦置於同一 wifi 下,手機設置代理

設置手機的 HTTP 代理,代理 IP 地址設置爲 PC 的 IP 地址,端口爲spy-debugger的啓動端口

spy-debugger 默認端口:9888

Android :設置 - WLAN - 長按選中網絡 - 修改網絡 - 高級 - 代理設置 - 手動

IOS :設置 - Wi-Fi - 選中網絡, 點擊感嘆號, HTTP 代理手動

  1. 手機打開瀏覽器或者 app 中 H5 頁面
  2. 打開桌面日誌網站進行調試,點擊 npm 控制檯監聽地址。查看抓包和 H5 頁面結構
相關文章
相關標籤/搜索