學完這 4 個小技巧,讓你的移動端互動體驗更加優秀

蚊子部落格發表於2021-03-03

現在在手機等移動端裝置訪問的人越來越多,我們前端開發者一直致力於將設計稿還原成頁面,供使用者訪問。但除高度還原設計稿外,互動上的良好體驗也是我們應該做到的。

玩玩手機

1. 即時反饋

我們在玩遊戲的過程中,通常都會遇到一個詞:“打擊感”,通俗的理解就是我們做出的每一個操作,都有很強烈的反饋,比如視覺上的動畫變化,聽覺上產生的聲音,或者移動裝置的震動感等。

1.1 按鈕的即時反饋

在前端頁面中,也應當像遊戲中的打擊感一樣,使用者任何的操作都應當予以即時的反饋,告訴使用者他的操作是有效的,系統已收到他的操作,內部正在處理中。

例如使用者在點選頁面中的按鈕時,按鈕最好有一種被按下的效果:

button:active {
  transform: translateY(4px);
}

若按鈕被下壓的效果不太適合頁面整體的風格,您也可以做一個背景顏色上的變化。

1.2 持續性的反饋

每個使用者的裝置型號、網路狀態等情況都不一樣,我們不能要求每個使用者都在良好的 WiFi 下操作我們的頁面。

若使用者的某個行為產生了網路請求,並要根據請求返回的結果,反饋給使用者。這種情況,頁面都應當給使用者一種持續性的反饋,表示一個動作正在後臺執行。如果沒有這種效果,即使已經在請求介面了,使用者也會認為點選沒有反應,會多次的去點選按鈕,以期望得到響應。

我們可以在這裡給自己定下一條規則:

凡是有網路請求的情形,均要有 loading 效果的持續性反饋。

我們通常可以在使用者觸發的按鈕上展示 loading 效果,也可以在全域性頁面上展示 loading 效果,這個根據每個頁面的風格自行選擇即可。

開啟紅包

例如頁面上有個紅包需要點選按鈕開啟,當使用者點選按鈕後,按鈕就可以展示出一個旋轉的 loading 效果,待介面返回結果再開啟紅包,展示具體的金額,或者其他的結果。

1.3 頁面初始化

在現在大部分前後端分離的場景下(同時沒有使用同構直出方案),都是先展示出一個沒有資料的前端頁面,然後請求資料,待資料返回後再渲染頁面。

這種情形和上面 1.2 中是一樣的,不過這個是在剛進入頁面就觸發的!這裡我們也是要展示出 loading 效果的,只不過是 loading 展示的時機的問題。

  1. 先一個全域性 loading 的開啟頁,在資料沒有返回回來時,看不到任何相關活動元素;
  2. 先用初始化的假資料或者兜底資料,渲染一個基本框架,然後在某個位置展示 loading 效果,並請求資料,資料返回後再替換假資料進行渲染。

這兩種方式也是各有不同的使用場景,就我個人而言,我更喜歡第 2 種方式,能夠第一時間將頁面中的元素展示給使用者;但如果頁面佈局因介面的資料改變較大,建議還是採用第 1 種方式,這樣 loading 結束時,不會出現頁面大幅度閃動的感覺。

1.4 資料的展示

我們拿到介面的資料後,通常會有兩種展示狀態:

  1. 無資料,進行“暫無資料”之類的提示;
  2. 有資料,正常展示資料;

比如一個展示獎品列表中資料中,這裡我們通常會初始化一個 list 變數來接收介面返回的資料:

const List = () => {
  const [list, setList] = useState([]);

  useEffect(() => {
    // 設定資料
    // setList([]);
  }, []);

  return (
    <div className="list">
      {list.length ? (
        <div className="container">
          {list.map((item) => (
            <div key={item.key}>{item.title}</div>
          ))}
        </div>
      ) : (
        <div className="nothing">暫無資料</div>
      )}
    </div>
  );
};

