kana

背景

在日语学习初期阶段,我发现日语五十音的记忆并不是很容易的,片假名的记忆尤其令人费神。这时我想如果有一个应用可以充分利用碎片时间,在午休或地铁上随时可以练习五十音该多好。于是搜索 App Store,确实有很多五十音学习的小软件,但是商店的软件不是含有内购、夹带广告、就是动辄 40M 以上,没找到一个自己满意的应用。于是打算自己写一个,主要介绍自己在开发设计该应用过程中的一些收获。

实现

在线体验地址 https://dragonir.github.io/kanaApp/

实现效果如下,该应用主要分为三个页面:

  • 首页:包括菜单选项(平假名练习、片假名练习、混合练习)、深色模式切换按钮。
  • 答题页:包括剩余机会和分数显示区、中间出题区、底部答题按钮。
  • 结果页:结果分数显示和返回首页按钮。

答题逻辑规则是从给出的 4 个答案按钮中选出题目展示区的那个单词对应正确的那个选项,应用根据点击给出错对反馈并进行记分,错误 10 次后游戏结束,加载结果页。游戏逻辑实现不是本文的主要内容,因此后面不再赘述。本文后续主要内容是此次小游戏开发流程涉及到的前端知识的介绍。

app

深色模式 ⚪⚫

随着 Windows 10MacOsAndroid 等系统陆续推出深色模式,浏览器也开始支持检测系统主题色配置,越来越多的网页应用都配置了深色模式切换功能。为了优化 50音小游戏 的视觉体验,我也配置了深色样式,实现效果如下:

dark

CSS 媒体查询判断深色模式

prefers-color-scheme 媒体特性用于检测用户是否有将系统的主题色设置为亮色或者暗色。使用语法如下所示:

@media (prefers-color-scheme: value) {} 其中 value 有以下 3 种值,其中:

  • light:表示用户系统支持深色模式,并且已设置为浅色主题(默认值)。
  • dark:表示用户系统支持深色模式,并且已设置为深色主题。
  • no-preference:表示用户系统不支持深色模式或无法得知是否设置为深色模式(已废弃)。

若结果为 no-preference,无法通过此媒体特性获知宿主系统是否支持设置主题色,或者用户是否主动将其设置为无偏好。出于隐私保护等方面的考虑,用户或用户代理也可能在一些情况下在浏览器内部将其设置为 no-preference

下面例子中,当系统主题色为深色时 .demo 元素的背景色为 #FFFFFF;当系统主题色为浅色时,.demo 元素的背景色为 #000000

