4
头图

background

In the early stage of Japanese learning, I found that the memory of Japanese kana syllabary is not very easy, and the memory of katakana is particularly troublesome. At this time, I thought that if there is an application that can make full use of the fragmented time, it would be great to be able to practice kana syllabary at any time during lunch break or on the subway. So I searched for App Store learning, but the software in the store does not contain in-app purchases, entrained advertisements, or frequently 40M , and I did not find a satisfactory application. So I plan to write one myself, mainly introducing some of my gains in the process of developing and designing this application.

achieve

Online experience address: https://dragonir.github.io/kanaApp/

The implementation effect is as follows, the application is mainly divided into three pages:

  • Home page: Includes menu options (hiragana practice, katakana practice, mixed practice), dark mode switch button.
  • Answer page: including remaining opportunities and score display area, middle question area, bottom answer button.
  • Result page: result score display and back to home button.

The answer logic rule is 4 . The application will give feedback and score the error according to the click. After the error 10 times, the game ends and the result page is loaded. The implementation of game logic is not the main content of this article, so I won't repeat it later. The main content of this article is the introduction of the front-end knowledge involved in the development process of this mini game.

app

Dark mode ⚪⚫

As Windows 10 , MacOs , Android and other systems have successively introduced dark mode, browsers have also begun to support the detection of system theme color configuration, and more and more web applications are equipped with dark mode switching functions. In order to optimize 50-tone game, I also configured a dark style, and the effect is as follows:

dark

CSS Media query judgment dark mode

prefers-color-scheme media feature is used to detect whether the user has set the theme color of the system to light or dark. The usage syntax is as follows:

@media (prefers-color-scheme: value) {} where value has the following 3 values, of which:

  • light : Indicates that the user system supports dark mode and has been set as a light theme (default value).
  • dark : Indicates that the user system supports dark mode and has been set to dark theme.
  • no-preference : Indicates that the user system does not support dark mode or cannot know whether it is set to dark mode (obsolete).
If the result is no-preference , it is impossible to know whether the host system supports the theme color setting through this media feature, or whether the user actively set it to no preference. For privacy protection and other considerations, the user or user agent may also set it to no-preference inside the browser in some cases.

In the following example, when the system relating to a dark color .demo background color elements is #FFFFFF ; theme color when the system is light, .demo background color elements is #000000 .

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

JavaScript judge dark mode

window.matchMedia() method returns a new MediaQueryList object, which represents the parsed result of the (en-US). The returned MediaQueryList can be used to determine Document matches the media query, or monitor a document to determine whether it matches or stop matching the media query. The MediaQueryList object has attributes matches and media , and methods addListener and removeListener .

Using matchMedia as the judgment medium, you can also identify whether the system supports theme colors:

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

In addition, you can dynamically monitor the dark mode status of the system, and respond in real time according to the system dark mode switching:

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

Or detect the dark or light mode separately:

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);

In a 50-tone game, use JavaScript detect whether the dark mode is enabled, and dynamically add the css class name to automatically load the dark mode. It also provides a dark and light color switch button to manually switch themes.

Determine the dark mode in the HTML

When using picture elements on the page, you can directly judge whether the system has enabled the dark mode HTML Such as:

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

picture element allows us to display different pictures on different devices, generally used for responsiveness. HTML5 introduces the <picture> element, which can make the adjustment of image resources more flexible. <picture> element has zero or more <source> elements and one <img> element. Each <source> element matches a different device and references a different image source. If there is no match, select <img> in the src attribute of the url .

Note: The <img> element is placed <picture> element. If the browser does not support this attribute, the picture of the <img>

Offline cache

In order to be able to generate shortcuts on the desktop for quick access like native applications, and use them offline anytime, anywhere, the 50-tone game uses offline caching technology, which is a PWA application. The following is a brief description PWA offline application implementation technology.

PWA (progressing web app) , a progressive web application, is the next-generation WEB application model . A PWA application is first a web page, and with the help of App Manifest and Service Worker to achieve installation and offline functions.

Features:

  • Progressive: Suitable for all users who choose any browser, because it is developed with progressive enhancement as its core purpose.
  • Adaptive: Suitable for any model: desktop, mobile, tablet or any future device.
  • Connection independence: It can work in offline or low-quality network conditions with the help of service worker threads.
  • Offline push: Using push message notifications, we can make our apps like Native App and improve user experience.
  • Timely update: Keep up-to-date at all times under the action of the service worker thread update process.
  • Security: HTTPS by 060f83d87342a2 to prevent snooping and ensure that the content is not tampered with.

