Progressive Web App 介面外觀 針對應用操作介面優化操作體驗

me
林彥成
2021-10-01 | 3 min.
文章目錄
  1. 1. 全螢幕模式
  2. 2. 視覺設計
    1. 2.1. 使用系統字形
    2. 2.2. 配合系統主題
    3. 2.3. 網址列顏色
    4. 2.4. 樣式處理
  3. 3. 事件操作
    1. 3.1. 文字選取
    2. 3.2. contextmenu
    3. 3.3. Touch 效果
    4. 3.4. Touch 事件

網頁的外觀和操作本質上還是和原生的有差異但可以透過配置來讓體驗更接近。

  1. 全螢幕模式
  2. 視覺設計
  3. 事件操作

全螢幕模式

全螢幕模式有兩種方式,Progressive Web App 尚未安裝前可以透過程式觸發,安裝後可以透過 display 配置安裝後的執行顯示模式。

  • 程式觸發全螢幕
    • document.body.requestFullscreen();
  • Progressive Web App 可以透過 manifest 的 display 設定
    • fullscreen
    • standalone
    • minimal-ui

視覺設計

Progressive Web App 主視覺、字形、操作上若配合系統會讓用戶能有較好且更一致的操作體驗。

  • 使用系統字形
  • 配合系統主題
  • 網址列顏色
  • 樣式處理

使用系統字形

字形的選用上非常容易影響到使用者的觀感,CSS 上可以透過 system-ui 來使用系統原生的字型。

1
font-family: system-ui;

配合系統主題

當系統開啟暗黑模式後,PWA 也可以透過程式上的撰寫配合更改顯示設定,另外對於 AMOLED 的螢幕來說,暗黑模式也可以更省電。

程式上主要透過 Media Queries Level 5 支援的 prefers-color-scheme 可以判斷出使用者目前使用的系統主題。

  • light: 明亮模式 (深色字體、淺色背景).
  • dark: 暗黑模式 (淺色字體、深色背景).
1
2
3
if (window.matchMedia("(prefers-color-scheme)").media !== "not all") {
console.log("🎉 有支援暗黑模式");
}
1
2
3
4
5
6
7
8
9
10
@media (prefers-color-scheme: dark) {
.day.dark-scheme {
background: #333;
color: white;
}
.night.dark-scheme {
background: black;
color: #ddd;
}
}

Google 有推出了一個 dark-mode-toggle 的模組,主要是靠 Window.matchMedia() 在實作相關邏輯

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const PREFERS_COLOR_SCHEME = "prefers-color-scheme";
const MEDIA = "media";
const LIGHT = "light";
const DARK = "dark";
const MQ_DARK = `(${PREFERS_COLOR_SCHEME}:${DARK})`;
const MQ_LIGHT = `(${PREFERS_COLOR_SCHEME}:${LIGHT})`;

// 看瀏覽器是否支援暗黑模式
const hasNativePrefersColorScheme = matchMedia(MQ_DARK).media !== NOT_ALL;

if (hasNativePrefersColorScheme) {
matchMedia(MQ_DARK).addListener(({ matches }) => {
this.mode = matches ? DARK : LIGHT;
this._dispatchEvent(COLOR_SCHEME_CHANGE, { colorScheme: this.mode });
});
}

有興趣的大大也可以參考看看:

https://github.com/GoogleChromeLabs/dark-mode-toggle

動畫來源: https://github.com/GoogleChromeLabs/dark-mode-toggle >

網址列顏色

PWA 中 manifest 的 theme_color 設定,可以客製化 Android Chrome 的網址列的顏色,注意要和 meta 中的顏色相同。

<meta name="theme-color" content="#3c553c" />

樣式處理

針對操作上的 UI 優化,可以針對常見三個狀態做樣式設定

  • :hover
  • :focus
  • :active

按鈕上也建議把預設的樣式拿掉。

1
2
3
4
5
6
7
8
9
.btn:focus {
outline: 0;
/* 把外面的框框拿掉 */
}

