Progressive Web App 针对应用操作介面优化操作体验 (27)

网页的外观和操作本质上还是和原生的有差异但可以透过配置来让体验更接近。

  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 来使用系统原生的字型。

font-family: system-ui;

配合系统主题

当系统开启暗黑模式後,PWA 也可以透过程序上的撰写配合更改显示设定,另外对於 AMOLED 的萤幕来说,暗黑模式也可以更省电。

程序上主要透过 Media Queries Level 5 支援的 prefers-color-scheme 可以判断出使用者目前使用的系统主题。

  • light: 明亮模式 (深色字体、浅色背景).
  • dark: 暗黑模式 (浅色字体、深色背景).
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 的颜色 */
}

事件操作

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

文字选取

对於有触控功能原生 APP 跟网页最大的差别在很多文字是不能选取的,这时候我们也可以透过 CSS 针对这个部分进行设定。

/* 不给选 */
user-select: none;
/* 单击一次选取 */
user-select: all;

contextmenu

避免长按跳出浏览器的 menu

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

Touch 效果

如果想要自订义,就要把浏览器预设的样式行为停用,要注意一但设为 none 变成要将很多细节去用 JS 执行

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">

// 判断是否支援
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
  );
}

<<:  Day 28 - 设籍有关涉及射击的射击游戏

>>:  [Day 26] Reactive Programming - Spring WebFlux(R2DBC Repositories)

深入浅出 Computed

Vue.js 的自我介绍中,只有说自己接近 MVVM 但不是严格的 MVVM。 我觉得只要会「自动更...

Perspective 3D 成像

大家好,我是西瓜,你现在看到的是 2021 iThome 铁人赛『如何在网页中绘制 3D 场景?从 ...

分享一个好用的进销存软件

分享一个好用的进销存软件 eztool , 用这软件处理客人订单有一些时间了, 非常的方便又好用, ...

Day21-JDK可视化监控工具:jconsole(一)

Jconsole介绍 Jconsole是一个JMX相容的监视工具。它使用Java虚拟机器的JMX机制...

IOS Swift 请问你哪位 ? Protocol<协定>自我介绍。

前言 昨天问了面试网页前端的问题,收到板上前辈的许多回应真的是受宠若惊,让我感受到IT人的刚性温暖非...