自分の手で天気カードコンポーネントを作るというアイデアは実はずっと前からあったのですが、実際に作るとなると難易度が高く(レイアウト、データソース、天気表示、自動調整)、結局は中途半端になってしまいました。最近、学校のサークルの面接課題が天気カードを作るというもので、ちょうど国慶節の小さな休暇を利用してじっくり研究する機会ができました。そこで、今日の記事が生まれました。
~(実際には国慶節の小さな休暇の前にほぼ問題を解決していました)~ 現在のバージョンの DouWeather は一時的なリポジトリにホスティングされています。コード構造を考慮していなかったため、後のメンテナンスが難しくなっています。今後の進展をフォローするためにこのリポジトリをウォッチしておくと良いでしょう。時間を見つけてコードをリファクタリングし、このリポジトリで更新します。
Ⅰ. デザイン構想段階#
1. インスピレーションの源#
私は DouWeather(以下 DW)の位置付けをウェブ小コンポーネントと考えています。このため、iOS システムのウィジェット、新版 MIUI システムの小コンポーネント、HarmonyOS のウィジェット、Windows 11 の小コンポーネントなどを参考にしましたが、どれも例外なく同じ特徴を持っていることに気付きました:フラットデザイン、角が丸い、サンセリフフォントの使用、要素のスタイルがシンプルで、背景やアイコンにグラデーションが多用されており、ウィジェットが非常に動的に見えます。その中で Windows 11 のウィジェットは薄い影を追加しており、ウィジェットをアクリル背景から際立たせるためかもしれません。
そこで、私もそれを模倣して DW の晴れのアイコンをデザインし、XD を使って最初のカードスタイル(現在の DW のミディアムカードスタイル)を作成しました。
注:DouWeather のデザインは Google Adsense(MD2)、mew などのウェブサイトのデザインスタイルも参考にしています。
ミディアムスタイルのカードを短くすることで、正方形のスモールスタイルの小コンポーネントが派生しました。
以前、天気カードを書こうと思ったときに作った画像もあり、その時は MD スタイルを参考にしました。ちょうど国慶節に帰省した際にパソコンから引っ張り出してきました。以下は、いつ作ったのかもわからない天気カードの初稿です… ちょっと抽象的です…
2. アイコンデザイン#
DW の多くの要素は~コピー~参考にした小米天気から来ています。アイコンも同様です。DW はアイコンの全体的なスタイルをシンプルに保つよう努め、大きなグラデーション背景を使用して天気の特徴を際立たせています。正式に作業を始める前に、小米天気を模倣して晴れ、曇り、雨の 3 つのアイコンを作成しました。これにより、その後のデザインと開発が便利になります。フォーマットは引き続き svg を使用し、コンポーネント全体のサイズを制御し、読み込み速度を確保しています。
開発中にルームメイトの影響を受けて、天気アイコンにいくつかのアニメーションを追加しようとしましたが、少し主張が強すぎて、結局中止になりました。
3. normal スタイルと detail スタイル?#
開発前、私は実際には 2 種類のスタイル(すなわちスモールとミディアム)を作成するだけの計画でした。normal スタイルを作成する主な理由は、開発中に気づいたことです:ミディアムスタイルが幅の広すぎる要素の上に置かれると、内容が空虚に見え、美観が損なわれることです。そこで、ミディアムスタイルを基に幅を広げ、空気質や日焼け止めの提案などのデータ表示を追加しました。そして detail スタイルは、小米天気のトレンド予報が大好きなので、DW にそれを再現したいと思ったからです。
Ⅱ. 開発段階#
1. 構築ツールを使わずに Web Components を優雅に使用するには?#
天気カードを書く前に、私は Web Components を 1 回しか使用したことがありませんでした。それは原神プレイヤー情報検索の中で、多くの重複要素(キャラクター情報)があったため、この新しいものを使ってカプセル化しようとしたからです。得られた教訓は、構築ツールを使わずに優雅に開発したい場合、template タグは必須であるということです。そうでなければ、コードのメンテナンスが非常に困難になります。
2. アイコンを優雅に表示するには?#
天気アイコンはカード内で大量に再利用されるため(特に detail スタイル)、比較的シンプルな呼び出し方法がなければ、メンテナンスが非常に困難になります。また、開発時にはアイコンが 3 つしかデザインされていなかったため、開発後期にアイコンを簡単に追加・削除・変更できるようにし、アイコンの検索と主コード間の結合度をできるだけ低く抑える必要があります。フロントエンドでは、一般的に以下のようなアイコンの導入方法があります:
① @font-face を使用してアイコンフォントファイルを導入する#
大型アイコンフォントは一般的にこの方法を採用しています。例えば Font Awesome や Material Icons です。利点は操作が直感的で、font-size や color を使ってアイコンの表示形式を直接変更でき、ブラウザがcolrをサポートしているため、カラフルなアイコンフォントを使用できることです。しかし、欠点も明らかです:特にグラデーション塗りつぶしに関しては、メンテナンスが非常に困難で、現在のところ、優雅にこのタスクを完了できるフォント制作ソフトウェアはありません。また、特定の携帯電話のカスタムフォントのフックロジックが、この方法で導入されたアイコンフォントが機能しない原因となることがあります。
ただし、DW では一部のアイコンがこの方法を採用しています。それは風向きのアイコンで、アイコンは単色で数が固定されており、頻繁に変更する必要がないため(8 方向)、非常に適しています。
② svg の symbol を使用する#
これも非常に一般的なアイコンの参照方法です。互換性が非常に良い。メンテナンスが比較的簡単で、アニメーションもサポートしています。AI はアイコンを symbol タグとして直接エクスポートでき、多くの構築ツールもこれをサポートしているため、基本的に欠点はありません。
しかし、DW の天気アイコンは上記の 2 つの方法を採用していません。アイコン部分は Web Components を使ってカプセル化しており、すでに symbol に似た機能を持っているため、symbol を再度使用するのは少し無駄に感じます。
カプセル化された天気アイコンの呼び出しは非常に簡単になり、特定のアイコンを呼び出すために <dw-icon type="sunny"></dw-icon>
のようなコードを直接使用できます。以下はその例です。
その後、gulp を使用する予定で、この方法も開発を便利にすることができます。
3. 自動調整はどうする?#
天気コンポーネントの開発中に、デザイン稿を再現することが実は最も簡単なことだと気づきました。カード内のすべての要素が秩序正しく表示されることを保証する必要があります。私は元々、各スタイルのカード幅を固定したいと思っていました。そうすればカードのレイアウトが常に完璧であることが保証されますが、天気カードの汎用性が大きく損なわれます。他の DW ユーザーは、1 つの小コンポーネントのために自分のレイアウトを変更することはありませんし、固定幅はモバイルデバイスでの天気カードの体験を非常に悪くします。しかし、自動調整はどうすればよいのでしょうか?
最も一般的な自動調整方法はメディアクエリを書くことですが、私はメディアクエリを使用できません。他の開発者がカードをどこに挿入するか、どのように挿入するか、カードの親要素がどのような状態であるかは全くわかりません。画面サイズだけで天気カードの現在の状態を判断することはできません。
カードの幅を判断して特定の要素を隠したり表示したりすることもできません。なぜなら、DW の今後のバージョンではデータ表示部分がモジュール化され、他の開発者がどのデータを表示するかをカスタマイズできるようになるからです。勝手に表示する要素を変更すると、他の開発者の設定が期待通りに表示されない可能性があります。
親コンテナのサイズを判断することについて話すなら、実装方法についても触れましょう。一般的に、私がよく使う方法は、親コンテナに iframe を埋め込み、iframe のサイズ変化を監視してコンテナのサイズ変化を追跡することです。将来的には CSS コンテナクエリ(Container Queries)を試してみることもでき、非常に便利ですが、現在この機能は PR 段階にあります…互換性リスト。
私は一時的にカードの幅を固定したいと思っていましたが、実際には DW のレイアウトとロジックをほぼすべて書き終えた後でも、良い解決策を見つけることができませんでした。
私にインスピレーションを与えたのは、Windows のエクスプローラーです:
天気カードの主要な要素は左側に固定され、右側のデータ表示はカードの幅に応じてスクロールバーを表示します。実装は非常に簡単です。なぜなら、私はフレキシブルレイアウトを使用しているため、元のデータ表示領域の外側に flex-grow: 1;
スタイルを持つコンテナをラップするだけで済むからです。
4. detail スタイルのデータ表示#
この部分も非常に複雑です。なぜなら、サークルの面接課題でグラフ表示が求められたからです。当初は小米天気の 15 日間のトレンド予報を再現しようと考えていました。もし成功すれば、後で時間予報などのグラフ表示にも拡張できるかもしれません。グラフ部分は svg で実装されており、ダークモードのスタイル操作を便利にするために、canvas ではなく svg を使用しました。描画はブラウザのネイティブ JS を使用して直接行い、折れ線グラフを描くだけで済みます。chartjs は明らかに大げさで、少し重いです。元のデザイン稿で採用された表示方法は、中央にグラフを優雅に挿入するのが非常に難しかったため、後に朝のデータ、グラフ、夜のデータをすべて分けました。列幅は一貫しているため、ずれの問題を心配する必要はありません。
次にグラフを描くことになります。まず、折れ点の X 座標を統一して計算し、次に温度に応じて各折れ点の Y 座標を決定します。折れ点には svg の circle 要素を使用し、折れ線部分は直接 path で処理しました。
path の d パラメータの構文ロジックは、canvas での描画ロジックと非常に似ています。まず、M(MoveTo)命令を使用して起点を最初の点の位置に移動し、その後は L(LineTo)命令を使用して残りの折れ線を描くだけで完成します。
4. ダークモード#
@media(prefers-color-scheme: dark)
このメディアクエリを使用するだけで、ダークモード下のカードスタイルを定義できます。
特筆すべきは、私はCSS 変数を使用しており、現在ほとんどのブラウザが互換性を持っています。これにより、重複コードを大幅に減らすことができます。
時には、ユーザーがメディアクエリに自動的にカードスタイルを変更されたくない場合があるため、属性 theme
を提供してカードの色を制御できるようにしました。theme="light"
または theme="dark"
を使用して、カードを明るいモードまたはダークモードに固定できます。この小さな機能は完全に CSS で実装できると思っていましたが、以前は Web Components をあまり使っていなかったため、ホストセレクタを使って簡単に解決できると思い込み、以下の CSS を書きました…
:host {
// デフォルトスタイル
}
:host[theme="dark"] {
// ダークモードスタイル
}
しかし… 失敗しました。スタイルは適用されませんでした。MDN を隅々まで探した後、私はこのセレクタ()~(MDN に基づいてプログラミング)~ を見つけたので、正しい書き方はこうなります(それで、blink のソースコードを見たことがある友人がなぜこう設計されているのか教えてくれませんか…):
:host {
// デフォルトスタイル
}
:host([theme="dark"]) {
// ダークモードスタイル
}
Ⅲ. まとめ#
DW を書くことで、多くのことを学びました。以前はフロントエンドを書く際に自分でグラフ生成をすることはほとんどなく、いつも chartjs や echarts を引用して終わっていました。Web Components についても以前よりも包括的な理解を得ることができ、flex レイアウトの使用にも慣れました。少なくとも 2 日以上小米天気を見て無駄ではありませんでした。私の友人は、私が一日中小米天気を見ているのを見て、私が狂っていると思っていました。