Configure page parameters

manifest.webmanifest or manifest.json in the project root directory, and write the following configuration information in the file. In this example, 50-tone game is configured as follows:

// 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"
    }
  ]
}

parameter description :

  • name : Web App is also the name of the application icon when it is saved on the desktop.
  • short_name : When name too long, short_name will be used instead of name , which is the abbreviation Web App
  • start_url Web App is loaded when the user opens the URL . URL will be relative to the path where the manifest
  • display : Specifies the display mode of the application, it has four values to choose from:

    • fullscreen : Full screen display, as much as possible to occupy all the display area.
    • standalone : Browser-related UI (such as navigation bar, toolbar, etc.) will be hidden, which looks more like a Native App .
    • minimal-ui : The display format is standalone , the browser-related UI will be minimized to a button, and different browsers have slightly different implementations.
    • browser : Generally speaking, it will be the same as the normal browser opening style.
    • It should be noted that when some system browsers do not support fullscreen , it will display the standalone , when it does not support standalone , it will display minimal-ui , and so on.
  • description : Application description.
  • icons : Specifies the desktop icon and startup page image of the application, represented by an array:

    • sizes: Icon size. By specifying the size, the system will select the most suitable icon to display in the corresponding position.
    • src: icon path. The relative path is relative to the manifest file, and an absolute path can also be used.
    • type: Icon picture type. The browser will select the picture closest to 128dp(px = dp * (dpi / 160)) icons as the splash screen image.
  • background_color : Specify the background color of the splash screen. Using the same color can achieve a smooth transition from the splash screen to the home page, and can also be used to improve the user experience when the page resources are loading.
  • theme_color : The theme color Web App This property can be used to control the color of the UI For example, the color of the status bar, the status bar in the content page, and the address bar.
Configuration information automatic generation tool: https://tomitm.github.io/appmanifest/

Configure HTML file

In index.html introduced in manifest profile and head add the following configuration information to compatible iOS system

<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 : Designated application icon, similar to the icons manifest.json file, and also supports sizes attributes for different scenarios.
  • apple-mobile-web-app-capable : similar manifest.json in display function by setting yes enter standalone mode.
  • apple-mobile-web-app-title : Specify the name of the application.
  • apple-mobile-web-app-status-bar-style : Specify the status bar style of the Default , Black , Black-translucent can be set.

Register to use Service Worker