.btn {
-webkit-tap-highlight-color: transparent;
/* 移除 highlight 的顏色 */
}

事件操作

  • 文字選取
  • contextmenu
  • Touch 效果
  • Touch 事件

文字選取

對於有觸控功能原生 APP 跟網頁最大的差別在很多文字是不能選取的,這時候我們也可以透過 CSS 針對這個部分進行設定。

1
2
3
4
/* 不給選 */
user-select: none;
/* 單擊一次選取 */
user-select: all;

contextmenu

避免長按跳出瀏覽器的 menu

  • iOS
1
webkit-touch-callout: none;
  • Android
1
2
3
window.addEventListener("contextmenu", (e) => {
e.preventDefault();
});

Touch 效果

如果想要自訂義,就要把瀏覽器預設的樣式行為停用,要注意一但設為 none 變成要將很多細節去用 JS 執行

1
touch-action: none;

Touch 事件

越來越多裝置支援 touch 的行為,工程師在實作上其實就是針對觸控、手勢去實作相關事件。

  • touches: 所有有按到手指的陣列
    • force 壓力
    • 座標
    • 半徑座標
  • targetTouches: 有按到 touch 事件的才會傳回來
  • changedTouches: 產生變化的觸碰點資訊,當離開時 touchestargetTouches 為空,所以只能透過 changedTouches 來理解最後發生的事情

針對不同的事件去做相關的事件綁定,注意一下 touchMove 不等於 mouseMove,User Agent 會分派順序如下:

  1. touchstart
  2. touchmove: 觸發的次數很多,要注意是否影響原來的效能
  3. touchend
  4. mousemove: cursor 移動
  5. mousedown
  6. mouseup
  7. click

要注意的細節如下

  • 減少在 touchstart 就做事件處理,因為這個階段也抓不到其他手指的事件
  • 如果 touchStart 就 preventDefault() 會讓滑鼠事件 click 消失,但不 preventDefault() 又會同時觸發 touch 跟 click,這時候就需要看情境特別處理,像是 preventDefault() 後在 touch 事件的 callback 中決定是否要用程式去觸發 click
  • 觸控至少要設計間距要超過 5px 比較不會誤觸,可以用增加 padding 的方式
  • 300ms 延遲是怎麼來的,原因是因為 UA 要去判斷是否現在是在做更進階的動作,像是長按、點兩下等,如果直接監聽 touch 事件去做 click 事件就不會有這個延遲時間。或是透過 meta 設定不給縮放 (user-scalable=no) 就不會發生。

<meta name="viewport" content="width=device-width,user-scalable=no">

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 判斷是否支援
if (window.PointerEvent) {
// Pointer Event
swipeFrontElement.addEventListener(
"pointerdown",
this.handleGestureStart,
true
);
swipeFrontElement.addEventListener(
"pointermove",
this.handleGestureMove,
true
);
swipeFrontElement.addEventListener("pointerup", this.handleGestureEnd, true);
swipeFrontElement.addEventListener(
"pointercancel",
this.handleGestureEnd,
true
);
} else {
// 手指觸控螢幕觸發,只有一隻手指也會
swipeFrontElement.addEventListener(
"touchstart",
this.handleGestureStart,
true
);
// 手指在螢幕上滑動時連續觸發,一次 move 期間一秒 60 次
swipeFrontElement.addEventListener("touchmove", this.handleGestureMove, true);
// 離開螢幕時觸發
swipeFrontElement.addEventListener("touchend", this.handleGestureEnd, true);
// 當系統停止監聽時觸發,比較少遇到,可能的情境像是接電話的時候
swipeFrontElement.addEventListener(
"touchcancel",
this.handleGestureEnd,
true
);

// Mouse Listener
swipeFrontElement.addEventListener(
"mousedown",
this.handleGestureStart,
true
);
}

喜歡這篇文章,請幫忙拍拍手喔 🤣

share