JS如何讓你的移動端交互體驗更加優(yōu)秀
1. 即時反饋
我們在玩游戲的過程中,通常都會遇到一個詞:“打擊感”,通俗的理解就是我們做出的每一個操作,都有很強(qiáng)烈的反饋,比如視覺上的動畫變化,聽覺上產(chǎn)生的聲音,或者移動設(shè)備的震動感等。
1.1 按鈕的即時反饋
在前端頁面中,也應(yīng)當(dāng)像游戲中的打擊感一樣,用戶任何的操作都應(yīng)當(dāng)予以即時的反饋,告訴用戶他的操作是有效的,系統(tǒng)已收到他的操作,內(nèi)部正在處理中。
例如用戶在點擊頁面中的按鈕時,按鈕最好有一種被按下的效果:
button:active {
transform: translateY(4px);
}
若按鈕被下壓的效果不太適合頁面整體的風(fēng)格,您也可以做一個背景顏色上的變化。
1.2 持續(xù)性的反饋
每個用戶的設(shè)備型號、網(wǎng)絡(luò)狀態(tài)等情況都不一樣,我們不能要求每個用戶都在良好的 WiFi 下操作我們的頁面。
若用戶的某個行為產(chǎn)生了網(wǎng)絡(luò)請求,并要根據(jù)請求返回的結(jié)果,反饋給用戶。這種情況,頁面都應(yīng)當(dāng)給用戶一種持續(xù)性的反饋,表示一個動作正在后臺執(zhí)行。如果沒有這種效果,即使已經(jīng)在請求接口了,用戶也會認(rèn)為點擊沒有反應(yīng),會多次的去點擊按鈕,以期望得到響應(yīng)。
我們可以在這里給自己定下一條規(guī)則:
凡是有網(wǎng)絡(luò)請求的情形,均要有 loading 效果的持續(xù)性反饋。
我們通常可以在用戶觸發(fā)的按鈕上展示 loading 效果,也可以在全局頁面上展示 loading 效果,這個根據(jù)每個頁面的風(fēng)格自行選擇即可。

