新たなプロジェクト「RECTGRAPH(レクトグラフ)」を公開しました。

アプリで作成されたサンプルが、横 5 列、縦 2 行で合計 10 個並んでいる

アナログシンセをモチーフにした、SVG イメージを生成するシンプルなウェブアプリです。SVG は 30 個の矩形要素(<rect>)により構成されており、古き良きドット絵風のイメージを生成できます。公開時は 10 個のプリセットを用意しています。

生成される矩形要素の配置パターンはランダムなので図柄は毎回異なります。また、この矩形要素のカラーやエフェクトを調整して見た目を変更することが可能です。

なお、本アプリにはシンセサイザーで使われている名称(「CUTOFF」や「RESONANCE」)が使われていますが、厳密にシンセの仕組みと一致するものではありません。

本アプリは以下の機能で構成されています。

アプリ各機能の名称を示した図

イメージのプレビューが表示されます。この SVG 内の矩形要素(<rect>)の配置はランダムなので、毎回異なる図柄になります。

2023/06/29 追記 プレビューのサイズを変更しました。

タイトルはダウンロード時のファイル名や、ダウンロードしたの SVG の <title> に設定されます。このタイトルは自由に変更できます。

「BASE」は、ベースとなる背景カラーです。「OSC.1」は矩形要素(<rect>)のカラーです。「OSC.2」も同様に矩形要素のカラーですが、横幅が 2 マスの場合にのみ適用されます。

カラーを変更した状態のスクリーンショット
配色をグリーン系に変更

ちなみに OSC は Oscillator(オシレーター、発振器)の略称で、シンセサイザーにおいては音の基本となる波形を発生させるために用いられます。

「OSC.1 LEVEL」と「OSC.2 LEVEL」の 2 つのパラメータを持ち、矩形要素それぞれの不透明度(opacity)を調整できます。本アプリで指定できる色数は最大 3 色ですが、透過率を加えることで立体的な効果が生まれます。

「CUTOFF」は、矩形要素を移動・変形する量を指定します。値を大きくすると、各矩形要素が右下から切り取られ、サイズが小さくなります。

「CUTOFF」を適用している状態のスクリーンショット
「CUTOFF」を 50% まで上げた状態

「RESONANCE」は矩形要素に対して歪み効果を与えます。値を大きくすると振動数が増えます。「CUTOFF」の値が 0 のときには変化が生じませんが、「CUTOFF」の値を大きくするほど目立った効果が得られます。

「CUTOFF」と「RESONANCE」を適用している状態のスクリーンショット
「CUTOFF」と「RESONANCE」を 50% まで上げた状態

「MIX」ボタンを選択すると、プレビュー内の矩形要素の配置がシャッフルされます。

「MIX」ボタンを選択してプレビュー内の矩形要素の配置が変更された状態のスクリーンショット

「DOWNLOAD」ボタンを選択すると、プレビュー内容の SVG ファイルがダウンロードできます。

ダウンロードできる SVG ファイルのライセンスは MIT License で、SVG のコードにライセンスに関するコメントを含めています。

ビューモードは、プレビューエリアでの SVG の見え方を変更する機能です。モードを有効にすると、すべてのコンポーネントに設定が反映されます。

「DISC」を選択すると、プレビューの SVG が正円に収まります。

「DISC」をオンにした状態のスクリーンショット

「3D」を選択すると、プレビューの SVG が変形し遠近感が得られます。

「3D」をオンにした状態のスクリーンショット

ダウンロードファイルに「DISC」や「SVG」の効果は付きません。

現時点では以下の問題を認識しています。

閲覧ブラウザや表示される SVG のサイズ、矩形要素の組み合わせによっては、矩形要素が配置されるグリッドにわずかに隙間が生じます。

ダウンロードファイル

見出し「ダウンロードファイル」

ダウンロードした SVG ファイルは、一部の環境では開くことができませんのでご了承ください。

また、SVG ファイルを主要なブラウザで開くことはできますが、デザインツール(Adobe Illustator や Figma)で開くとフィルタ効果が正しく反映されません。

ここからは、具体的な実装内容を説明していきます。

SVG イメージには、以下の 4 パターンの矩形要素(<rect>)を配置しています。

矩形要素のパターンを表す図。矩形要素は 1x1、1x2、2x1、2x2 の 4 パターン。上下左右の 2 マスは余白(Clear Space)として確保されている

矩形要素の配置のルールは以下のとおりです。

  • 矩形要素の数は 30 個
  • 4 パターンのいずれかがランダムに配置される
  • すでに要素が配置されていてもその上に重なることがある
  • 横幅が 1 マスの場合には「OSC.1」のカラー、不透明度が適用される
  • 横幅が 2 マスの場合には「OSC.2」のカラー、不透明度が適用される
  • 上下左右の 2 マスは余白(Clear Space)のため配置されない