@media (prefers-color-scheme: dark) {
  .demo { background:  #FFFFFF; }
}
@media (prefers-color-scheme: light) {
  .demo { background: #000000; }
}

JavaScript 判断深色模式

window.matchMedia() 方法返回一个新的 MediaQueryList 对象,表示指定的媒体查询 (en-US)字符串 解析后的结果。返回的 MediaQueryList 可被用于判定 Document 是否匹配媒体查询,或者监控一个 document 来判定它匹配了或者停止匹配了此媒体查询。其中 MediaQueryList 对象具有属性 matchesmedia,方法 addListenerremoveListener

使用 matchMedia 作为判断媒介,也可以识别系统是否支持主题色:

if (window.matchMedia('(prefers-color-scheme)').media === 'not all') {
  // 浏览器不支持主题色设置
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches){
  // 深色模式
} else {
  // 浅色模式
}

另外还可以动态监听系统深色模式的状态,根据系统深色模式的切换做出实时响应:

window.matchMedia('(prefers-color-scheme: dark)').addListener(e => {
  if (e.matches) {
    // 开启深色模式
  } else {
    // 关闭深色模式
  }
});

或者单独检测深色或浅色模式:

const listeners = {
  dark: (mediaQueryList) => {
    if (mediaQueryList.matches) {
      // 开启深色模式
    }
  },
  light: (mediaQueryList) => {
    if (mediaQueryList.matches) {
      // 开启浅色模式
    }
  }
};
window.matchMedia('(prefers-color-scheme: dark)').addListener(listeners.dark);
window.matchMedia('(prefers-color-scheme: light)').addListener(listeners.light);

在50音小游戏中,就是使用 JavaScript 检测系统是否开启深色模式,动态添加 css 类名来自动加载深色模式,同时也提供深浅色切换按钮,可以手动切换主题。

HTML 元素中判断深色模式

页面使用图片元素时,可以直接在 HTML 中判断系统是否开启深色模式。如:

<picture>
  <source srcset="dark.png" media="(prefers-color-scheme: dark)">
  <img src="light.png">
</picture>

picture 元素允许我们在不同的设备上显示不同的图片,一般用于响应式。HTML5 引入了 <picture> 元素,该元素可以让图片资源的调整更加灵活。<picture> 元素零或多个 <source> 元素和一个 <img> 元素,每个 <source> 元素匹配不同的设备并引用不同的图像源,如果没有匹配的,就选择 <img> 元素的 src 属性中的 url

注意: <img> 元素是放在最后一个 <picture> 元素之后,如果浏览器不支持该属性则显示 <img> 元素的的图片。

离线缓存

为了能够像原生应用一样可以在桌面生成快捷方式快速访问,随时随地离线使用,50音小游戏 使用了离线缓存技术,它是一个 PWA应用 。下面内容是 PWA离线应用 实现技术的简要描述。

PWA (progressing web app),渐进式网页应用程序,是 下一代WEB应用模型。一个 PWA 应用首先是一个网页, 并借助于 App ManifestService Worker 来实现安装和离线等功能。

特点:

  • 渐进式:适用于选用任何浏览器的所有用户,因为它是以渐进式增强作为核心宗旨来开发的。
  • 自适应:适合任何机型:桌面设备、移动设备、平板电脑或任何未来设备。
  • 连接无关性:能够借助于服务工作线程在离线或低质量网络状况下工作。
  • 离线推送:使用推送消息通知,能够让我们的应用像 Native App 一样,提升用户体验。
  • 及时更新:在服务工作线程更新进程的作用下时刻保持最新状态。
  • 安全性:通过 HTTPS 提供,以防止窥探和确保内容不被篡改。

配置页面参数

在项目根目录添加文件 manifest.webmanifestmanifest.json 文件,并在文件内写入如下配置信息,本例中 50音小游戏 的页面参数信息配置如下:

// manifest.webmainifest
{
  "name": "かなゲーム",
  "short_name": "かなゲーム",
  "start_url": "index.html",
  "display": "standalone",
  "background_color": "#fff",
  "description": "かなゲーム",
  "icons": [
    {
      "src": "assets/images/icon-64x64.png",
      "sizes": "64x64",
      "type": "image/png"
    },
    {
      "src": "assets/images/icon-256x256.png",
      "sizes": "256x256",
      "type": "image/png"
    }
  ]
}

参数说明

  • nameWeb App 的名称,也是保存到桌面上时应用图标的名称。
  • short_namename 过长时,将会使用 short_name 代替 name 显示,是 Web App 的简称。
  • start_url:指定了用户打开该 Web App 时加载 URLURL 会相对于 manifest 文件所在路径。
  • display:指定了应用的显示模式,它有四个值可以选择:
    • fullscreen:全屏显示,会尽可能将所有的显示区域都占满。
    • standalone:浏览器相关 UI(如导航栏、工具栏等)将被隐藏,看起来更像一个 Native App
    • minimal-ui:显示形式与 standalone 类似,浏览器相关 UI 会最小化为一个按钮,不同浏览器在实现上略有不同。
    • browser:一般来说,会和正常使用浏览器打开样式一致。
    • 需要说明的是,当一些系统的浏览器不支持 fullscreen 时将会显示成 standalone 效果,当不支持 standalone 时,将会显示成 minimal-ui 的效果,以此类推。
  • description:应用描述。
  • icons:指定了应用的桌面图标和启动页图像,用数组表示:
    • sizes:图标大小。通过指定大小,系统会选取最合适的图标展示在相应位置上。
    • src:图标路径。相对路径是相对于 manifest 文件,也可以使用绝对路径。
    • type:图标图片类型。 浏览器会从 icons 中选择最接近 128dp(px = dp * (dpi / 160)) 的图片作为启动画面图像。
  • background_color:指定启动画面的背景颜色,采用相同颜色可以实现从启动画面到首页的平稳过渡,也可以用来改善页面资源正在加载时的用户体验。
  • theme_color:指定了Web App 的主题颜色。可以通过该属性来控制浏览器 UI 的颜色。比如状态栏、内容页中状态栏、地址栏的颜色。

配置信息自动生成工具:https://tomitm.github.io/appmanifest/

配置 HTML 文件

index.html 中引入 manifest 配置文件,并在 head 中添加以下配置信息以兼容 iOS系统

<meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no,viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes"  />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="かなゲーム">
<link rel="stylesheet" type="text/css" href="./assets/css/main.css">
<link rel="stylesheet" type="text/css" href="./assets/css/dark.css">
<link rel="stylesheet" type="text/css" href="./assets/css/petals.css">
<link rel="shortcut icon" href="./assets/images/icon-256x256.png">
<link rel="apple-touch-icon" href="./assets/images/icon-256x256.png"/>
<link rel="apple-touch-icon-precomposed" href="./assets/images/icon-256x256.png">
<link rel="Bookmark" href="./assets/images/icon-256x256.png" />
<link rel="manifest" href="./manifest.webmanifest">
<title>かなゲーム</title>
  • apple-touch-icon: 指定应用图标,类似与 manifest.json 文件的 icons 配置,也是支持 sizes 属性,来供不同场景的选择。
  • apple-mobile-web-app-capable:类似于 manifest.json 中的 display 的功能,通过设置为 yes 可以进入 standalone 模式。
  • apple-mobile-web-app-title:指定应用的名称。
  • apple-mobile-web-app-status-bar-style:指定iOS移动设备的 状态栏status bar 的样式,有 DefaultBlackBlack-translucent 可以设置。

注册使用 Service Worker

index.html 中添加如下代码进行server-worker注册:

window.addEventListener('load', () => {
  registerSW();
});
async function registerSW() {
  if ('serviceWorker' in navigator) {
    try {
      await navigator.serviceWorker.register('./sw.js');
    } catch (e) {
      console.log(`SW registration failed`);
    }
  }
}

使用 serviceWorkerContainer.register() 进行 Service worker 注册,同时添加 try...catch... 容错判断,以保证在不支持 Service worker 的情况下正常运行。另外需要注意的是只有在 https 下,navigator 里才会有 serviceWorker 对象。

Service workers 本质上充当 Web 应用程序、浏览器与网络(可用时)之间的代理服务器。旨在创建有效的离线体验,它会拦截网络请求并根据网络是否可用采取来适当的动作、更新来自服务器的的资源。它还提供入口以推送通知和访问后台同步 API。了解更多 Service workder 知识可以访问文章末尾链接

内容来源于网络如有侵权请私信删除