在請求介面的過程中,頁面會展示什麼?“暫無資料”,給使用者的第一視覺感受就是:我的獎品丟了。等過一會兒介面返回資料了,然後又重新將資料展示出來。

這裡,我們就忽略了一個很重要的狀態:loading狀態。因為“暫無資料”,也是一種結果,不是過程,是要告訴使用者,您當前是沒有資料的。因此,不能把“暫無資料”作為 loading 狀態來展示。

const List = () => {
  const [loading, setLoading] = useState(true);
  const [list, setList] = useState([]);

  useEffect(() => {
    // 設定資料
    // setList([]);
    setLoading(false); // 請求完介面,再把loading狀態取消,該展示什麼結果就展示什麼
  }, []);

  if (loading) {
    return (
      <div className="list">
        <div className="loading">請求資料中...</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">暫無資料</div>
      )}
    </div>
  );
};

勿擾

2. 行為跟隨

這裡我也不太想好用個什麼名字,概況來說,告訴使用者剛才發生了什麼,將使用者操作視覺化, 來增強使用者對操作行為的感知度, 同時也能對元素內容的認知。

因使用者行為產生的新互動,應當與當前使用者的行為相關。

2.1 點選按鈕後呼起彈窗

使用者點選按鈕後,會彈出一個彈窗,彈窗可以從按鈕所在的方向或者位置,彈出到整個頁面的中心。

呼起彈窗

給到使用者的感受就是該彈窗與按鈕是相關的。

2.2 列表中有物件變動時

例如在一個表格或者列表中,有新增、修改或者刪除一行(一列)的行為,可以用一個動畫和背景色來區分該元素, 過一段時間再恢復正常。

列表中有物件變動時

2.3 絲滑的滑動跟隨

在不新增任何 CSS 屬性時,滑動有滾動條區域時,總感覺有一種卡頓感,就是手指滑動時頁面就跟著滑動,手指離開則頁面停止滑動。

這裡我們新增上一個屬性即可:

body {
  -webkit-overflow-scrolling: touch;
}

3. 考慮移動裝置的握持姿勢

在現在手機螢幕越來越大的趨勢下,單手握持手機時,大模板只能在以左下角或者右下角為中心的區域活動。因此,在底部區域操作的情況越來越多,例如底部區域的導航,彈窗中點選空白區域即可關閉等等。

3.1 避免滾動穿透

在一個可滾動的頁面中,呼起一個彈窗,這個彈窗中的內容也比較多,也需要滾動,如果不加處理的話,可能會造成兩個區域同時滾動,體驗不好。也就是避免滾動穿透。

這裡我們就要把底層的滾動鎖住,只可以滾動處在最上層的區域。這裡的原理我就不多講解,推薦一個我一直在使用的元件tua-body-scroll-lock,該元件匯出了 2 個方法:

  • lock: 鎖定區域,傳入 dom 元素,則表示該 dom 區域內是可以滾動的;
  • unlock: 解除鎖定,當彈窗消除時,需要解除被鎖定的區域;

在 react 中的使用方式:

useEffect(() => {
  // 鎖定body的滾動,只在彈窗內部滾動
  // 只有需要設定可以滾動區域時,才使用該方法
  if (props.scrollContainer) {
    lock(props.scrollContainer);
  }

  return () => {
    if (props.scrollContainer) {
      unlock(props.scrollContainer);
    }
  };
}, [props.scrollContainer]);

同時的,我們最好在遮罩區域新增可以關閉彈窗的操作,避免使用者伸手夠彈窗右上角的關閉按鈕。

3.2 原生 select 標籤的使用

在移動端開發中,下拉框我們使用原生 select 標籤時,iOS 和 Android 的表現是不一樣的,iOS 會出現在螢幕的底部,滾動選擇某個選項;而 Android 中,則是螢幕中間彈出一個彈層,然後可以進行選擇。

