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