「MIX」ボタンを選択したときも同様で、再び矩形要素をランダムに配置します。

ブラウザネイティブの <input type="color"> ではブラウザごとに使い勝手が大きく異なるため、「Coloris」というカラーピッカーのライブラリを導入しています。

カラーピッカーを表示している状態のスクリーンショット

ただ、デフォルトではカラーピッカーを開くと入力フィールドにフォーカスが当たるのですが、モバイル端末ではバーチャルキーボードが開いてしまうのがわずらわしいため、オプションで focusInput: false を指定し、フォーカスが当たらないようにしました。

しかし、この状態だと、キーボード操作でカラーピッカーを閉じたときにフォーカス位置が元の位置に戻らないため、イベントリスナーを追加して調整しました。

color.addEventListener('close', (e) => e.target.focus());

また、スクリーンリーダーを有効にした状態では、十分にカラーピッカーのキーボード操作ができないようなので、苦肉の策ではありますが、カラーピッカーを開くボタン要素に aria-disabled="true" を付与しました。

「CUTOFF」と「RESONANCE」の機能は SVG フィルタの <feTurbulence><feDisplacementMap> の組み合わせで実現しています。

<feTurbulence> と <feDisplacementMap> を組み合わせたコードの抜粋
<svg>
  <filter>
    <feTurbulence
      type="turbulence"
      baseFrequency="0"
    />
    <feDisplacementMap
      in="SourceGraphic"
      scale="0"
    />
  </filter>
  <!-- ... -->
</svg>

おおまかに説明すると、「CUTOFF」の値を大きくすると <feDisplacementMap>scale の値が大きくなり、各矩形要素が右下から切り取られ、サイズが小さくなる効果が得られます。

<feDisplacementMap> は画像と組み合わせて、その画像の RGBA チャネルに応じて、X 軸と Y 軸を変位させることもできるようなのですが、このアプリではそのような使い方はしておりません。

一方の「RESONANCE」は、値を大きくすると、<feTurbulence>baseFrequency の値が大きくなり、矩形要素の歪みの効果がより細かくなります。この歪みの効果は、「CUTOFF」の値が 0 だと発生しませんが、値を大きくするほど顕著になります。

正直なところ、SVG フィルタに関しては深く理解できていないので、以下の記事を参考にしつつ、試行錯誤するうちにこの形に落ち着きました。

「DISC」と「3D」による見た目の切り替えは、いわばオマケの機能です。

「DISC」は単純に CSS の border-radius で見た目を変更し、「3D」は transition3d() 関数を使用しています。

「3D」の実装方法については、以下の記事を参考にしました。

「ダウンロード」ボタン選択時には、以下の処理を実行しています。

  • ファイル名の作成
  • SVG コードの整理
    • スタイル指定の調整
    • 不要な属性の削除
    • フィルタを使用していなければ <filter> 要素を削除
  • ライセンスコメントの追加
  • ファイル圧縮(空白や改行の削除)
  • ファイルのダウンロード
    • Blob オブジェクトの作成
    • ダウンロード用に <a> 要素を生成して click() イベントを実行
  • 後処理

SVG イメージのダウンロード方法については、以下の記事を参考にしました。

このプロジェクトは、「CSS コンテナクエリ考察」というブログ記事を書いた後に、コンテナクエリの使い方の可能性を調べる目的で、CSS Grid と CSS コンテナクエリの組み合わせた実験をするなかで生まれました。

当初は、CSS Grid で 10 x 10 に区切ったマスに、空の <div> 要素を grid-area で配置し、コンテナクエリで @container (inline-size > 10cqi) { ... } と指定し、基準コンテナのインラインサイズが 10% を超える場合に色を変更する仕組みでした。

初期のプロトタイプのスクリーンショット。CSS Grid のガイドが表示されている
初期のプロトタイプでは CSS Grid をベースとしていた

ただ、以下の点より、最終的には SVG での実装に変更しました。

  • 表示サイズによって、コンテナクエリ(@container)のスタイルが適用されないことがある
  • 画像のダウンロード機能を実装したい

前者については、特に macOS Safari が顕著で、ブラウザのウィンドウをリサイズすると解消することがあります。ブラウザ側でのコンテナ幅の計算がうまくいっていないのか、こちらの CSS 指定がよくないのか、原因はよくわかっていません。


このように、当初の目的から少しずつ変わっていきましたが、調整を重ねるうちに現在の形に落ち着きました。

まだ改善の余地はありますし、いろいろ試してみたい機能もありますが一旦ここまでとして、また新たなアイデアが浮かんだら何かつくります。