background

In the early stage of learning Japanese, I found it not easy to memorize fifty tones of Japanese, especially Katakana. Then I thought, what if there was an app that could make the most of all that time? I could practice fifty notes on my lunch break or on the subway. So search the App Store, there are indeed a lot of 50 sound learning software, but the Store software is not containing internal purchase, with advertising, is often more than 40M, did not find a satisfactory application. So I decided to write one of my own, which mainly introduces what I learned during the process of developing and designing this application.

implementation

Online Experience Address:
https://dragonir.github.io/ka…

The effect is as follows. The application is mainly divided into three pages:

  • Home page: including menu options (hiragana practice, katakana practice, mixed practice), dark mode switch button.
  • Answer page: including the remaining opportunity and score display area, the middle answer area, the bottom answer button.
  • Results page: results score display and back to the home button.

The logical rule of answering questions is to select the correct word in the question display area from the four answer buttons, and the application will give feedback and score points according to the error. After 10 mistakes, the game will end and the result page will be loaded. Game logic implementation is not the focus of this article, so I won’t go into it later. The following main content of this article is the introduction of the front-end knowledge involved in the development process of the small game.

Dark mode⚪ ⚫

As Windows 10, MacOS, Android and other systems have successively launched dark mode, browsers have also begun to support detection of system theme color configuration, and more and more web applications are configured with dark mode switching function. In order to optimize the visual experience of the 50-tone game, I also configured the dark style to achieve the following effects:

CSSMedia queries judge dark patterns

The paint-color-scheme media feature detects whether the user has set the system’s theme color to light or dark. The syntax is as follows:

@media (pain-color-scheme: value) {} where value has the following three values, including:

  • light: Indicates that the user system supports dark mode and has set the theme to light color (the default).
  • dark: Indicates that the user system supports dark mode and has set the theme to dark color.
  • no-preference: Indicates that the user’s system does not support dark mode or is unable to tell if it is set to dark mode (deprecated).

If the result is
no-preferenceThere is no way to know from this media feature whether the host system supports setting the theme color or whether the user has actively set it to unpreferred. For privacy reasons, users or user agents may also set it to inside the browser in some cases
no-preference.

In the following example, the background color of the. Demo element is #FFFFFF when the system theme color is dark; When the system theme color is light, the background color of the. Demo element is #000000.

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

JavaScriptIdentify Dark Patterns

The window.matchMedia() method returns a new MediaQueryList object that represents the result of the parsing of the specified media query (EN-US) string. The returned mediaQueryList can be used to determine whether the Document matches the media query, or to monitor a Document to determine whether it matches or stops matching the media query. The mediaQueryList object has properties matches and media, and methods addListener and removeListener.

Using Matchmedia as the judging medium, you can also identify whether the system supports theme color:

If (window.matchmedia ('(paint-color scheme)').media === 'not all') {// Browsers do not support theme color Settings} if (window.matchmedia ('(paint-color scheme)').media === 'not all') {// Browsers do not support theme color Settings} (window.matchmedia ('(paint-scheme: dark)').matches){// color mode} else {// color mode}

In addition, it can dynamically monitor the state of the dark mode of the system and make real-time response according to the switch of the dark mode of the system:

Window.matchmedia ('(paint-scheme: dark)').addListener(e => {if (e.matches) {// turn on dark mode} else {// turn off dark mode});

Or detect dark or light patterns separately:

Const Listeners = {dark: (mediaQueryList.matches) = bb0 {if (mediaQueryList.matches) {// start dark}}, (mediaQueryList) = bb0 {if (mediaQueryList.matches) {// turn on color mode}}; window.matchMedia('(prefers-color-scheme: dark)').addListener(listeners.dark); window.matchMedia('(prefers-color-scheme: light)').addListener(listeners.light);

In the 50 tone game, it is to use JavaScript to detect whether the dark mode is turned on, dynamically add CSS class name to automatically load the dark mode, and also provide a button to switch the dark color, you can manually switch the theme.

HTMLThe element determines the dark pattern

When a page uses image elements, you can tell directly in the HTML if the system is in dark mode. Such as:

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

The picture element allows us to display different images on different devices, and is generally used in reactive form. HTML5 introduces the element, which allows for more flexibility in adjusting image resources. element zero or moreelements and one
element. Eachelement matches a different device and references a different image source. If none matches, the URL in the SRC attribute of the
element is selected.

Note:
<img>The element is placed last
<picture>Element, if the browser does not support the attribute
<img>The image of the element.

Offline caching

In order to be able to generate shortcuts on the desktop like a native application to quickly access, anytime and anywhere to use offline, 50 Tone game uses offline caching technology, it is a PWA application. The following is a brief description of PWA offline application implementation techniques.

PWA (progressing web app), a progressive web application, yes
Next generation Web application model. a
PWAAn application is first a web page, and with the help of
App Manifest
Service WorkerTo achieve installation and offline functions.

Features:

  • Progressive: This works for all users of any browser, as it was developed with progressive enhancement as its core tenet.
  • Adaptable: Fits any model: desktop, mobile, tablet or any future device.
  • Connection independence: The ability to work offline or in low-quality network conditions with the help of a service worker thread.
  • Offline Push: Using Push notifications, we can make our app look likeNative AppAgain, improve the user experience.
  • Updated: Keep up to date with the service worker’s update process.
  • Security: PassHTTPSProvide to prevent prying and ensure content is not tampered with.

Configure page parameters

Add a file MANIFEST.WEBMANIFEST or MANIFEST.JSON file in the root directory of the project, and write the following configuration information in the file. In this example, the page parameter information configuration of the 50-tone game is as follows:

/ / the 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 AppIs also the name of the icon to be applied when saved to the desktop.
  • short_name:nameIt will be used if it is too longshort_nameInstead ofnameDisplay,Web AppThe abbreviation.
  • start_url: Specifies that the user opens thisWeb AppWhen loadingURL.URLWill be relative to themanifestThe path to the file.
  • Display: Specifies the display mode to apply. It has four values to choose from:

    • fullscreen: Full screen display, will try to fill up all the display area.
    • standalone: Browser specificUI(like navigation bars, toolbars, etc.) will be hidden and look more like oneNative App.
    • minimal-ui: Display form andstandaloneSimilarly, browser-dependentUIThis is minimized to a single button, which is implemented slightly differently by different browsers.
    • browser: In general, it will open in the same style as normal browser.
    • It should be noted that when some systems are not supported by the browserfullscreenWill be displayed asstandaloneEffects when not supportedstandaloneWill be displayed asminimal-uiAnd so on.
  • description: Application description.
  • Icons: Specifies the desktop icon and startup page image of the application and is represented by an array:

    • SIZES: Icon size. By specifying the size, the system will select the most appropriate icon to display on the corresponding position.
    • SRC: Icon path. Relative path is relative to thetamanifestFile, you can also use an absolute path.
    • Type: Icon image type. The browser will go fromiconsChoose the closest of the two128dp(px = dp * (dpi / 160))The image of the startup screen is used as the image.
  • background_color: Specifies the background color of the startup screen. The same color can be used for a smooth transition from the startup screen to the home page, and can also be used to improve the user experience while page resources are loading.
  • theme_color: specifies theWeb AppThe theme color of. This property allows you to control the browserUIThe color. For example, the color of the status bar, the status bar and the address bar in the content page.

Automatic configuration information generation tool:
https://tomitm.github.io/appm…

configurationHTMLfile

Introduce the MANIFEST configuration file in index.html and add the following configuration information in the HEAD to be compatible with 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: Specifies the application icon, similar tomanifest.jsonOf the fileiconsConfiguration, also supportedsizesProperty for different scenarios to choose from.
  • apple-mobile-web-app-capable: similar tomanifest.jsonIn thedisplayThe function is set toyesCan enter thestandaloneMode.
  • apple-mobile-web-app-title: Specifies the name of the application.
  • apple-mobile-web-app-status-bar-style: Specifies the iOS mobile deviceStatus bar, there isDefault.Black.Black-translucentIt can be set.

Registered to useService Worker

inindex.htmlAdd the following code to register the 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`); }}}

Using serviceWorkerContainer. The register () for the Service worker registration, at the same time add a try… catch… Fault tolerant to ensure that the Service worker is not supported. Another thing to note is that the ServiceWorker object is only in the Navigator under HTTPS.

Service workersEssentially acting as
WebProxy server between the application, browser, and network (when available). Designed to create an effective offline experience, it intercepts network requests and takes appropriate action to update resources from the server based on whether the network is available. It also provides entry for push notifications and access to background synchronization
API. To learn more
Service workderKnowledge can access the link at the end of the article
🔗.

Add in the root directorysw.js, and define the cache information and methods

Const cacheName = 'Kana-v1 '; // Define files to be cached const StaticasSets = ['./', './index.html', './assets/ CSS /main.css', './assets/js/main.js', './assets/images/bg.png' // ... ] ; // Listen for the install event. When the install is complete, Self. AddEventListener ('install', Async e => {// const cache = await caches. Open (cacheName); // Add "await Cache.addAll (StaticasSets)" to "await Cache.addAll"; return self.skipWaiting(); }); // Listen for the Activate event to update the cached data self.addEventListener(' Activate ', e => {// Ensure that the first fetch triggers self.client.claim (); }); // Listen to the fetch event to use the cached data: 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); / / a cache with cache, there have been new send request for return cached | | the fetch (the req); } async function networkAndCache(req) { const cache = await caches.open(cacheName); Const fresh = await fetch(req); const fresh = await fetch(req); await cache.put(req, fresh.clone()); return fresh; } catch (e) { const cached = await cache.match(req); return cached; }}

The standard Web Worker programming method used in SW.js uses self.addEventListener() because it runs in another global context (self), which is different from the window.

The Cache API is the interface provided by the Service Worker to manipulate the Cache. These interfaces are based on the Promise implementation, including the Cache and Cache Storage. The Cache directly deals with the request. CacheStorage provides a storage mechanism for cached Request/Response object pairs. CacheStorage represents a storage instance of the Cache object. We can access the Cache API directly using the global caches property.

** Cache related API specification:

  • Cache.match(request, options): return aPromiseObject,resolveThe result is the followingCacheObject matches the first cached request.
  • Cache.matchAll(request, options): return aPromiseObject,resolveThe result is the followingCacheObject is an array of all requests matched by the.
  • Cache.addAll(requests): receive aURLArray, retrieved and returnedresponseObject is added to the givenCacheObject.
  • Cache.delete(request, options)Search:keyA value ofrequestCacheEntries. If found, it is deletedCacheEntry, and returns oneresolvetruePromiseObject; If not found, one is returnedresolvefalsePromiseObject.
  • Cache.keys(request, options): return aPromiseObject,resolveAs a result of theCacheobjectkeyAn array of values.

Note: the use of
request.clone()
response.clone()because
request
responseIt’s a stream. It can only be consumed once. Cache has been consumed once, again initiated
HTTPThe request needs to be consumed once more, so use it at this time
cloneMethod clones the request.

At this point, when the installed Service Worker page is opened, the Service Worker script update is triggered. A Service Worker script update is triggered when the timestamp of the last script update written to the Service Worker database has been more than 24 hours since the current update. When the sw.js file changes, the Service Worker script updates are triggered. The update process is similar to the installation process, but it will not enter the active state immediately after the successful update installation. The updated Service Worker will co-exist with the original Service Worker and run its install. Once the new Service Worker is installed, it enters a wait state where it waits for the old Service Worker to enter/terminate the thread.

More and more
Server WorkerFor more information, see the link at the end of this article
🔗

Realized effect:

On Windows, there will be an installation prompt after opening the application in the browser for the first time. Click the installation icon to install the application. Shortcuts to the application will be generated in the desktop and Start menu.

Mac 💻 : The browsers above the Chromiumn kernel (Chrome, Opera, new Edge) are similar. Shortcuts are generated in the Launchpad after installation.

Mobile 📱 : iPhone. Select Save to Desktop in the browser to generate a desktop icon. Click the icon to open the offline application.

Cherry blossom falling animation🌸

In order to enhance the visual effect and interest, we added the effect of cherry blossom 🌸 falling on the page. The falling-effect animation mainly uses the element.animate () method.

Element’s animate() method is a handy way to create a new Animation, apply it to the Element, and then run the Animation. This will return an instance of the newly created Animation object. Multiple animation effects can be applied to a single element. You can get a list of these animations by calling this function: Element.getanimations ().

Basic grammar:

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

Parameters:

  • keyframes: Keyframe. An object that represents a collection of keyframes.
  • Options: Optionally an int number (in milliseconds) representing the duration of the animation, or an object containing one or more time attributes:

    • id: Optional inanimate()An attribute that can be uniquely identified in: A string that refers to the animation (DOMString
    • delay: Optional, number of milliseconds delay to start time, default is0.
    • direction: Optional, motion direction of the animation. Running forwardnormal, running backwardreverseSwitch directions after each iterationalternate, running backwards and switching directions after each iterationalternate-reverse. The default isnormal.
    • duration: Optional, the number of milliseconds in which the animation completes each iteration. Default is0.
    • easing: Optional, the frequency of the animation changes over time. Accepts default values includinglinear,ease,ease-in,ease-out,ease-in-outAnd a custom valuecubic-bezier, such asCubic - the bezier (0.42, 0, 0.58, 1). The default value islinear.
    • endDelay: Optional, an animation after the end of the delay, the default value is0.
    • fill: Optional. Defines when the animation effect affects the element.backwardsAffect the element before the animation starts,forwardsWhen the animation is finished, it will affect the elements,bothBoth. The default value isnone.
    • iterationStart: Optional, describing at which point in the iteration the animation should start. For example,0.5Indicates that, after starting midway through the first iteration and setting this value, has2The animation for the next iteration will end halfway through the third iteration. The default is0.0.
    • iterations: Optional, number of times the animation should be repeated. The default is1I can also takeinfinityIs so that it repeats in the presence of the element.

The following code is the specific implementation in this example. There are several.petal elements in HTML, and then all.petal elements are obtained from JavaScript to add random animation. Two kinds of rotation and deformation animation are added in CSS to achieve the effect of falling cherry 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.25 s; } .petal:nth-of-type(7n + 1) .askew { animation-delay: -.879s; Animation - duration: 3.5 s; } / *... */ .petal:nth-of-type(9n) .rotate { animation-duration: 2s; }. Petal :nth-of-type(10n + 1). Rotate {animation-duration: 2.5s; } / *... */ @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; }}

See the link below for the full code
🔗.

CSSDetermine the landscape of the phone

This example of 50-tone small game application is developed for mobile terminal, without the style adaptation of PC terminal, so you can add a horizontal screen guide page to prompt users to use vertical screen. To determine whether the mobile device is in landscape state in CSS, aspect ratio is needed to conduct media query, and the aspect ratio of viewport is tested to determine.

The aspect-ratio aspect ratio property is specified as the

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

/ * minimum width to height ratio * / @ media (min - aspect - thewire: 8/5) {/ /... } / * * / width to height ratio biggest @ media (Max - aspect - thewire: 3/2) {/ /... */ @media (aspect-ratio: 1/1) {//... }

In the application, you can add a.mod_orient_layer bootstrap layer and hide it, showing 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; }}

Realized effect:

compatibility

The following is a compatibility view of several of the properties covered in this article that you need to pay attention to in a production project.

Photoshop skills

Logo design

Logo is mainly composed of two elements: a ⛩️ icon and a Japanese Hiragana bar. Both are classic Japanese elements. At the same time, the logo is stretched and gradually changed to form a shadow similar to “, so that the letters and graphics are cleverly connected together to make the picture harmonious. LOGO background color uses the application theme background color, and the page in the invisible connection, forming a full link unified style standard. (Can’t make this up… 😂

Torii’s original model comes from
dribbble:
https://dribbble.com

External links and references

  • Cherry blossoms scattered animation full version https://codepen.io/dragonir/f…
  • Dark Mode Support in WebKit https://webkit.org/blog/8840/…
  • PWA technology theory + practice all parse https://www.cnblogs.com/yangy…
  • H5 PWA technology https://zhuanlan.zhihu.com/p/…
  • aspect-ratio https://developer.mozilla.org…
  • Service Worker https://developer.mozilla.org…
  • Element.animate() https://developer.mozilla.org…

Author: Dragonir
https://segmentfault.com/a/11…