例如頁面上有個紅包需要點擊按鈕開啟,當(dāng)用戶點擊按鈕后,按鈕就可以展示出一個旋轉(zhuǎn)的 loading 效果,待接口返回結(jié)果再打開紅包,展示具體的金額,或者其他的結(jié)果。
1.3 頁面初始化
在現(xiàn)在大部分前后端分離的場景下(同時沒有使用同構(gòu)直出方案),都是先展示出一個沒有數(shù)據(jù)的前端頁面,然后請求數(shù)據(jù),待數(shù)據(jù)返回后再渲染頁面。
這種情形和上面 1.2 中是一樣的,不過這個是在剛進(jìn)入頁面就觸發(fā)的!這里我們也是要展示出 loading 效果的,只不過是 loading 展示的時機(jī)的問題。
1.先一個全局 loading 的開啟頁,在數(shù)據(jù)沒有返回回來時,看不到任何相關(guān)活動元素;
2.先用初始化的假數(shù)據(jù)或者兜底數(shù)據(jù),渲染一個基本框架,然后在某個位置展示 loading 效果,并請求數(shù)據(jù),數(shù)據(jù)返回后再替換假數(shù)據(jù)進(jìn)行渲染。
這兩種方式也是各有不同的使用場景,就我個人而言,我更喜歡第 2 種方式,能夠第一時間將頁面中的元素展示給用戶;但如果頁面布局因接口的數(shù)據(jù)改變較大,建議還是采用第 1 種方式,這樣 loading 結(jié)束時,不會出現(xiàn)頁面大幅度閃動的感覺。
1.4 數(shù)據(jù)的展示
我們拿到接口的數(shù)據(jù)后,通常會有兩種展示狀態(tài):
1.無數(shù)據(jù),進(jìn)行“暫無數(shù)據(jù)”之類的提示;
2.有數(shù)據(jù),正常展示數(shù)據(jù);
比如一個展示獎品列表中數(shù)據(jù)中,這里我們通常會初始化一個 list 變量來接收接口返回的數(shù)據(jù):
const List = () => {
const [list, setList] = useState([]);
useEffect(() => {
// 設(shè)置數(shù)據(jù)
// setList([]);
}, []);
return (
<div className="list">
{list.length ? (
<div className="container">
{list.map((item) => (
<div key={item.key}>{item.title}</div>
))}
</div>
) : (
<div className="nothing">暫無數(shù)據(jù)</div>
)}
</div>
);
};
在請求接口的過程中,頁面會展示什么?“暫無數(shù)據(jù)”,給用戶的第一視覺感受就是:我的獎品丟了。等過一會兒接口返回數(shù)據(jù)了,然后又重新將數(shù)據(jù)展示出來。
這里,我們就忽略了一個很重要的狀態(tài):loading狀態(tài)。因為“暫無數(shù)據(jù)”,也是一種結(jié)果,不是過程,是要告訴用戶,您當(dāng)前是沒有數(shù)據(jù)的。因此,不能把“暫無數(shù)據(jù)”作為 loading 狀態(tài)來展示。
const List = () => {
const [loading, setLoading] = useState(true);
const [list, setList] = useState([]);
useEffect(() => {
// 設(shè)置數(shù)據(jù)
// setList([]);
setLoading(false); // 請求完接口,再把loading狀態(tài)取消,該展示什么結(jié)果就展示什么
}, []);
if (loading) {
return (
<div className="list">
<div className="loading">請求數(shù)據(jù)中...</div>
</div>
);
}
return (
<div className="list">
{list.length ? (
<div className="container">
{list.map((item) => (
<div key={item.key}>{item.title}</div>
))}
</div>
) : (
<div className="nothing">暫無數(shù)據(jù)</div>
)}
</div>
);
};
2. 行為跟隨
這里我也不太想好用個什么名字,概況來說,告訴用戶剛才發(fā)生了什么,將用戶操作可視化, 來增強(qiáng)用戶對操作行為的感知度, 同時也能對元素內(nèi)容的認(rèn)知。
因用戶行為產(chǎn)生的新交互,應(yīng)當(dāng)與當(dāng)前用戶的行為相關(guān)。
2.1 點擊按鈕后呼起彈窗
用戶點擊按鈕后,會彈出一個彈窗,彈窗可以從按鈕所在的方向或者位置,彈出到整個頁面的中心。

給到用戶的感受就是該彈窗與按鈕是相關(guān)的。
2.2 列表中有對象變動時
例如在一個表格或者列表中,有新增、修改或者刪除一行(一列)的行為,可以用一個動畫和背景色來區(qū)分該元素, 過一段時間再恢復(fù)正常。

2.3 絲滑的滑動跟隨
在不添加任何 CSS 屬性時,滑動有滾動條區(qū)域時,總感覺有一種卡頓感,就是手指滑動時頁面就跟著滑動,手指離開則頁面停止滑動。
這里我們添加上一個屬性即可:
body {
-webkit-overflow-scrolling: touch;
}
3. 考慮移動設(shè)備的握持姿勢
在現(xiàn)在手機(jī)屏幕越來越大的趨勢下,單手握持手機(jī)時,大模板只能在以左下角或者右下角為中心的區(qū)域活動。因此,在底部區(qū)域操作的情況越來越多,例如底部區(qū)域的導(dǎo)航,彈窗中點擊空白區(qū)域即可關(guān)閉等等。
3.1 避免滾動穿透
在一個可滾動的頁面中,呼起一個彈窗,這個彈窗中的內(nèi)容也比較多,也需要滾動,如果不加處理的話,可能會造成兩個區(qū)域同時滾動,體驗不好。也就是避免滾動穿透。
這里我們就要把底層的滾動鎖住,只可以滾動處在最上層的區(qū)域。這里的原理我就不多講解,推薦一個我一直在使用的組件tua-body-scroll-lock,該組件導(dǎo)出了 2 個方法:
1.lock: 鎖定區(qū)域,傳入 dom 元素,則表示該 dom 區(qū)域內(nèi)是可以滾動的;
2.unlock: 解除鎖定,當(dāng)彈窗消除時,需要解除被鎖定的區(qū)域;
在 react 中的使用方式:
useEffect(() => {
// 鎖定body的滾動,只在彈窗內(nèi)部滾動
// 只有需要設(shè)置可以滾動區(qū)域時,才使用該方法
if (props.scrollContainer) {
lock(props.scrollContainer);
}
return () => {
if (props.scrollContainer) {
unlock(props.scrollContainer);
}
};
}, [props.scrollContainer]);
同時的,我們最好在遮罩區(qū)域添加可以關(guān)閉彈窗的操作,避免用戶伸手夠彈窗右上角的關(guān)閉按鈕。
3.2 原生 select 標(biāo)簽的使用
在移動端開發(fā)中,下拉框我們使用原生 select 標(biāo)簽時,iOS 和 Android 的表現(xiàn)是不一樣的,iOS 會出現(xiàn)在屏幕的底部,滾動選擇某個選項;而 Android 中,則是屏幕中間彈出一個彈層,然后可以進(jìn)行選擇。

