HN 日本語サマリー

← 一覧へ戻る
Web開発

WebアプリをHTMLではなくCanvasで構築することを検討してもよい理由

You might want to build your WebApp in Canvas instead of HTML (hivekit.io)

8 pointsby wolframhempel8 コメント

要約

この記事では、GoogleドキュメントやMiroのような複雑なWebアプリケーションにおいて、HTMLのDOM要素の代わりにCanvas要素を使用する利点について解説しています。Canvasは、速度、レンダリングの完全な制御、デバイス間での一貫性といったメリットを提供し、特にピクセル操作、ズームやパン機能、複雑なレイアウトが必要な場合に強力な選択肢となります。ただし、`<input type="text">`のような標準的なHTML要素が提供する豊富な機能やアクセシビリティはCanvasにはないため、用途に応じた適切な選択が重要です。

全文翻訳

HTMLではなくCanvasでWebアプリを構築することを検討してもよい理由 Google、Microsoftなどの企業がどのようにWebアプリを構築しているのか、常に興味があります。それらは、最高性能のマシンから配線だらけの古いコンピューターまで、あらゆるコンピューターでうまく動作する必要があります。 もちろん、この質問には多くの答えがありますが、特に興味深いのは、通常HTMLで実装される機能にCanvas要素を使用していることです。GoogleドキュメントのドキュメントはCanvasです。GoogleスプレッドシートのスプレッドシートやExcelのWeb版も同様です。当然ながら、CanvaもCanvasですが、MiroのボードもCanvasです。Hivekitの独自のスケジューリングインターフェースもCanvasです。 Hivekitのスケジューラーはズーム(表示される時間範囲を変更)、X軸およびY軸方向へのパン、そして管理が必要な多くのインタラクティブな側面を持っています。私がその構築を手伝ったのですが、この記事では、なぜDOM要素よりもCanvasを選択したのか、その過程で何を学んだのか、WebアプリにとってCanvasが良い選択肢となる場合とならない場合、そしてなぜこれらの大手企業がパフォーマンスが重要なアプリにCanvasを使用しているのかを説明したいと思います。 Canvasとは何でしたか? Canvasは20年以上前から存在しています。HTMLドキュメント内に描画できる空白の領域を提供します。これを行うには、`fillRect()`のような高レベルメソッドで長方形を塗りつぶしたり、`getImageData()`のような低レベルメソッドでピクセルの生のRGBA値にアクセスしたりするJavaScript APIを使用します。どちらのアプローチを取るにしても、基本的には静止画像になります。 Web開発者にとっては、これは少し奇妙に感じられます。結局のところ、彼らは複雑なDocument Object Model、要素ツリーに解析されたHTML、クリックハンドラーとイベントバブリング、動的なレンダリングとリフローに慣れています。これらはすべてブラウザによって管理され、ユーザーのデバイスに完全に最適化されています。Canvasでは、これらすべてがなくなります。 では、なぜCanvasを使うのでしょうか? Canvasにしかできないことがいくつかあります。ピクセル画像操作がその明白な例です。しかし、HTMLでも構築できるWebアプリを構築するためにCanvasを選択する理由は何でしょうか? いくつかの理由があります。 速度:HTMLの解析、DOMの作成、CSSスタイルの適用、ユーザーインタラクションに関連する無数の機能の処理には時間がかかります。Webアプリが複雑になると、ブラウザはかなりの重労働を強いられる可能性があります。「ダミー」な描画APIは、作業量を減らします。作業量が減れば、速度が向上します。 制御:無限ワークスペースを持つホワイトボードアプリ、数え切れないほどの行を持つグリッド、またはズーム可能なワークスペースを持つ計画ツールを構築している場合、いずれにしてもレンダリングの制御を自分で取る必要があります。HTMLでも(ある程度)それは可能です。例えば、「仮想スクロール」を通じて、ブラウザのネイティブスクロールの代わりにグリッド行の内容を入れ替えたり、ユーザーがズームやパンをする際に要素を巧妙にDOMに追加・削除したりします。しかし、その時点では、レンダリング全体を自分で所有する方が良いかもしれません。 一貫性:Canvasを使用すると、デバイス間で指定したものを正確に出力できます。これは、ブラウザの実装が異なっていた頃はより大きな問題でしたが、現在でも、レスポンシブデザイン、CSSグラデーション、トランジションエフェクトは、オペレーティングシステムや画面によってかなり異なって見えることがあります。Canvasを使用すると、良くも悪くも同じ結果が得られます。 ポータビリティ:Canvasは、他のビジュアルフレームワークからの出力をレンダリングするために使用されます。Flutter Webや一部のWebAssembly実装は、画面バッファをCanvasに出力します。しかし、逆もまた同様です。EjectaやNativeScriptのようなツールは、C++描画APIをCanvas呼び出しでラップし、他のシステムにグラフィックスを出力できるようにします。 なぜCanvasを使わないのでしょうか? Canvasを使わない理由の方が、使う理由よりもはるかに多くあります。そして、ほとんどのWebアプリでは、昔ながらのDOM要素の方がはるかに優れています。例えば、地味な`<input type="text">`要素を見てみましょう。これを使用すると、あらゆる解像度でシャープなレンダリング、タブ、フォーカス、選択、マウスインタラクション、矢印キーナビゲーションのサポート、右から左へのテキストやアジアの複合文字の国際化、スクリーンリーダーのアクセシビリティなどが得られます…リストは続きます。 ブラウザは多くの機能を標準で提供しており、チーム間で整理された標準的な方法で、簡単に使用、スケーリング、作業できる優れたフレームワークがたくさんあります。 Canvasがより良い選択肢となるのはいつですか? Canvasがより良い選択肢となる特定のユースケースがあります。 絶対配置された要素が多い場合、不規則な形状、または複雑なレンダリング順序/z-indexの要件がある場合。ビジョンボードアプリを構築する場合でも、2Dプラットフォーマーを構築する場合でも、アプリが通常のHTMLレイアウトフローの外にある場合、Canvasはあなたの人生を楽にするかもしれません。 特定のものをレンダリングする必要があるだけの場合。アプリがズーム、パン、カメラ変換、クリッピング、タイリング、レベルオブディテールレンダリング、または仮想化を使用できる場合、Canvasは必要なものだけをレンダリングすることを容易にします。 アプリケーションがすでに強力な内部モデルを持っている場合。アプリがすでに状態、ジオメトリ、フォーカス、インタラクションの強力な概念を持っており、それを視覚化する方法だけが必要な場合、CanvasはHTMLよりも簡単です。 しかし、Canvas実装を決定した場合は、注意すべき重要な点がいくつかあります。 いつレンダリングするかを管理する:私たちにとって最も効果的だったパターンは、1つの中心的なレンダラーを持ち、それが`backgroundRenderer`、`rowRenderer`、`taskRenderer`のような特定の側面をレンダリングする他のクラスを呼び出すことです。他の各クラスは、レンダラーに`scheduleRender`を呼び出すことができます。これは、次のアニメーションフレームの単一のレンダリングパスをスケジュールします。注意点として、私たちは各フレームでCanvas全体をクリアし、すべてを最初からレンダリングします。これは無駄であり、より洗練された実装では特定の領域だけをクリアして再レンダリングするかもしれません。しかし、それを行うと複雑さが増します。そして、各フレームでCanvas全体を再レンダリングしても、私たちには問題が発生したことはありません。 ```javascript class Renderer{ constructor(canvas){ this.renderScheduled = false; this.canvas = canvas; this.context = canvas.getContext('2d'); this.backgroundRenderer = new BackgroundRenderer(this); this.rowRenderer = new RowRenderer(this); } scheduleRender(){ if(this.renderScheduled) return; this.renderScheduled = true; requestAnimationFrame(this.render.bind(this)); } render() { this.renderScheduled = false; this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); // Render the individual layers in order this.backgroundRenderer.render(this.context); this.rowRenderer.render(this.context); } } ``` 複数のCanvas要素をレイヤー化する:私たちの計画インターフェースでは、ユーザーがマウスで操作している間、基本的な計画は比較的静的です。しかし、多くのハイライトやホバーエフェクトがあります。これらには、元のCanvasと同じ寸法の2番目のCanvas要素を使用します。この`InteractionRenderer`は、実際のCanvasよりもはるかに頻繁に更新されますが、数フレームのバウンディングボックスしかレンダリングしないため、はるかに軽量です。 スタイルを分離する:CSSをHTMLから分離することはあなたの人生を楽にします。なぜCanvasでもそうしないのですか?スタイル用の別のファイルを用意します。例:`gapDiagonalLineSpacing: 10`, `textColor: '#ecf0f5'`, `textColorSecondary: '#4d6585'`, `fontStyle: "13px 'Lato', sans-serif"`, `fontStyleBold: "bold 13px 'Lato', sans-serif"`... デバイスの解像度とピクセル密度を管理する:シャープなレンダリング結果を得るには、Canvas要素がデバイスのピクセル比にスケーリングされていることを確認してください。次に、直感に反しますが、コンテキストのスケールを設定して要素のスケールを相殺します。これにより、コードが常にスケールとピクセル密度を意識する必要なしに、シャープなレンダリング結果が得られます。 ```javascript getPixelScale() { return Math.max(window.devicePixelRatio, 1); } scaleCanvas(canvas, ctx) { const pixelScale = this.getPixelScale(); canvas.width = canvas.offsetWidth * pixelScale; canvas.height = canvas.offsetHeight * pixelScale; ctx.scale(pixelScale, pixelScale); } ``` ドメイン座標をピクセルに変換する中央関数を持つ:無限ワークスペースタイプのアプリを構築している場合、このワークスペース上の要素はおそらくXとY座標を持っています。スプレッドシートを構築している場合は、行と列があります...