index.html for server-worker registration:

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`);
    }
  }
}

Use serviceWorkerContainer.register() to Service worker , and add try...catch... fault-tolerant judgment to ensure normal operation Service worker Also note that only https under, navigator there will have serviceWorker object.

Service workers essentially acts as Web application, the browser, and the network (when available). Designed to create an effective offline experience, it will intercept network requests and take appropriate actions based on whether the network is available, and update resources from the server. It also provides entry to push notifications and access background synchronization API . To learn more about Service workder 🔗 at the end of the article.

sw.js root directory to define the cache information and method

// 定义缓存的key值
const cacheName = 'kana-v1';
// 定义需要缓存的文件
const staticAssets = [
  './',
  './index.html',
  './assets/css/main.css',
  './assets/js/main.js',
  './assets/images/bg.png'
  // ...
];

// 监听install事件,安装完成后,进行文件缓存
self.addEventListener('install', async e => {
  // 找到key对应的缓存并且获得可以操作的cache对象
  const cache = await caches.open(cacheName);
  // 将需要缓存的文件加进来
  await cache.addAll(staticAssets);
  return self.skipWaiting();
});

// 监听activate事件来更新缓存数据
self.addEventListener('activate', e => {
  // 保证第一次加载fetch触发
  self.clients.claim();
});

// 监听fetch事件来使用缓存数据:
self.addEventListener('fetch', async e => {
  const req = e.request;
  const url = new URL(req.url);
  if (url.origin === location.origin) {
    e.respondWith(cacheFirst(req));
  } else {
    e.respondWith(networkAndCache(req));
  }
});

async function cacheFirst(req) {
  // 判断当前请求是否需要缓存
  const cache = await caches.open(cacheName);
  const cached = await cache.match(req);
  // 有缓存就用缓存,没有就从新发请求获取
  return cached || fetch(req);
}

async function networkAndCache(req) {
  const cache = await caches.open(cacheName);
  try {
    // 缓存报错还直接从新发请求获取
    const fresh = await fetch(req);
    await cache.put(req, fresh.clone());
    return fresh;
  } catch (e) {
    const cached = await cache.match(req);
    return cached;
  }
}

In sw.js standard employed in web worker programming, since run in another global context (self) , the global context is different from window , so the use of self.addEventListener() .

Cache API is Service Worker provides an interface for the operation of the cache, based on these interfaces Promise implemented, including Cache and Cache Storage , Cache and dealing directly request, the cached Request / Response objects provide storage mechanism, CacheStorage represents Cache stored instance of an object, we can directly Use the global caches attribute to access Cache API .

** Cache related API description:

  • Cache.match(request, options) : Returns a Promise objects, resolve results is with Cache first request object is already cached match.
  • Cache.matchAll(request, options) : Returns a Promise objects, resolve results is with Cache array of all the objects that match the request composed.
  • Cache.addAll(requests) : Receive a URL array, retrieve and add the returned response object to the given Cache object.
  • Cache.delete(request, options) : Search for the Cache entry key value is request If found, delete the Cache entry, and return a resolve true is Promise ; if it is not found, return a resolve false is Promise .
  • Cache.keys(request, options) : Returns a Promise objects, resolve results is Cache objects key array value thereof.
Note: request.clone() and response.clone() are used because request and response are one stream and can only be consumed once. The cache has been consumed once, and the HTTP request will be consumed again. At this time, use the clone method to clone the request.

At this point, when the installed Service Worker page is opened, the Service Worker script update will be triggered. When the timestamp written in the Service Worker database of the last script update and this update exceed 24 hours Service Worker script update will be triggered. When the sw.js file changes, it will trigger the Service Worker script update. The update process is similar to the installation, except that it will not enter the active state immediately after the update installation is successful. The updated Service Worker will coexist with the original Service Worker and run its install . Once the new Service Worker installed successfully, it will enter the wait state. Wait for the old version of Service Worker enter/thread termination.

For more Server Worker please view the link at the end of the article 🔗

achieves the effect :

PC terminal 🖥️ : Windows , after opening the application for the first time in the browser, there will be an installation prompt, click the installation icon to install, the desktop and start menu will generate application shortcuts, click the shortcut to open the application.

windows

Mac 💻 : The above chromiumn kernel browser ( chrome , opera , new version edge ) is similar. After installation, a shortcut launchpad

mac

mobile terminal 📱 : iPhone . Choose to save to the desktop in the browser to generate a desktop icon, click the icon to open the offline application.

iphone

Cherry Blossom 🌸

petals

In order to enhance the visual effect and interest, the effect of falling 🌸 The falling effect animation mainly uses the Element.animate() method.

Element of animate() method is a newly created Animation convenient method of applying it to the elements, and then run the animation. It will return a newly created Animation object instance. Multiple animation effects can be applied to an element. You can get a list of these animation effects by calling this function: Element.getAnimations() .

basic grammar :

var animation = element.animate(keyframes, options);

parameter :

  • keyframes : key frame. An object represents a collection of key frames.
  • options : An optional integer representing the duration of the animation (in milliseconds), or an object containing one or more time attributes:

    • id : Optional, can be used as a uniquely identified attribute animate() DOMString )
    • delay : Optional, the delay in milliseconds for the start time, the default value is 0 .
    • direction : Optional, the direction of motion of the animation. Moves forward normal , run back reverse , after each iteration switching direction alternate , running backwards and the switching direction after each iteration alternate-reverse . The default is normal .
    • duration : Optional, the number of milliseconds to complete each iteration of the animation, the default value is 0 .
    • easing : Optional, the frequency of animation changes over time. Accepted preset values include linear , ease , ease-in , ease-out , ease-in-out and a custom value cubic-bezier , such as cubic-bezier(0.42, 0, 0.58, 1) . The default value is linear .
    • endDelay : Optional, a delay after the end of the animation, the default value is 0 .
    • fill : Optional. Define the timing of the influence of the animation effect on the element. backwards affects the element before the animation starts, forwards affects the element after the animation is completed, and both both. The default value is none .
    • iterationStart : Optional, describe at which point in the iteration the animation should start. For example, 0.5 means that it starts midway through the first iteration, and after setting this value, an 2 iterations will end midway through the third iteration. The default is 0.0 .
    • iterations : Optional, the number of times the animation should be repeated. The default is 1 , or you can take infinity to make it repeat when the element exists.

The following code embodied in the present embodiment, HTML which has several .petal elements, then JavaScript acquired all .petal element to random animation, css addition of both rotation and deformation of the two kinds of animation, to achieve the effect of falling cherry blossom petals.

<div id="petals_container">
  <div class="petal"></div>
  <!-- ... -->
  <div class="petal"></div>
</div>
var petalPlayers = [];
function animatePetals() {
  var petals = document.querySelectorAll('.petal');
  if (!petals[0].animate) {
    var petalsContainer = document.getElementById('petals_container');
    return false;
  }
  for (var i = 0, len = petals.length; i < len; ++i) {
    var petal = petals[i];
    petal.innerHTML = '<div class="rotate"><img src="petal.png" class="askew"></div>';
    var scale = Math.random() * .6 + .2;
    var player = petal.animate([{
        transform: 'translate3d(' + (i / len * 100) + 'vw,0,0) scale(' + scale + ')',
        opacity: scale
      },
      {
        transform: 'translate3d(' + (i / len * 100 + 10) + 'vw,150vh,0) scale(' + scale + ')',
        opacity: 1
      }
    ], {
      duration: Math.random() * 90000 + 8000,
      iterations: Infinity,
      delay: -(Math.random() * 5000)
    });
    petalPlayers.push(player);
  }
}
animatePetals();
.petal .rotate {
  animation: driftyRotate 1s infinite both ease-in-out;
  perspective: 1000;
}
.petal .askew {
  transform: skewY(10deg);
  display: block;
  animation: drifty 1s infinite alternate both ease-in-out;
  perspective: 1000;
}
.petal:nth-of-type(7n) .askew {
  animation-delay: -.6s;
  animation-duration: 2.25s;
}
.petal:nth-of-type(7n + 1) .askew {
  animation-delay: -.879s;
  animation-duration: 3.5s;
}
/* ... */
.petal:nth-of-type(9n) .rotate {
  animation-duration: 2s;
}
.petal:nth-of-type(9n + 1) .rotate {
  animation-duration: 2.3s;
}
/* ... */
@keyframes drifty {
  0% {
    transform: skewY(10deg) translate3d(-250%, 0, 0);
    display: block;
  }
  100% {
    transform: skewY(-12deg) translate3d(250%, 0, 0);
    display: block;
  }
}
@keyframes driftyRotate {
  0% {
    transform: rotateX(0);
    display: block;
  }
  100% {
    transform: rotateX(359deg);
    display: block;
  }
}
The complete code can be viewed at the following link 🔗 .

CSS Determine the horizontal screen of the mobile phone

In this example, the 50-tone mini game application is developed for the mobile terminal and has not been adapted to the style of the PC terminal, so a horizontal screen guide page can be added to prompt the user to use the vertical screen. In CSS to determine whether the mobile device is in landscape mode, aspect-ratio needs to be used for media query, which is determined by testing the aspect ratio of viewport

aspect-ratio aspect ratio attribute is specified as the <ratio> value to represent the viewport aspect ratio. It is a range, you can use min-aspect-ratio and max-aspect-ratio to query the minimum and maximum values respectively. The basic syntax is as follows:

/* 最小宽高比 */
@media (min-aspect-ratio: 8/5) {
  // ...
}
/* 最大宽高比 */
@media (max-aspect-ratio: 3/2) {
  // ...
}
/* 明确的宽高比, 放在最下部防止同时满足条件时的覆盖 */
@media (aspect-ratio: 1/1) {
  // ...
}

The specific implementation in the application is to add a .mod_orient_layer guide layer and hide it, and display it when the minimum aspect ratio is reached:

<div class="mod_orient_layer"></div>
.mod_orient_layer {
  display: none;
  position: fixed;
  height: 100%;
  width: 100%;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  z-index: 999;
  background: #FFFFFF url('landscape.png') no-repeat center;
  background-size: auto 100%;
}
@media screen and (min-aspect-ratio: 13/8) {
  .mod_orient_layer {
    display: block;
  }
}

Realize the effect:

landscape

compatibility

The following is the compatibility view of several attributes involved in this article. In actual production projects, you need to pay attention to compatibility and adaptation.

caniuse

Photoshop skills

logo design

logo mainly composed of two elements. It is composed of a ⛩️ icon and the Japanese hiragana , both of which are classic Japanese elements. At the same time, is stretched and gradual to form ⛩️ , which makes letters and graphics cleverly connected to make the picture harmonious. The logo background color uses the application theme background color to establish a connection with the page invisibly, forming a full-link unified style standard. (Can't edit it anymore...😂

logo

original model of dribbble comes from 060f83d8735aa7: 160f83d8735aac https://dribbble.com

External links and reference materials

Blog address: 160f83d8735c64 https://segmentfault.com/a/1190000040383870

dragonir
1.8k 声望3.9k 粉丝

Accepted ✔