網頁的外觀和操作本質上還是和原生的有差異但可以透過配置來讓體驗更接近。
全螢幕模式有兩種方式,Progressive Web App 尚未安裝前可以透過程式觸發,安裝後可以透過 display 配置安裝後的執行顯示模式。
document.body.requestFullscreen();
Progressive Web App 主視覺、字形、操作上若配合系統會讓用戶能有較好且更一致的操作體驗。
字形的選用上非常容易影響到使用者的觀感,CSS 上可以透過 system-ui
來使用系統原生的字型。
font-family: system-ui;
當系統開啟暗黑模式後,PWA 也可以透過程式上的撰寫配合更改顯示設定,另外對於 AMOLED 的螢幕來說,暗黑模式也可以更省電。
程式上主要透過 Media Queries Level 5 支援的 prefers-color-scheme 可以判斷出使用者目前使用的系統主題。
if (window.matchMedia("(prefers-color-scheme)").media !== "not all") {
console.log("? 有支援暗黑模式");
}
@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()
在實作相關邏輯
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
按鈕上也建議把預設的樣式拿掉。
.btn:focus {
outline: 0;
/* 把外面的框框拿掉 */
}
.btn {
-webkit-tap-highlight-color: transparent;
/* 移除 highlight 的顏色 */
}
對於有觸控功能原生 APP 跟網頁最大的差別在很多文字是不能選取的,這時候我們也可以透過 CSS 針對這個部分進行設定。
/* 不給選 */
user-select: none;
/* 單擊一次選取 */
user-select: all;
避免長按跳出瀏覽器的 menu
webkit-touch-callout: none;
window.addEventListener("contextmenu", (e) => {
e.preventDefault();
});
如果想要自訂義,就要把瀏覽器預設的樣式行為停用,要注意一但設為 none 變成要將很多細節去用 JS 執行
touch-action: none;
越來越多裝置支援 touch 的行為,工程師在實作上其實就是針對觸控、手勢去實作相關事件。
touches
、targetTouches
為空,所以只能透過 changedTouches
來理解最後發生的事情針對不同的事件去做相關的事件綁定,注意一下 touchMove 不等於 mouseMove,User Agent 會分派順序如下:
要注意的細節如下
preventDefault()
會讓滑鼠事件 click 消失,但不 preventDefault()
又會同時觸發 touch 跟 click,這時候就需要看情境特別處理,像是 preventDefault()
後在 touch 事件的 callback 中決定是否要用程式去觸發 click5px
比較不會誤觸,可以用增加 padding 的方式user-scalable=no
) 就不會發生。<meta name="viewport" content="width=device-width,user-scalable=no">
// 判斷是否支援
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
);
}