前言
?不知不覺在網易已有三年半,占了一半時間都在與移動端打交道,整個階段都是遇坑填坑的學習過程。移動端開發在前端里像神一樣地存在,不是說它多難而是說它坑位實在太多了,怎樣填都填不完。Android和iOS各顯神通,Android的系統版本和屏幕分辨率多得難以一招兼容,iOS的頑固標準和未知特性多得難以快速掌握。
?三年半沉淀通過本文記錄下所遇到的坑位,或許有些坑位還未遇到,但本文記錄的「40條坑位」絕對能讓同學們少走很多彎路,特別是前端小白。為了減少廢話提高本文質量,對以下內容做一些約定。
?提及的安卓系統包括Android和基于Android開發的系統提及的蘋果系統包括iOS和iPadOS本文針對的開發場景是移動端瀏覽器,因此大部分坑位的解決方案在桌面端瀏覽器里不一定有效解決方案若未提及適用系統就默認在安卓系統和蘋果系統上都適用,若提及適用系統則會詳細說明Webkit及其衍生內核在移動端瀏覽器市場占有率里達到驚人的97%,因此無需太過擔心CSS3、ES6和瀏覽器新特性的兼容性真正的開發環境都是基于webpack構建,因此代碼演示都不會帶上CSS前綴,除非該屬性是Webkit獨有才會帶上-webkit-
?每次填坑都是一次實踐過程,全部坑位的源碼都按語言方向記錄在筆者Github上,若有未記錄的坑位可提PR讓筆者合并,給個Star支持下咧!
?本來想為每個坑位都截圖或錄制GIF作為演示,但考慮到目前掘金的Markdown編輯器操作圖片還存在缺陷就放棄了,每次上傳圖片都會花費很多時間甚至上傳失敗(「望掘金的產品小姐姐和程序小哥哥優化喔」)。若需演示只能自行復制代碼了。
?HTML方向
?調用系統功能
?使用<a>能快速調用移動設備的電話/短信/郵件三大通訊功能,使用<input>能快速調用移動設備的的圖庫/文件。
?這些功能方便了頁面與系統的交互,關鍵在于調用格式一定要準確,否則會被移動端瀏覽器忽略。
?<!-- 撥打電話 -->
<a href="tel:10086">撥打電話給10086小姐姐</a>
<!-- 發送短信 -->
<a href="sms:10086">發送短信給10086小姐姐</a>
<!-- 發送郵件 -->
<a href="mailto:young.joway@aliyun.com">發送郵件給JowayYoung</a>
<!-- 選擇照片或拍攝照片 -->
<input type="file" accept="image/*">
<!-- 選擇視頻或拍攝視頻 -->
<input type="file" accept="video/*">
<!-- 多選文件 -->
<input type="file" multiple>
?忽略自動識別
?有些移動端瀏覽器會自動將數字字母符號識別為電話/郵箱并將其渲染成上述「調用系統功能」里的<a>。雖然很方便卻有可能違背需求。
?<!-- 忽略自動識別電話 -->
<meta name="format-detection" content="telephone=no">
<!-- 忽略自動識別郵箱 -->
<meta name="format-detection" content="email=no">
<!-- 忽略自動識別電話和郵箱 -->
<meta name="format-detection" content="telephone=no, email=no">
?彈出數字鍵盤
?使用<input type="tel">彈起數字鍵盤會帶上#和*,適合輸入電話。推薦使用<input type="number" pattern="\d*">彈起數字鍵盤,適合輸入驗證碼等純數字格式。
?<!-- 純數字帶#和* -->
<input type="tel">
<!-- 純數字 -->
<input type="number" pattern="\d*">
?喚醒原生應用
?通過location.href與原生應用建立通訊渠道,這種頁面與客戶端的通訊方式稱為「URL Scheme」,其基本格式為scheme://[path][?query],筆者曾經發表過《H5與App的通訊方式》講述URL Scheme的使用。
?「scheme」:應用標識,表示應用在系統里的唯一標識「path」:應用行為,表示應用某個頁面或功能「query」:應用參數,表示應用頁面或應用功能所需的條件參數
?URL Scheme一般由前端與客戶端共同協商。喚醒原生應用的前提是必須在移動設備里安裝了該應用,有些移動端瀏覽器即使安裝了該應用也無法喚醒原生應用,因為它認為URL Scheme是一種潛在的危險行為而禁用它,像Safari和微信瀏覽器。還好微信瀏覽器可開啟白名單讓URL Scheme有效。
?若在頁面引用第三方原生應用的URL Schema,可通過抓包第三方原生應用獲取其URL。
?<!-- 打開微信 -->
<a href="weixin://">打開微信</a>
<!-- 打開支付寶 -->
<a href="alipays://">打開支付寶</a>
<!-- 打開支付寶的掃一掃 -->
<a href="alipays://platformapi/startapp?saId=10000007">打開支付寶的掃一掃</a>
<!-- 打開支付寶的螞蟻森林 -->
<a href="alipays://platformapi/startapp?appId=60000002">打開支付寶的螞蟻森林</a>
?禁止頁面縮放
?在智能手機的普及下,很多網站都具備桌面端和移動端兩種瀏覽版本,因此無需雙擊縮放查看頁面。禁止頁面縮放可保障移動端瀏覽器能無遺漏地展現頁面所有布局。
?<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, minimum-scale=1, maximum-scale=1">
?禁止頁面緩存
?「Cache-Control」指定請求和響應遵循的緩存機制,不想使用瀏覽器緩存就禁止唄!
?<meta http-equiv="Cache-Control" content="no-cache">
?禁止字母大寫
?有時在輸入框里輸入文本會默認開啟首字母大寫糾正,就是輸入首字母小寫會被自動糾正成大寫,特么的煩。直接聲明autocapitalize=off關閉首字母大寫功能和autocorrect=off關閉糾正功能。
?<input autocapitalize="off" autocorrect="off">
?針對Safari配置
?貼一些Safari較零散且少用的配置。
?<!-- 設置Safari全屏,在iOS7+無效 -->
<meta name="apple-mobile-web-app-capable" content="yes">
<!-- 改變Safari狀態欄樣式,可選default/black/black-translucent,需在上述全屏模式下才有效 -->
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- 添加頁面啟動占位圖 -->
<link rel="apple-touch-startup-image" href="pig.jpg" media="(device-width: 375px)">
<!-- 保存網站到桌面時添加圖標 -->
<link rel="apple-touch-icon" sizes="76x76" href="pig.jpg">
<!-- 保存網站到桌面時添加圖標且清除默認光澤 -->
<link rel="apple-touch-icon-precomposed" href="pig.jpg">
?針對其他瀏覽器配置
?貼一些其他瀏覽器較零散且少用的配置,主要是常用的QQ瀏覽器、UC瀏覽器和360瀏覽器。從網易MTL的測試數據得知,新版的QQ瀏覽器和UC瀏覽器已不支持以下<meta>聲明了。
?<!-- 強制QQ瀏覽器豎屏 -->
<meta name="x5-orientation" content="portrait">
<!-- 強制QQ瀏覽器全屏 -->
<meta name="x5-fullscreen" content="true">
<!-- 開啟QQ瀏覽器應用模式 -->
<meta name="x5-page-mode" content="app">
<!-- 強制UC瀏覽器豎屏 -->
<meta name="screen-orientation" content="portrait">
<!-- 強制UC瀏覽器全屏 -->
<meta name="full-screen" content="yes">
<!-- 開啟UC瀏覽器應用模式 -->
<meta name="browsermode" content="application">
<!-- 開啟360瀏覽器極速模式 -->
<meta name="renderer" content="webkit">
?讓:active有效,讓:hover無效
?有些元素的:active可能會無效,而元素的:hover在點擊后會一直處于點擊狀態,需點擊其他位置才能解除點擊狀態。給<body>注冊一個空的touchstart事件可將兩種狀態反轉。
?<body ontouchstart></body>
?CSS方向
?自動適應布局
?針對移動端,筆者通常會結合JS依據屏幕寬度與設計圖寬度的比例動態聲明<html>的font-size,以rem為長度單位聲明所有節點的幾何屬性,這樣就能做到大部分移動設備的頁面兼容,兼容出入較大的地方再通過媒體查詢做特別處理。
?筆者通常將rem布局比例設置成1rem=100px,即在設計圖上100px長度在CSS代碼上使用1rem表示。
?function AutoResponse(width = 750) {
? ? const target = document.documentElement;
? ? if (target.clientWidth >= 600) {
? ? ? ? target.style.fontSize = "80px";
? ? } else {
? ? ? ? target.style.fontSize = target.clientWidth / width * 100 + "px";
? ? }
}
AutoResponse();
window.addEventListener("resize", () => AutoResponse());
?當然還可依據屏幕寬度與設計圖寬度的比例使用calc()動態聲明<html>的font-size,這樣就能節省上述代碼。不對,是完全代替上述代碼。
?html {
? ? font-size: calc(100vw / 7.5);
}
?若以iPad Pro分辨率1024px為移動端和桌面端的斷點,還可結合媒體查詢做斷點處理。1024px以下使用rem布局,否則不使用rem布局。
?@media screen and (max-width: 1024px) {
? ? html {
? ? ? ? font-size: calc(100vw / 7.5);
? ? }
}
?自動適應背景
?使用rem布局聲明一個元素背景,多數情況會將background-size聲明為cover。可能在設計圖對應分辨率的移動設備下,背景會完美貼合顯示,但換到其他分辨率的移動設備下就會出現左右空出1px到npx的空隙。
?此時將background-size聲明為100% 100%,跟隨width和height的變化而變化。反正width和height都是量好的實際尺寸。
?.elem {
? ? width: 1rem;
? ? height: 1rem;
? ? background: url("pig.jpg") no-repeat center/100% 100%;
}
?監聽屏幕旋轉
?你還在使用JS判斷橫屏豎屏調整樣式嗎?那就真的Out了。
?/* 豎屏 */
@media all and (orientation: portrait) {
? ? /* 自定義樣式 */
}
/* 橫屏 */
@media all and (orientation: landscape) {
? ? /* 自定義樣式 */
}
?支持彈性滾動
?在蘋果系統上非<body>元素的滾動操作可能會存在卡頓,但安卓系統不會出現該情況。通過聲明overflow-scrolling:touch調用系統原生滾動事件優化彈性滾動,增加頁面滾動的流暢度。
?body {
? ? -webkit-overflow-scrolling: touch;
}
.elem {
? ? overflow: auto;
}
?禁止滾動傳播
?與桌面端瀏覽器不一樣,移動端瀏覽器有一個奇怪行為。當頁面包含多個滾動區域時,滾完一個區域后若還存在滾動動量則會將這些剩余動量傳播到下一個滾動區域,造成該區域也滾動起來。這種行為稱為「滾動傳播」。
?若不想產生這種奇怪行為可直接禁止。
?.elem {
? ? overscroll-behavior: contain;
}
?禁止屏幕抖動
?對于一些突然出現滾動條的頁面,可能會產生左右抖動的不良影響。在一個滾動容器里,打開彈窗就隱藏滾動條,關閉彈窗就顯示滾動條,來回操作會讓屏幕抖動起來。提前聲明滾動容器的padding-right為滾動條寬度,就能有效消除這個不良影響。
?每個移動端瀏覽器的滾動條寬度都有可能不一致,甚至不一定占位置,通過以下方式能間接計算出滾動條的寬度。100vw為視窗寬度,100%為滾動容器內容寬度,相減就是滾動條寬度,妥妥的動態計算。
?body {
? ? padding-right: calc(100vw - 100%);
}
?禁止長按操作
?有時不想用戶長按元素呼出菜單進行點鏈接、打電話、發郵件、保存圖片或掃描二維碼等操作,聲明touch-callout:none禁止用戶長按操作。
?有時不想用戶復制粘貼盜文案,聲明user-select:none禁止用戶長按操作和選擇復制。
?* {
? ? /* pointer-events: none; */ /* 微信瀏覽器還需附加該屬性才有效 */
? ? user-select: none; /* 禁止長按選擇文字 */
? ? -webkit-touch-callout: none;
}
?但聲明user-select:none會讓<input>和<textarea>無法輸入文本,可對其聲明user-select:auto排除在外。
?input,
textarea {
? ? user-select: auto;
}
?禁止字體調整
?旋轉屏幕可能會改變字體大小,聲明text-size-adjust:100%讓字體大小保持不變。
?* {
? ? text-size-adjust: 100%;
}
?禁止高亮顯示
?觸摸元素會出現半透明灰色遮罩,不想要!
?* {
? ? -webkit-tap-highlight-color: transparent;
}
?禁止動畫閃屏
?在移動設備上添加動畫,多數情況會出現閃屏,給動畫元素的父元素構造一個3D環境就能讓動畫穩定運行了。
?.elem {
? ? perspective: 1000;
? ? backface-visibility: hidden;
? ? transform-style: preserve-3d;
}
?美化表單外觀
?表單元素樣式太丑希望自定義,appearance:none來幫你。
?button,
input,
select,
textarea {
? ? appearance: none;
? ? /* 自定義樣式 */
}
?美化滾動占位
?滾動條樣式太丑希望自定義,::-webkit-scrollbar-*來幫你。記住以下三個關鍵詞就能隨機應變了。
?[x] 「::-webkit-scrollbar」:滾動條整體部分[x] 「::-webkit-scrollbar-track」:滾動條軌道部分[x] 「::-webkit-scrollbar-thumb」:滾動條滑塊部分
?::-webkit-scrollbar {
? ? width: 6px;
? ? height: 6px;
? ? background-color: transparent;
}
::-webkit-scrollbar-track {
? ? background-color: transparent;
}
::-webkit-scrollbar-thumb {
? ? border-radius: 3px;
? ? background-image: linear-gradient(135deg, #09f, #3c9);
}
?美化輸入占位
?輸入框占位文本太丑,::-webkit-input-placeholder來幫你。
?input::-webkit-input-placeholder {
? ? color: #66f;
}
?對齊輸入占位
?有強迫癥的同學總會覺得輸入框文本位置整體偏上,感覺未居中心里就癢癢的。桌面端瀏覽器里聲明line-height等于height就能解決,但移動端瀏覽器里還是未能解決,需將line-height聲明為normal才行。
?input {
? ? line-height: normal;
}
?對齊下拉選項
?下拉框選項默認向左對齊,是時候改改向右對齊了。
?select option {
? ? direction: rtl;
}
?修復點擊無效
?在蘋果系統上有些情況下非可點擊元素監聽click事件可能會無效,針對該情況只需對不觸發click事件的元素聲明cursor:pointer就能解決。
?.elem {
? ? cursor: pointer;
}
?識別文本換行
?多數情況會使用JS換行文本,那就真的Out了。若接口返回字段包含\n或<br>,千萬別替換掉,可聲明white-space:pre-line交由瀏覽器做斷行處理。
?* {
? ? white-space: pre-line;
}
?開啟硬件加速
?想動畫更流暢嗎,開啟GPU硬件加速唄!
?.elem {
? ? transform: translate3d(0, 0, 0);
? ? /* transform: translateZ(0); */
}
?描繪像素邊框
?萬年話題,如何描繪一像素邊框?
?.elem {
? ? position: relative;
? ? width: 200px;
? ? height: 80px;
? ? &::after {
? ? ? ? position: absolute;
? ? ? ? left: 0;
? ? ? ? top: 0;
? ? ? ? border: 1px solid #f66;
? ? ? ? width: 200%;
? ? ? ? height: 200%;
? ? ? ? content: "";
? ? ? ? transform: scale(.5);
? ? ? ? transform-origin: left top;
? ? }
}
?控制溢出文本
?萬年話題,如何控制文本做單行溢出和多行溢出?
?.elem {
? ? width: 400px;
? ? line-height: 30px;
? ? font-size: 20px;
? ? &.sl-ellipsis {
? ? ? ? overflow: hidden;
? ? ? ? text-overflow: ellipsis;
? ? ? ? white-space: nowrap;
? ? }
? ? &.ml-ellipsis {
? ? ? ? display: -webkit-box;
? ? ? ? overflow: hidden;
? ? ? ? text-overflow: ellipsis;
? ? ? ? -webkit-line-clamp: 3;
? ? ? ? -webkit-box-orient: vertical;
? ? }
}
?JS方向
?禁止點擊穿透
?移動端瀏覽器里點擊操作會存在300ms延遲,往往會造成點擊延遲甚至點擊無效,這個是眾所周知的事情。
?2007年蘋果發布首款iPhone搭載的Safari為了將桌面端網站能較好地展示在移動端瀏覽器上而使用了雙擊縮放。該方案就是上述300ms延遲的主要原因,當用戶執行第一次單擊后會預留300ms檢測用戶是否繼續執行單擊,若是則執行縮放操作,若否則執行點擊操作。鑒于該方案的成功,其他移動端瀏覽器也復制了該方案,現在幾乎所有移動端瀏覽器都配備該功能。而該方案引發的點擊延遲被稱為「點擊穿透」。
?在前端領域里最早解決點擊穿透是jQuery時代的zepto,估計現在大部分同學都未使用過zepto,其實它就是移動端版本的jquery。zepto封裝tap事件能有效地解決點擊穿透,通過監聽document上的touch事件完成tap事件的模擬,并將tap事件冒泡到document上觸發。
?在移動端瀏覽器上不使用click事件而使用touch事件是因為click事件有著明顯的延遲,后續又出現fastclick。該解決方案監聽用戶是否做了雙擊操作,可正常使用click事件,而點擊穿透就交給fastclick自動判斷。更多fastclick原理可自行百度,在此不作過多介紹。
?fastclick有現成的NPM包,可直接安裝到項目里。引入fastclick可使用click事件代替tap事件,接入方式極其簡單。
?import Fastclick from "fastclick";
FastClick.attach(document.body);
?禁止滑動穿透
?移動端瀏覽器里出現彈窗時,若在屏幕上滑動能觸發彈窗底下的內容跟著滾動,這個是眾所周知的事情。
?首先明確解決滑動穿透需保持哪些交互行為,那就是除了彈窗內容能點擊或滾動,其他內容都不能點擊或滾動。目前很多解決方案都無法做到這一點,全部解決方案都能禁止<body>的滾動行為卻引發其他問題。
?彈窗打開后內部內容無法滾動彈窗關閉后頁面滾動位置丟失Webview能上下滑動露出底色
?當打開彈窗時給<body>聲明position:fixed;left:0;width:100%并動態聲明top。聲明position:fixed會導致<body>滾動條消失,此時會發現雖然無滑動穿透,但頁面滾動位置早已丟失。通過scrollingElement獲取頁面當前滾動條偏移量并將其取負值且賦值給top,那么在視覺上就無任何變化。當關閉彈窗時移除position:fixed;left:0;width:100%和動態top。
?scrollingElement可兼容地獲取scrollTop和scrollHeight等屬性,在移動端瀏覽器里屢試不爽。document.scrollingElement.scrollHeight可完美代替曾經的document.documentElement.scrollHeight || document.body.scrollHeight,一眼看上去就是代碼減少了。
?該解決方案在視覺上無任何變化,完爆其他解決方案,其實就是一種反向思維和障眼法。該解決方案完美解決固定彈窗和滾動彈窗對<body>全局滾動的影響,當然也可用于局部滾動容器里,因此很值得推廣。
?body.static {
? ? position: fixed;
? ? left: 0;
? ? width: 100%;
}
?const body = document.body;
const openBtn = document.getElementById("open-btn");
const closeBtn = document.getElementById("close-btn");
openBtn.addEventListener("click", e => {
? ? e.stopPropagation();
? ? const scrollTop = document.scrollingElement.scrollTop;
? ? body.classList.add("static");
? ? body.style.top = `-${scrollTop}px`;
});
closeBtn.addEventListener("click", e => {
? ? e.stopPropagation();
? ? body.classList.remove("static");
? ? body.style.top = "";
});
?支持往返刷新
?點擊移動端瀏覽器的前進按鈕或后退按鈕,有時不會自動執行舊頁面的JS代碼,這與往返緩存有關。這種情況在Safari上特別明顯,簡單概括就是往返頁面無法刷新。
?「往返緩存」指瀏覽器為了在頁面間執行前進后退操作時能擁有更流暢體驗的一種策略,以下簡稱BFCache。該策略具體表現為:當用戶前往新頁面前將舊頁面的DOM狀態保存在BFCache里,當用戶返回舊頁面前將舊頁面的DOM狀態從BFCache里取出并加載。大部分移動端瀏覽器都會部署BFCache,可大大節省接口請求的時間和帶寬。
?了解什么是BFCache再對癥下藥,解決方案就在window.onunload上做文章。
?// 在新頁面監聽頁面銷毀事件
window.addEventListener("onunload", () => {
? ? // 執行舊頁面代碼
});
?若在Vue SPA上使用keep-alive也不能讓頁面刷新,可將接口請求放到beforeRouteEnter()里。
?當然還有另一種解決方案。pageshow事件在每次頁面加載時都會觸發,無論是首次加載還是再次加載都會觸發,這就是它與load事件的區別。pageshow事件暴露的persisted可判斷頁面是否從BFCache里取出。
?window.addEventListener("pageshow", e => e.persisted && location.reload());
?若瀏覽器不使用<meta http-equiv="Cache-Control" content="no-cache">禁用緩存,該解決方案還是很值得一用。
?解析有效日期
?在蘋果系統上解析YYYY-MM-DD HH:mm:ss這種日期格式會報錯Invalid Date,但在安卓系統上解析這種日期格式完全無問題。
?new Date("2019-03-31 21:30:00"); // Invalid Date
?查看Safari相關開發手冊發現可用YYYY/MM/DD HH:mm:ss這種日期格式,簡單概括就是年月日必須使用/銜接而不能使用-銜接。當然安卓系統也支持該格式,然而接口返回字段的日期格式通常是YYYY-MM-DD HH:mm:ss,那么需替換其中的-為/。
?const date = "2019-03-31 21:30:00";
new Date(date.replace(/\-/g, "/"));
?修復高度坍塌
?當頁面同時出現以下三個條件時,鍵盤占位會把頁面高度壓縮一部分。當輸入完成鍵盤占位消失后,頁面高度有可能回不到原來高度,產生坍塌導致Webview底色露臉,簡單概括就是輸入框失焦后頁面未回彈。
?頁面高度過小輸入框在頁面底部或視窗中下方輸入框聚焦輸入文本
?只要保持前后滾動條偏移量一致就不會出現上述問題。在輸入框聚焦時獲取頁面當前滾動條偏移量,在輸入框失焦時賦值頁面之前獲取的滾動條偏移量,這樣就能間接還原頁面滾動條偏移量解決頁面高度坍塌。
?const input = document.getElementById("input");
let scrollTop = 0;
input.addEventListener("focus", () => {
? ? scrollTop = document.scrollingElement.scrollTop;
});
input.addEventListener("blur", () => {
? ? document.scrollingElement.scrollTo(0, this.scrollTop);
});
?修復輸入監聽
?在蘋果系統上的輸入框輸入文本,keyup/keydown/keypress事件可能會無效。當輸入框監聽keyup事件時,逐個輸入英文和數字會有效,但逐個輸入中文不會有效,需按回車鍵才會有效。
?此時可用input事件代替輸入框的keyup/keydown/keypress事件。
?簡化回到頂部
?曾幾何時編寫一個返回頂部函數麻煩得要死,需scrollTop、定時器和條件判斷三者配合才能完成。其實DOM對象里隱藏了一個很好用的函數可完成上述功能,一行核心代碼就能搞定。
?該函數就是scrollIntoView,它會滾動目標元素的父容器使之對用戶可見,簡單概括就是相對視窗讓容器滾動到目標元素位置。它有三個可選參數能讓scrollIntoView滾動起來更優雅。
?「behavior」:動畫過渡效果,默認auto無,可選smooth平滑「inline」:水平方向對齊方式,默認nearest就近對齊,可選start頂部對齊、center中間對齊和end底部對齊「block」:垂直方向對齊方式,默認start頂部對齊,可選center中間對齊、end底部對齊和nearest就近對齊
?const gotopBtn = document.getElementById("gotop-btn");
openBtn.addEventListener("click", () => document.body.scrollIntoView({ behavior: "smooth" }));
?當然還可滾動到目標元素位置,只需將document.body修改成目標元素的DOM對象。一行核心代碼就能搞掂的事情為何還編寫那么多代碼去完成,不累嗎?
?簡化懶性加載
?與上述「簡化回到頂部」一樣,編寫一個懶性加載函數也同樣需scrollTop、定時器和條件判斷三者配合才能完成。其實DOM對象里隱藏了一個很好用的函數可完成上述功能,該函數無需監聽容器的scroll事件,通過瀏覽器自身機制完成滾動監聽。
?該函數就是InterpObserver,它提供一種異步觀察目標元素及其祖先元素或頂級文檔視窗交叉狀態的方法。詳情可參照MDN文檔,在此不作過多介紹。
?懶性加載的第一種使用場景:「圖片懶加載」。只需確認圖片進入可視區域就賦值加載圖片,賦值完成還需對圖片停止監聽。
?<img src="pig.jpg">
<!-- 很多<img> -->
?const imgs = document.querySelectorAll("img.lazyload");
const observer = new InterpObserver(nodes => {
? ? nodes.forEach(v => {
? ? ? ? if (v.isIntersecting) { // 判斷是否進入可視區域
? ? ? ? ? ? v.target.src = v.target.dataset.src; // 賦值加載圖片
? ? ? ? ? ? observer.unobserve(v.target); // 停止監聽已加載的圖片
? ? ? ? }
? ? });
});
imgs.forEach(v => observer.observe(v));
?懶性加載的第二種使用場景:「下拉加載」。在列表最底部部署一個占位元素且該元素無任何高度或實體外觀,只需確認占位元素進入可視區域就請求接口加載數據。
?<ul>
? ? <li></li>
? ? <!-- 很多<li> -->
</ul>
<!-- 也可將#bottom以<li>的形式插入到<ul>內部的最后位置 -->
<div id="bottom"></div>
?const bottom = document.getElementById("bottom");
const observer = new InterpObserver(nodes => {
? ? const tgt = nodes[0]; // 反正只有一個
? ? if (tgt.isIntersecting) {
? ? ? ? console.log("已到底部,請求接口");
? ? ? ? // 執行接口請求代碼
? ? }
})
bottom.observe(bottom);
?優化掃碼識別
?通常移動端瀏覽器都會配備長按二維碼圖片識別鏈接的功能,但長按二維碼可能無法識別或錯誤識別。二維碼表面看上去是一張圖片,可二維碼生成方式卻五花八門,二維碼生成方式有以下三種。
?[x] 使用<img>渲染[x] 使用<svg>渲染[x] 使用<canvas>渲染
?從網易MTL的測試數據得知,大部分移動端瀏覽器只能識別<img>渲染的二維碼,為了讓全部移動端瀏覽器都能識別二維碼,那只能使用<img>渲染二維碼了。若使用SVG和Canvas的方式生成二維碼,那就想方設法把二維碼數據轉換成Base64再賦值到<img>的src上。
?一個頁面可能存在多個二維碼,若長按二維碼只能識別最后一個,那只能控制每個頁面只存在一個二維碼。
?自動播放媒體
?常見媒體元素包括音頻<audio>和視頻<video>,為了讓用戶得到更好的媒體播放體驗與不盲目浪費用戶流量,大部分移動端瀏覽器都明確規定不能自動播放媒體或默認屏蔽autoplay。為了能讓媒體在頁面加載完成后自動播放,只能顯式聲明播放。
?const audio = document.getElementById("audio");
const video = document.getElementById("video");
audio.play();
video.play();
?對于像微信瀏覽器這樣的內置瀏覽器,還需監聽其應用SDK加載完成才能觸發上述代碼,以保障WebView正常渲染。其他內置瀏覽器同理,在此不作過多介紹。
?document.addEventListener("WeixinJSBridgeReady", () => {
? ? // 執行上述媒體自動播放代碼
});
?在蘋果系統上明確規定用戶交互操作開始后才能播放媒體,未得到用戶響應會被Safari自動攔截,因此需監聽用戶首次觸摸操作并觸發媒體自動播放,而該監聽僅此一次。
?document.body.addEventListener("touchstart", () => {
? ? // 執行上述媒體自動播放代碼
}, { once: true });
?總結
?若有未記錄的坑位可提PR讓筆者合并,我們一起記錄更多的「移動端坑位」,讓更多前端開發者少走彎路。本文也是筆者2021年在掘金社區的首篇文章,希望今年能產出更多高質量文章。從筆者角度上看,一篇文章不是技術多牛逼筆風多優雅才歸納為好文章,而是整體內容能幫助自己和更多人進步才算是好文章。向這個方向進發,為自己打Call。