12個前端必會 H5 問題及解決方法

2020-08-14 21:08:15

前言

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

問題

下面 下麪列舉了我遇到的一些常規問題,如有遇到其他問題請在評論區補充,之後我也會實踐後加以補充,感謝!(經常更新該文)

行動端 H5 相關問題彙總:

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

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

 

 

 

 

原理與實踐

之前兩篇文章已經詳細的論述了1px 問題與 響應式佈局問題,並給出了原理和解決方案。

防止丟失,點贊收藏後跳轉至快捷通道:1px通道與響應式佈局通道

接下來呢,我們看看其他問題的原理和解決方案吧。

以下解決方案,均經過我測試成功,健康安全,請放下食用。由於篇幅原因,某些非核心解決方案的實現細節暫未談論,需要自行研究。

iOS 滑動不流暢

 

表現

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

產生原因

爲什麼 iOS 的 webview 中 滑動不流暢,它是如何定義的?

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

原來在 iOS 5.0 以及之後的版本,滑動有定義有兩個值 auto 和 touch,預設值爲 auto

-webkit-overflow-scrolling: touch; /* 當手指從觸控式螢幕上移開,會保持一段時間的卷動 */

-webkit-overflow-scrolling: auto; /* 當手指從觸控式螢幕上移開,卷動會立即停止 */

解決方案

1.在卷動容器上增加卷動 touch 方法

-webkit-overflow-scrolling 值設定爲 touch

.wrapper {
    -webkit-overflow-scrolling: touch;
}
設定卷軸隱藏: .container ::-webkit-scrollbar {display: none;}

可能會導致使用position:fixed; 固定定位的元素,隨着頁面一起卷動

2.設定 overflow

設定外部 overflow 爲 hidden,設定內容元素 overflow 爲 auto。內部元素超出 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-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 點選事件延時與穿透

表現

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

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

產生原因

爲什麼會產生 click 延時?

iOS 中的 safari,爲了實現雙擊縮放操作,在單擊 300ms 之後,如果未進行第二次點選,則執行 click 單擊操作。也就是說來判斷使用者行爲是否爲雙擊產生的。但是,在 App 中,無論是否需要雙擊縮放這種行爲,click 單擊都會產生 300ms 延遲。

爲什麼會產生 click 點選穿透?

雙層元素疊加時,在上層元素上系結 touch 事件,下層元素系結 click 事件。由於 click 發生在 touch 之後,點選上層元素,元素消失,下層元素會觸發 click 事件,由此產生了點選穿透的效果。

原理與解決方案

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

前面已經介紹了,移動裝置不僅支援點選,還支援幾個觸控事件。那麼我們現在基本思路就是用 touch 事件代替click 事件。

將 click 替換成 touchstart 不僅解決了 click 事件都延時問題,還解決了穿透問題。因爲穿透問題是在 touch 和 click 混用時產生。

在原生中使用

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

在 vue 中使用

<button @touchstart="handleTouchstart()">點選</button>

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

那麼,是否可以將 click 事件全部替換成 touchstart 呢?爲什麼開源框架還會給出 click 事件呢?

我們想象一種情景,同時需要點選和滑動的場景下。如果將 click 替換成 touchstart 會怎樣?

事件觸發順序: touchstarttouchmovetouchendclick

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

所以呢,在具有卷動的情況下,還是建議使用 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 延時和穿透問題都沒了

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

fastclick原始碼 核心程式碼不長, 1000 行不到。有興趣可以瞭解一下!

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

表現

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

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

產生原因

我們在app 佈局中會有個固定的底部。安卓一些版本中,輸入彈窗出來,會將解壓 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;
  }
}

鍵盤不能回落問題出現在 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-topsafe-area-inset-rightsafe-area-inset-bottomsafe-area-inset-left safe-area-inset-*由四個定義了視口邊緣內矩形的 toprightbottom 和 left 的環境變數組成,這樣可以安全地放入內容,而不會有被非矩形的顯示切斷的風險。對於矩形視口,例如普通的筆記型電腦顯示器,其值等於零。對於非矩形顯示器(如圓形錶盤,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 頁面結構

這種方式可以偵錯生成環境的頁面,不需要修改程式碼,可以應付大多數偵錯需求

總結

本篇文章耗費作者一個多星期的業餘時間,存手工敲打 4500 +字,同時收集,整理之前很多坑點和邊寫作邊思考總結。如果能對你有幫助,便是它最大的價值。都看到這裏還不點贊,太過不去啦!

由於技術水平有限,文章中如有錯誤地方,請在評論區指出,感謝!

關於行動端 H5 的文章告一段落了,之後實踐中遇到的問題都將在此文中更新。另外準備做一個行動端 H5 開源專案。

之後,應該回去研究下開源和麪試題相關內容分享,想持續瞭解更多,不妨點贊關注唄。


小編是個多年開發經驗的程式設計師。如果你想要學好WEB前端,在學習過程中,身邊沒有一個能夠指導你學習的人,可以到這個WEB前端裙,裏面最新學習路線和教學,不管是計算機專業想要往WEB前端方向發展,還是零基礎想轉行,都可以跟着教學學,有什麼不懂的可以在裏面問,這就是WEB前端裙。前面三個輸入112,中間三個輸入666,後面三個輸入2127