
記事の目的:
- 動的なコンテンツ、リンク、およびコントロールをサポートするCanvasフルHTMLページに基づく実装メカニズムを検討してください。
- このメカニズムを使用できるかどうか、そしてゲームがろうそくに値するかどうかを理解してください。
強み:
- Canvasは典型的なHTML要素であるため、HTML DOMに簡単に埋め込むことができます。
- Canvasは低レベルのAPIを持ち、通常のラスタ形式をサポートしているため高速です。
弱点:
- CanvasはHTML DOMを内部的にサポートしていないため、便利なコンテンツ、リンク、およびコントロールをCanvasに配置することはできません。
- 低レベルAPIは単純すぎるため、JavaScriptライブラリを使用するとパフォーマンスが必然的に低下し、最終的にCanvasの適用範囲が制限されます。
それを理解する最良の方法は、単純ではあるが単純な例を作ることです。 例は、リンク上の資料を読むことで断片化された形で得られる知識の私的な一般化としてコンパイルされます:
キャンバスのリファレンス
キャンバストレーニング
キャンバスの例
興味深い記事パート1
興味深い記事パート2
GitHab-eのサンプルコード(ディスクからブラウザーでhtmファイルを開くだけ)
デモの例(結果をすばやく確認)
作業環境
開発環境:
メインブラウザーはFF(Windows、Linux Mint)を使用します。
CRとIEの互換性を確認するには(Windows)。
次-利用可能なガジェット(詳細は最後にあります)。
IDE for Widows-Visual Studio Community、メモ帳。 スタジオは無料で、コードを適切にフォーマットし、自動追加を許容し、明らかなエラーを検出します(たとえば、括弧は省略されます)。
Linux IDE-gedit。
建築的アプローチ:
フロントエンド、静的サーバー。
外部ライブラリなしのネイティブAPI。 ライブラリの欠如はそれ自体で終わりではなく、今のところそれらなしで実行できます。
コード構造:
コードを1つのファイルに詰め込むこともできます(あまりコードはありません)。 しかし、明確にするために、ファイルに分割することをお勧めします。
HTMLページ:html_page_on_canvas.htm
汎用コード:html_page_on_canvas_main.js
モデル管理コード:html_page_on_canvas_model.js
キャンバスレンダーコントロールコード:html_page_on_canvas_canva.js
グローバル変数:
グローバル変数は悪いです。 グローバルな範囲なしで生活するのは難しいです。 妥協案として、グローバルAPELSERGオブジェクトが作成され、そこにすべての関数とグローバル変数が保存されます。
メイン:一般的な機能
モデル:モデル変更関数
MODEL.DATA:モデルデータ
CANVA:モデルレンダリング関数
CONFIG:グローバル変数
モデル
基本的な機能構造は、古典的なMVCパターンに基づいています。
モデルの役割はMODEL.DATAデータによって実行されます
CANVA機能によって実行される役割を表示する
コントローラーの役割はMODEL関数によって実行されます
ページオブジェクトの実装を始めましょう。 そのような人がいますが、広告バナーで雪が降るときはいつもそれが好きでした。 ページの背景全体に雪が降りたいです。 これで実装できます。 一般に、これには魔法はありません-Canvasはそのようなタスクを実装するように設計されています。
各スノーフレークはオブジェクトです。
APELSERG.MODEL.Flake = function (flakeX, flakeY, flakeSize, flakeColor) { this.BaseX = flakeX; this.X = flakeX; this.Y = flakeY; this.Size = flakeSize; this.Color = flakeColor; }
ダイナミクスの可能性はオブジェクトに固有であると考えることが重要です-XとYのプロパティの変更。
降雪を整理するために、雪片の配列が作成されます。
APELSERG.MODEL.MakeFlakes = function (flakeNum) { var flakes = []; var color = "white"; for(var n = 0; n < flakeNum; n++) { var x = Math.round(Math.random() * APELSERG.CONFIG.SET.PicWidth); var y = Math.round(Math.random() * APELSERG.CONFIG.SET.PicHeight); var s = n % APELSERG.CONFIG.SET.FlakesSize; var flake = new APELSERG.MODEL.Flake(x, y, s, color); flakes.push(flake); } return flakes; }
明確にするために、コンテンツも動的になります。 これを行うために、各行は個別のオブジェクトとして保存されます。
APELSERG.MODEL.ContentLine = function (text, textX, textY, textColor) { this.Text = text; this.X = textX; this.Y = textY; this.Color = textColor; this.FontHeight = 0; }
ここでは、移動に加えて、フォントのサイズを変更する可能性(FontHeightプロパティ)がダイナミクスに組み込まれています。 または、客観的に単語や文字を個別に保存することもできます。 そのため、さまざまな動的視覚効果をシミュレートできます。
コンテンツについては、冬をテーマにした童wereが選択されました。
ROOT.MODEL.MakeContent = function () { var color = "white"; var pointX = 0; var pointY = ROOT.CONFIG.PROC.CanvaID.height; // var addY = 30; // var Cnt = 0; // var content = []; content.push(new ROOT.MODEL.ContentLine(" ", pointX, pointY + addY * Cnt++, color)); content.push(new ROOT.MODEL.ContentLine(" ", pointX, pointY + addY * Cnt++, color)); content.push(new ROOT.MODEL.ContentLine(" ", pointX, pointY + addY * Cnt++, color)); content.push(new ROOT.MODEL.ContentLine(" ", pointX, pointY + addY * Cnt++, color)); return content; }
公平に言うと、関数にコンテンツを保存することは良くないと言う必要があります。 このアプローチは、例としてのみ使用されます。 作業環境では、たとえばWebサービスから動的コンテンツをロードできます。
アニメーション
最近のブラウザのアニメーションには、window.requestAnimationFrameという特別な機能があります。 これは典型的なアクティベーションコードです。
//-- window.MyRequestAnimationFrame = (function (callback) { return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; })(); //-- MyAnimation = function () { //-- //-- Canvas //-- window.MyRequestAnimationFrame(function () { MyAnimation(); }); }
この例からわかるように、機能的にrequestAnimationFrameは1秒あたり60回のタイムアウトで開始されます。 ただし、setTimeoutとは異なり、ブラウザの再描画サイクルと同期され、実行中のデバイスのリソースに適応できるという点で優れています。 requestAnimationFrameの詳細はこちら 。
欠点のうち、モデルで何かが変更されたかどうかに関係なくCanvasが再描画されるため、中央処理装置にとってこれはリソース集約的なタスクであることに注意する必要があります。 大規模なモデルは、割り当てられた時間間隔で常に再集計および描画できるとは限りません。
CONFIG.SET.CntHandle変数を使用して、モデルを変更し、異なるrequestAnimationFrameサイクルでレンダリングするプロセスを分離する単純なメカニズムが実装されています。 メカニズムは完全ではありませんが、簡単に制御できます。 品質基準は主観的な視覚認識です。快適に見える場合は、すべてが正常です。 CONFIG.SET.CntHandleの正しい値を選択することは残っています。
例では、window.requestAnimationFrameが互換性ラッパーなしで使用されています。 結果は、すべてのアニメーションプロセスを制御する関数です。
APELSERG.MAIN.Animation = function () { (APELSERG.CONFIG.SET.CntHandle > APELSERG.CONFIG.PROC.CntHandle) ? (APELSERG.CONFIG.PROC.CntHandle++) : (APELSERG.CONFIG.PROC.CntHandle = 0); if (APELSERG.CONFIG.PROC.CntHandle == 0) APELSERG.CANVA.Rewrite(); if (APELSERG.CONFIG.PROC.CntHandle == 1) APELSERG.MODEL.UpdateContent(); if (APELSERG.CONFIG.PROC.CntHandle == 2) APELSERG.MODEL.UpdateFlakes(); window.requestAnimationFrame(function () { APELSERG.MAIN.Animation(); }); }
いくつかのメモ:
- 次のレンダリングの基準は、常にモデルの変更です。 つまり、モデルが変更されていない場合、Canvasに再描画する必要はありません。 たとえば、チェスのゲームをアニメーション化するのは、次の動きの後でのみ意味があります。 これを考慮すると、場合によっては、中央処理装置を大幅に軽減できます。
- RequestAnimationFrameは、主にアニメーションプロセスの可視性を確保することを目的としているため、呼び出し間の時間を保証しないように設計されています。 また、モデルにとって、多くの場合、重要なのは時間間隔の安定性です。 モデル変更サイクルはsetTimeoutに実装できます(例には示されていません)。
雪とコンテンツをアニメーション化するための基準は、コードのシンプルさとコンパクトさです。
雪片のアニメーション(モデル変更):
APELSERG.MODEL.UpdateFlakes = function () { for (var n = 0 in APELSERG.MODEL.DATA.Flakes) { var flake = APELSERG.MODEL.DATA.Flakes[n]; var dir = 1; if (Math.round(Math.random() * 100) % 2 == 0) dir = -1; var shift = Math.round(Math.random() * 100) % 3; var move = Math.round(Math.random() * 100) % 2 + APELSERG.CONFIG.SET.FlakesMove; // if (((flake.X + shift * dir) < (flake.BaseX + 10)) && ((flake.X + shift * dir) > (flake.BaseX - 10))) { flake.X += shift * dir; } // flake.Size += dir; if (flake.Size > APELSERG.CONFIG.SET.FlakesSize) flake.Size = APELSERG.CONFIG.SET.FlakesSize; if (flake.Size < 0) flake.Size = 0; // flake.Y += move; if (flake.Y > APELSERG.CONFIG.SET.PicHeight) flake.Y = 1; } }
コンテンツアニメーション(モデルの変更):
APELSERG.MODEL.UpdateContent = function () { for (var n = 0; APELSERG.MODEL.DATA.Content.length > n; n++) { var contentLine = APELSERG.MODEL.DATA.Content[n]; // contentLine.Y -= APELSERG.CONFIG.SET.ContentMove; // if (contentLine.Y > 150 && contentLine.Y < APELSERG.CONFIG.PROC.CanvaID.height - 50) { if (contentLine.FontHeight < APELSERG.CONFIG.SET.ContentFontSize) { contentLine.FontHeight++; } } else { if (contentLine.FontHeight > 0) contentLine.FontHeight--; } } }
モデルを変更した後、キャンバスにレンダリングされます。 背景には適切な写真を使用できます。 すべてのレンダリングは、1つの単純な関数によって実行されます。
APELSERG.CANVA.Rewrite = function () { var ctx = APELSERG.CONFIG.PROC.Ctx; // ctx.drawImage(APELSERG.CONFIG.PROC.Img, 0, 0); // for (var n = 0 in APELSERG.MODEL.DATA.Content) { var content = APELSERG.MODEL.DATA.Content[n]; if (content.X >= 0 && content.Y >= 0 && content.FontHeight > 0) { ctx.font = content.FontHeight.toString() + "px Arial"; ctx.textAlign = "center"; ctx.fillStyle = content.Color; ctx.fillText(content.Text, APELSERG.CONFIG.PROC.CanvaID.width / 2, content.Y); } } // ( ) for (var n = 0 in APELSERG.MODEL.DATA.Flakes) { var flake = APELSERG.MODEL.DATA.Flakes[n]; ctx.beginPath(); ctx.arc(flake.X, flake.Y, flake.Size / 2, 0, 2 * Math.PI); ctx.fillStyle = flake.Color; ctx.fill(); } }
機能ボタンとリンクの追加
機能ボタンとリンクも動的オブジェクトです(雪とコンテンツに似ています)。 しかし、これらのオブジェクトのダイナミクスは移動ではなく、マウスまたはクリックでオブジェクトの上にマウスを移動すると色が変化します。
コマンドを説明するオブジェクト:
APELSERG.MODEL.Command = function (cmdCode, cmdName, cmdX, cmdY, lengthX, lengthY, cmdColor) { this.Code = cmdCode; this.Name = cmdName; this.X = cmdX; this.Y = cmdY; this.LengthX = lengthX; this.LengthY = lengthY; this.Color = cmdColor; this.SelectColor = 'red'; this.SelectCnt = 0; this.SelectName = false; this.ShowBorder = true; this.FontHeight = 20; }
リンクを説明するオブジェクト:
APELSERG.MODEL.Link = function (linkUrl, linkName, linkX, linkY, lengthX, lengthY, linkColor) { this.Url = linkUrl; this.Name = linkName; this.X = linkX; this.Y = linkY; this.LengthX = lengthX; this.LengthY = lengthY; this.Color = linkColor; this.SelectColor = 'lightblue'; this.SelectCnt = 0; this.SelectName = false; this.ShowBorder = false; this.FontHeight = 20; }
オブジェクトの構造は似ていますが、知覚しやすいように異なるエンティティとして説明されています。
マウスが移動すると、その座標はグローバル変数に保存されます。
APELSERG.CONFIG.PROC.CanvaID.addEventListener('mousemove', function (event) { APELSERG.CONFIG.PROC.MouseMoveX = event.clientX - APELSERG.CONFIG.PROC.CanvaID.offsetLeft - APELSERG.CONFIG.SET.PicBorder; APELSERG.CONFIG.PROC.MouseMoveY = event.clientY - APELSERG.CONFIG.PROC.CanvaID.offsetTop - APELSERG.CONFIG.SET.PicBorder; });
モデルを処理するとき、マウス座標はリンクオブジェクトまたはコマンドの場所と比較されます。
APELSERG.MODEL.CheckMoveFrame = function (frame) { if ((APELSERG.CONFIG.PROC.MouseMoveX > frame.X) && (APELSERG.CONFIG.PROC.MouseMoveX < frame.X + frame.LengthX) && (APELSERG.CONFIG.PROC.MouseMoveY > frame.Y) && (APELSERG.CONFIG.PROC.MouseMoveY < frame.Y + frame.LengthY)) { return true; } return false; }
マウスカーソルがオブジェクトにヒットすると、SelectNameプロパティが設定されます。
command.SelectName = APELSERG.MODEL.CheckMoveFrame(command);
クリックは同様の方法で処理されます。
APELSERG.CONFIG.PROC.CanvaID.addEventListener('click', function (event) { APELSERG.CONFIG.PROC.MouseClickX = event.clientX - APELSERG.CONFIG.PROC.CanvaID.offsetLeft - APELSERG.CONFIG.SET.PicBorder; APELSERG.CONFIG.PROC.MouseClickY = event.clientY - APELSERG.CONFIG.PROC.CanvaID.offsetTop - APELSERG.CONFIG.SET.PicBorder; }); APELSERG.MODEL.CheckClickFrame = function (frame) { if ((APELSERG.CONFIG.PROC.MouseClickX > frame.X) && (APELSERG.CONFIG.PROC.MouseClickX < frame.X + frame.LengthX) && (APELSERG.CONFIG.PROC.MouseClickY > frame.Y) && (APELSERG.CONFIG.PROC.MouseClickY < frame.Y + frame.LengthY)){ return true; } return false; }
クリックがオブジェクトに当たると、SelectCntプロパティが設定されます。
if (APELSERG.MODEL.CheckClickFrame(command)) command.SelectCnt = APELSERG.CONFIG.SET.CntSelect;
SelectCntは、フレームが強調表示されるアニメーションサイクルの数を設定します。
クリックが行われた要素を特定した後、必要なコマンドを実行したり、リンクをたどったりすることはもはや難しくありません。
アニメーションループの停止と再開の実装を簡単に説明するのは理にかなっています。
最初に、フォームがロードされると、アニメーションサイクルが開始されます(APELSERG.MAIN.OnLoad()関数のAPELSERG.MAIN.Animation())。 停止するには、requestAnimationFrameによってアニメーションループの再開を停止するだけです。 停止は、APELSERG.CONFIG.PROC.Stopフラグが設定されているときに発生します。 requestAnimationFrameを使用した単一サイクルでcancelAnimationFrameを適用してキャンセルすることは、すでに実行されているサイクルがキャンセルされるため意味がありません(新しいサイクルが開始されます)。 アニメーションサイクルを再開するには、APELSERG.CONFIG.PROC.Stopフラグをクリアし、APELSERG.MAIN.Animation()を開始する必要があります。 これは、マウスまたはキーボードの「F2」をダブルクリックすると発生します。
アニメーションモードで「F1」を押すと停止しますが、すでにcancelAnimationFrameを使用しており、APELSERG.CONFIG.PROC.Stopフラグを設定していません。 ダブルクリックアニメーションは再開できません;リセットフラグAPELSERG.CONFIG.PROC.Stopはこれを防ぎます。 この場合のアニメーション再開メカニズムは、「F2」を押すことによってのみ提供されます。 「F1」の停止は、cancelAnimationFrame操作のメカニズムを示すために特に追加されました。
APELSERG.CONFIG.PROC.CanvaID.addEventListener('dblclick', function (event) { if (APELSERG.CONFIG.PROC.Stop) { APELSERG.CONFIG.PROC.Stop = false; APELSERG.CONFIG.PROC.ShowCommands = false; //-- APELSERG.MAIN.Animation(); } }); window.addEventListener('keydown', function (event) { if (event.keyCode == APELSERG.CONFIG.KEY.F1) { window.cancelAnimationFrame(APELSERG.CONFIG.PROC.TimeoutID); } if (event.keyCode == APELSERG.CONFIG.KEY.F2) { APELSERG.CONFIG.PROC.Stop = true; window.cancelAnimationFrame(APELSERG.CONFIG.PROC.TimeoutID); if(APELSERG.CONFIG.PROC.Stop) { APELSERG.CONFIG.PROC.Stop = false; APELSERG.CONFIG.PROC.ShowCommands = false; //-- APELSERG.MAIN.Animation(); } } });
データ入力コントロールの追加
データ入力を担当するコントロールでは、すべてがそれほど明確ではありません。 値の増減などの単純な要素は、上記のように実装できます(例では、これらは「+」および「-」ボタンです)。
しかし、質問が本格的な入力要素(ドロップダウンリストまたはデータライン入力)に関するものである場合、Canvasフレームワーク内でのこのようなタスクの実装ははるかに複雑です。
この場合、いわゆる「ハイブリッド」アプローチが役立ちます。 つまり、データを入力するために、Canvasに加えて、標準のHTML DOM要素が使用されます。 要素は静的に作成し、絶対配置とZインデックスを使用してCanvasの下に隠すことができます。 または、入力時に動的に作成します。 この例は、2つの入力要素の動的作成を示しています。 1つの入力要素がCanvasの上に配置されます(APELSERG.MAIN.ShowSettingsTextSpeed()関数)。 もう1つはCanvasの外部にあります(APELSERG.MAIN.ShowSettingsThemeSelect()関数)。 機能的には、両方のオプションは機能しますが、Canvasの上にある要素にはスケーリングの問題があります(Ctrl +、Ctrl-)。
動的な適応性
主な例(Snowfall)が開発された後、それはあまり面白くないように思われました。 したがって、この例はトピックごとに拡張されています。 テーマの背景には、キャンバスのダイナミクスを確認およびデバッグするために、さまざまなサイズの写真が特別に選択されました。
この関数は、ダイナミクスを担当します。
APELSERG.MAIN.CanvasSize = function () { if (APELSERG.CONFIG.SET.PicWidth < Math.round(window.innerWidth * 0.9) && APELSERG.CONFIG.PROC.CanvaID.width != APELSERG.CONFIG.SET.PicWidth) { APELSERG.CONFIG.PROC.CanvaID.width = APELSERG.CONFIG.SET.PicWidth; } else if (APELSERG.CONFIG.SET.PicWidth > Math.round(window.innerWidth * 0.9) && APELSERG.CONFIG.PROC.CanvaID.width != Math.round(window.innerWidth * 0.9)) { APELSERG.CONFIG.PROC.CanvaID.width = Math.round(window.innerWidth * 0.9); } if (APELSERG.CONFIG.SET.PicHeight < Math.round(window.innerHeight * 0.8) && APELSERG.CONFIG.PROC.CanvaID.height != APELSERG.CONFIG.SET.PicHeight) { APELSERG.CONFIG.PROC.CanvaID.height = APELSERG.CONFIG.SET.PicHeight; } else if (APELSERG.CONFIG.SET.PicHeight > Math.round(window.innerHeight * 0.8) && APELSERG.CONFIG.PROC.CanvaID.height != Math.round(window.innerHeight * 0.8)) { APELSERG.CONFIG.PROC.CanvaID.height = Math.round(window.innerHeight * 0.8) } }
この関数は単純なアルゴリズムに従って動作します-背景写真がウィンドウサイズよりも小さい場合(スクロール要素を考慮して)、Canvasは写真のサイズに設定され、それ以外の場合、Canvasはウィンドウサイズに設定されます。
適合性
Canvas(およびHTML5全般)は比較的新しい要素であり、すべてのデバイスおよびブラウザーでサポートされているわけではありません。 また、HTMLページは常にどこにでも表示する必要があります。そのため、HTMLページを使用すると便利です。 互換性とは、次の新機能の技術的なサポートだけではありません。 決定的な要因は、たとえば、画面のサイズや解像度、コントロールの存在(タッチスクリーンのみ)、プロセッサのパフォーマンスなどです。 基本的な機能を実行できない場合、新しいテクノロジーや美しさではありません。
HTMLページを設計するときは、必要なレベルの互換性を確保するためのメカニズムが必要です。 このステートメントは、HTML5とCanvasだけに適用されるわけではありません。
この例は、モデルの別の表示のメカニズムを、古典的なHTMLページの形式で示しています。 これは記事のメイントピックには当てはまらないため、すべてが非常に簡単に行われます。
まず、ブートプロセス中に、互換性の重要な兆候が識別されます。
APELSERG.MAIN.CheckCompatible = function () { if (!window.requestAnimationFrame || screen.width < 1000) { APELSERG.CONFIG.SET.CompatibleType = 1; } }
また、Canvasの表示が不可能または望ましくない場合、APELSERG.MODEL.ContentAsHtmlText()関数を使用して単純なHTMLページが形成されます。
キャンバスの問題
Canvasは定期的に「遅れる」。 低負荷でも遅れが発生します。 主観的に-遅延の時間と頻度は、特定のデバイス、OS、およびブラウザーによって異なります。 問題の本質は、明らかに、シングルスレッドのイベントループおよび/またはガベージコレクターにあります。