模擬的select標籤

如果圖方便的話,其實可以使用原生的 select 標籤。但這種方式,總感覺與頁面元素之間產生了割裂,因此如果可以的話,儘量模擬出一個 select 標籤。

4. 良好的兜底策略

每個使用者的裝置型號、網路狀態等情況都不一樣。總會因為各種各樣的原因,導致頁面展示異常。因此,我們應當做好提示和一些兜底策略。

4.1 全屏沉浸式頁面應當保持關閉操作

通常情況下,在移動端 APP 中開啟的頁面,頂部都會有一個白色的標題欄。但有些活動頁面為了更好地沉浸式體驗,會把白色標題欄去掉,同時還去掉了右劃退出的操作,只能點選自定義的返回按鈕才能退出。

沉浸式的頁面

例如這個頁面,左上角的返回按鈕是頁面本身自定義的。而這個頁面必須是介面正常返回資料後才展示出來,在最開始時,如果有異常時,會展示錯誤資訊,但沒有返回按鈕。這就導致使用者無法退出該活動,只能殺掉 APP 再重新進入。

體驗非常不好,這裡我們就要保證:全屏沉浸式頁面不管是哪種狀態,應當全程保持關閉操作!

當然,現在已經沒有這個問題了。

4.2 永遠不要相信後臺一直很穩定

後臺經常說的一句話是“不要相信任何從前端傳過來的資料”,我們也一樣:

永遠不要相信後臺一直很穩定。

我們要做好介面服務可能會掛掉的預案:

  1. 設定請求介面的超時時間,不要讓使用者無限制等待;
  2. 良好的提示;
  3. 有條件時,可以自動重試,或者讓使用者手動嘗試重試請求介面;
  4. 採用兜底策略遮蓋;

前 3 種我們都可以理解,當介面異常並無法繼續後續的操作時,應當告知使用者有服務有異常了,可以稍後重試。

對於第 4 種,通常可能會發生在高併發的抽獎過程中,越是讓使用者重試,併發量就越高。因此在抽獎異常時,可以直接告訴使用者未中獎,而不是“服務異常”之類的話術。要不然,一方面會引起使用者的不滿,另一方面會造成使用者的大量重試。

這個百度在春晚發紅包中,就有用到過,在伺服器短時間內承受到高併發量時,則直接告訴使用者未抽中紅包;同時,對於一些抽獎會同時發放多個獎品時,也要做好每個獎品服務都可以會掛掉的準備,比如同時會發放 3 個獎品:

  1. 服務都正常,正常發放;
  2. 2 個正常,就只發放 2 個獎品,左右排列;
  3. 只有 1 個服務正常,則只發放 1 個獎品,居中排列;
  4. 均異常,則告訴使用者未中獎;

千萬不要留有空間或者槽位告訴使用者“該位置本應該有獎品,但實際上沒有”的感覺。

4.3 懶載入

懶載入是一個老生常談的話題,這裡我們只針對圖片懶載入來進行梳理。

在頁面中圖片比較多時,請儘量使用圖片懶載入,並考慮好圖片載入失敗的情況,可以先建立一個 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的回撥,當dom元素進入到可是區域內時
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', '');
  }
};

同時,我們在體驗的過程中發現,在有些華為手機裡,圖片還沒載入完畢時,會展示一個裂開的圖片,如果該圖片 alt 註釋,也把 alt 註釋顯示出來,稍過一會兒,等圖片載入完畢了,就正常展示圖片了。

這種情況,我們也可以使用圖片懶載入,或者將圖片設定為背景圖片,避免出現圖片裂開的狀態。

5. 總結

我們在移動端開發的過程中,總會有多種解決方案。如果我們站在使用者的角度多想一想,就能讓產品的互動體驗變的更好。

感謝您的閱讀,歡迎關注我的公眾號:

蚊子的部落格-公眾號

相關文章