React を利用してオフラインでも簡単な画像編集をできる1画面ウェブアプリを作ってみました。
作ったものは揺れる※※画像ジェネレータです。 ネーミングはまあ微妙かな…(汗
それを作るなかで調べたことなどをまとめました。
基本の基
まずは、今回利用したツールについて。
利用したのは create-react-app です。
Set up a modern web app by running one command.
とあるようにコマンド一発で
- React を利用するのに最適な環境を構築
- PWA に簡単に対応できる Service Worker などの実装
- 開発用サーバー&ビルド環境
- ユニットテスト
がそろったプロジェクトが設定要らずで作成できます。
UI 周り
UI は React Bootstrap と…
ダークモードに対応するためにカスタマイズされたテーマの bootstrap-dark を…
利用しました。 ダークモードについてはこの後に記載があります。
アイコンは、React から利用できる Font Awesome である react-fontawesome を利用しています。
あとは、
などを、このアプリに固有の UI を実装するため利用しています。
アプリ固有処理
今回のアプリは、
- 画像をアップロード
- 画像を加工
- 出来上がった画像をダウンロード
という感じに順次進んでいく操作が主となります。
それらの処理の実装についてさらっと記載しておきます。
画像をアップロード
ここでは、単なる画像のアップロードと URL を利用したとえば Public Domain な画像などを利用した加工をできるようにしてあります。
このうち画像のアップロード(といいつつサーバーにはアップロードしない)は、 react-dropzone を使ってサクッと実装してあります。
また、URL を指定しての画像編集は、 CORS などによりブロックされるので cors-anywhere というプロキシを Heroku にデプロイし利用しています。
画像を加工
画像の加工は react-image-crop を選択の UI に利用し、HTML5 Canvas をマスクや画像の加工に利用しています。
出来上がった画像をダウンロード
出来上がった画像のダウンロードには js-file-download を利用しています。
PWA 対応
react-create-app では、標準で Service Worker の実装が含まれていますが、プロジェクトの作成直後は無効にされています。
src/index.js
の中身を
- serviceWorker.unregister();
+ serviceWorker.register();
と変更すると、Service Worker でリソースのキャッシュが有効にされ、オフラインでも利用できるようになります。
ただ、ローカルでは実行されなかったり http では動作しなかったりと色々制限はあります。 もっとも、オフラインの場合に特別な処理を行うような機能はないので追加で独自に実装しています。
オフラインモードの検出
オフラインモードの検出は
window.addEventListener('online', () => console.log('change network: online mode'));
window.addEventListener('offline', () => console.log('change network: offline mode'));
のような感じでできます。
また、今のモードの取得は
> console.log(navigator.onLine);
true
のような感じで取得できます。
まあ、それ以外にはどうしようもないのですが…
Lighthouse によるスコアの改善
Lighthouse によるスコアの改善などもしています。
大体は指摘に沿って直していけばいいのですが、不具合らしきものを見つけました。
[role]s are not contained by their required parent element
具体的には React Bootstrap の Card Navigation で [role]s are not contained by their required parent element
(訳:[role]は必須の親要素に含まれていません) と指摘がされます。
どうやら role
属性が Card Navigation に対して設定できない(設定しても React で生成された要素に付加されていない)状態になるようです。
ドキュメントによれば…
ARIA role for the Nav, in the context of a TabContainer, the default will be set to "tablist", but can be overridden by the Nav when set explicitly. When the role is "tablist", NavLink focus is managed according to the ARIA authoring practices for tabs:
訳: TabContainer のコンテキストでの Nav の ARIA ロールは、デフォルトが "tablist" に設定されますが、明示的に設定すると Nav によってオーバーライドできます。
ロールが「タブリスト」の場合、NavLinkフォーカスはタブの ARIA オーサリングプラクティスに従って管理されます。
role="tablist"
がデフォルトで設定されるようですがどうやらそれすらも無視されているようです。
しばらく悩み、最終的に Nav の親に属性を着ける事でとりあえずの対応としています。
対応方法はこんな感じ。
<Card>
- <Card.Header>
+ <Card.Header role="tablist">
<Nav variant="tabs" defaultActiveKey="#first">
<Nav.Item>
ダークモード対応
macOS や Windows 10 や Android 10 にはダークモードなる通常とは色調が反転した色合いのテーマに変更する機能があります。
ライトモード | ダークモード |
---|---|
ダークモード 対応
などと検索すると、画面上で切り替えスイッチを実装し、その設定を保存してテーマを切り替えるサンプルやライブラリが色々見つかりました。
とりあえず今回は CSS のメディア特性 prefers-color-scheme を利用し、システムの設定に沿って切り替わるようにしました。
現在の実装に落ち着くまで色々調べてみたのですが…
- CSS 全部に prefix をつけて JavaScript で切り替えるのは面倒(たぶん CSS をビルドすればできると思うけど…)
import('darkmode.css')
で読み込んで JavaScript で制御しようにもアンロードの方法が見つからない- CSS の
@media (prefers-color-scheme: dark) { ... }
のブロック内で@import
してもビルド対象に含まれない(外側だと埋め込まれるがそれでは意味がない…)
と、いろいろ課題があり、最終的には… dark-theme.css
という名前の CSS を用意し、@media (prefers-color-scheme: dark) { ... }
のブロック内に bootstrap-dark を直接埋め込む、という対応をしています。
それもこれも react-create-app で webpack のビルド設定が隠匿されているのでカスタマイズできないことが1番の要因だと思っています。
また、 react-dropzone や react-stepper-horizontal はダークモードに対応していないので追加でいい感じのスタイルを用意し、同じく @media (prefers-color-scheme: dark)
のブロック内に追加しました。
react-dropzone 用
@media (prefers-color-scheme: dark) {
.dropzone {
background-color: #444444;
}
}
react-stepper-horizontal 用
@media (prefers-color-scheme: dark) {
.stepper > div > div > div > a {
color: #EEEEEE !important;
}
.stepper > div > div > div > div > a,
.stepper > div > div > div > div > span {
color: #333333 !important;
}
}
参考
- React
- アクセシビリティ
- role属性とaria-*属性(WAI-ARIA)について【HTML5 Advent Calendar 2012 Day 9】 - E-riverstyle Vanguard
- HTML5 & CSS3 リファレンス - role属性 (要素の役割(WAI-ARIA))
- ARIA: tab role - Accessibility | MDN
- WAI-ARIAを意識したタブパネルのマークアップを考えてみる【アクセシビリティ】【HTML5】 - E-riverstyle Vanguard
- タブ切り替えを実装する時の注意点 | dkrkのブログ
- Google Lighthouseについて調べてみた vol.2 #lighthouse - ユアマイスター株式会社エンジニアブログ
- HTML 本当は怖い target="_blank" 。rel="noopener" ってなに? - かもメモ
- PWA
- create-react-appで作った雛形のコードがService Workerで何をしているのか - Qiita
- Progressive Web App のデバッグ | Tools for Web Developers
- Build a Realtime PWA with React - Better Programming - Medium
- React+PWAを最速で試してみた - Qiita
- How to add an “Offline” notification to your PWA - Tyler Argo - Medium
- Progressive Web Apps with React.js: Part 3 — Offline support and network resilience
- reactでオフラインでも実行可能なpwaの電卓を作ってみた │ どらごんテック
- window.navigator.onLine - Web API | MDN
- ダークモード
- その他