如果圖方便的話,其實可以使用原生的 select 標(biāo)簽。但這種方式,總感覺與頁面元素之間產(chǎn)生了割裂,因此如果可以的話,盡量模擬出一個 select 標(biāo)簽。
4. 良好的兜底策略
每個用戶的設(shè)備型號、網(wǎng)絡(luò)狀態(tài)等情況都不一樣??倳驗楦鞣N各樣的原因,導(dǎo)致頁面展示異常。因此,我們應(yīng)當(dāng)做好提示和一些兜底策略。
4.1 全屏沉浸式頁面應(yīng)當(dāng)保持關(guān)閉操作
通常情況下,在移動端 APP 中打開的頁面,頂部都會有一個白色的標(biāo)題欄。但有些活動頁面為了更好地沉浸式體驗,會把白色標(biāo)題欄去掉,同時還去掉了右劃退出的操作,只能點擊自定義的返回按鈕才能退出。

例如這個頁面,左上角的返回按鈕是頁面本身自定義的。而這個頁面必須是接口正常返回數(shù)據(jù)后才展示出來,在最開始時,如果有異常時,會展示錯誤信息,但沒有返回按鈕。這就導(dǎo)致用戶無法退出該活動,只能殺掉 APP 再重新進(jìn)入。
體驗非常不好,這里我們就要保證:全屏沉浸式頁面不管是哪種狀態(tài),應(yīng)當(dāng)全程保持關(guān)閉操作!
當(dāng)然,現(xiàn)在已經(jīng)沒有這個問題了。
4.2 永遠(yuǎn)不要相信后臺一直很穩(wěn)定
后臺經(jīng)常說的一句話是“不要相信任何從前端傳過來的數(shù)據(jù)”,我們也一樣:
永遠(yuǎn)不要相信后臺一直很穩(wěn)定。
我們要做好接口服務(wù)可能會掛掉的預(yù)案:
1.設(shè)置請求接口的超時時間,不要讓用戶無限制等待;
2.良好的提示;
3.有條件時,可以自動重試,或者讓用戶手動嘗試重試請求接口;
4.采用兜底策略遮蓋;
前 3 種我們都可以理解,當(dāng)接口異常并無法繼續(xù)后續(xù)的操作時,應(yīng)當(dāng)告知用戶有服務(wù)有異常了,可以稍后重試。
對于第 4 種,通??赡軙l(fā)生在高并發(fā)的抽獎過程中,越是讓用戶重試,并發(fā)量就越高。因此在抽獎異常時,可以直接告訴用戶未中獎,而不是“服務(wù)異常”之類的話術(shù)。要不然,一方面會引起用戶的不滿,另一方面會造成用戶的大量重試。
這個百度在春晚發(fā)紅包中,就有用到過,在服務(wù)器短時間內(nèi)承受到高并發(fā)量時,則直接告訴用戶未抽中紅包;同時,對于一些抽獎會同時發(fā)放多個獎品時,也要做好每個獎品服務(wù)都可以會掛掉的準(zhǔn)備,比如同時會發(fā)放 3 個獎品:
1.服務(wù)都正常,正常發(fā)放;
2.2 個正常,就只發(fā)放 2 個獎品,左右排列;
3.只有 1 個服務(wù)正常,則只發(fā)放 1 個獎品,居中排列;
4.均異常,則告訴用戶未中獎;
千萬不要留有空間或者槽位告訴用戶“該位置本應(yīng)該有獎品,但實際上沒有”的感覺。
4.3 懶加載
懶加載是一個老生常談的話題,這里我們只針對圖片懶加載來進(jìn)行梳理。
在頁面中圖片比較多時,請盡量使用圖片懶加載,并考慮好圖片加載失敗的情況,可以先創(chuàng)建一個 Image 來先加載圖片,加載城后再給到頁面中的 dom 元素,否則使用兜底圖片:
// 判斷圖片是否可以加載成功
const loadImage = (imgUrl: string): Promise<HTMLImageElement> => {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = imgUrl;
if (img.complete) {
return resolve(img);
}
img.onload = () => {
resolve(img);
};
img.onerror = reject;
});
};
// IntersectionObserver的回調(diào),當(dāng)dom元素進(jìn)入到可是區(qū)域內(nèi)時
const targetExposeCallback = async (dom: HTMLElement) => {
let original = dom.getAttribute('data-original');
if (original) {
try {
await loadImage(original);
} catch (err) {
// 1x1的圖片
original = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
}
setLoading(false);
if (dom.tagName.toLowerCase() === 'img') {
dom.setAttribute('src', original);
} else {
// eslint-disable-next-line
dom.style.backgroundImage = `url("${original}")`;
}
dom.setAttribute('data-original', '');
}
};
同時,我們在體驗的過程中發(fā)現(xiàn),在有些華為手機(jī)里,圖片還沒加載完畢時,會展示一個裂開的圖片,如果該圖片 alt 注釋,也把 alt 注釋顯示出來,稍過一會兒,等圖片加載完畢了,就正常展示圖片了。
這種情況,我們也可以使用圖片懶加載,或者將圖片設(shè)置為背景圖片,避免出現(xiàn)圖片裂開的狀態(tài)。
5. 總結(jié)
我們在移動端開發(fā)的過程中,總會有多種解決方案。如果我們站在用戶的角度多想一想,就能讓產(chǎn)品的交互體驗變的更好。
以上就是JS如何讓你的移動端交互體驗更加優(yōu)秀的詳細(xì)內(nèi)容,更多關(guān)于JS讓你的移動端交互體驗更加優(yōu)秀的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript高級編程之Array的用法總結(jié)
這篇文章主要為大家介紹了JavaScript中Array常見用法的總結(jié),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-11-11
基于JavaScript中標(biāo)識符的命名規(guī)則介紹
下面小編就為大家分享一篇基于JavaScript中標(biāo)識符的命名規(guī)則介紹,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01
在JavaScript中創(chuàng)建對象的可行方法小結(jié)
在Web前端開發(fā)中,JavaScript是一門功能強(qiáng)大的語言,其核心之一便是對象的創(chuàng)建與操作,對象是JavaScript中數(shù)據(jù)結(jié)構(gòu)的重要組成部分,本文將深入探討JavaScript中創(chuàng)建對象的各種方法,并結(jié)合代碼示例和實際開發(fā)經(jīng)驗進(jìn)行詳細(xì)講解,需要的朋友可以參考下2025-03-03
JS將所有對象s的屬性復(fù)制給對象r(原生js+jquery)
這篇文章主要介紹了js中將所有對象s的屬性復(fù)制給對象r的方法,原生js+jquery分別實現(xiàn)2014-01-01

