良い一日、友達!
「インポート/エクスポート」構文を使用するES6モジュールは非常に強力なツールであり、一般的なフレームワークのコンポーネントと競合します。
キャンバスにさまざまな形を描いて、これを示しましょう。MDNのJavaScriptガイドのこのセクションに
触発されました。 小さなアプリケーションに実装される機能は次のとおりです。
- 指定された寸法のキャンバスの自動作成とページ上でのレンダリング
- キャンバス上に特定のサイズと色の正方形、円、三角形を描画する機能
- コードをアプリケーションの論理部分を含むモジュールに分割する
アプリケーションを作成するプロセスでは、静的および動的インポートだけでなく、デフォルトの名前付きエクスポート/インポートにも特に注意を払います。ほとんどのアプリケーションは、クラス構文を使用して記述されます。
少なくとも、クラスとキャンバスの操作について基本的な知識があることが望ましいです。
プロジェクトコードはこちらです。
アプリケーションのデモはここで見ることができます。
サポートから始めましょう。
全体的にかなり良い。平均して約93%。
プロジェクトの構造は次のようになります(すべてのファイルを一度に作成することも、必要に応じて作成することもできます)。
modules
helpers
convert.js
shapes
circle.js
square.js
triangle.js
canvas.js
index.html
main.js
main.css
マークアップは次のようになります。
<div>
<section>
<h3>Circle</h3>
<label>
X:
<input type="number" value="75" data-prop="x" />
</label>
<label>
Y:
<input type="number" value="75" data-prop="y" />
</label>
<label>
Radius:
<input type="number" value="50" data-prop="radius" />
</label>
<label>
Color:
<input type="color" value="#ff0000" data-prop="color" />
</label>
<button data-btn="circle" class="draw_btn">Draw</button>
</section>
<section>
<h3>Square</h3>
<label>
X:
<input type="number" value="275" data-prop="x" />
</label>
<label>
Y:
<input type="number" value="175" data-prop="y" />
</label>
<label>
Length:
<input type="number" value="100" data-prop="length" />
</label>
<label>
Color:
<input type="color" value="#00ff00" data-prop="color" />
</label>
<button data-btn="square" class="draw_btn">Draw</button>
</section>
<section>
<h3>Triangle</h3>
<label>
X:
<input type="number" value="150" data-prop="x" />
</label>
<label>
Y:
<input type="number" value="100" data-prop="y" />
</label>
<label>
Length:
<input type="number" value="125" data-prop="length" />
</label>
<label>
Color:
<input type="color" value="#0000ff" data-prop="color" />
</label>
<button data-btn="triangle" class="draw_btn">Draw</button>
</section>
</div>
<button>Clear Canvas</button>
<script src="main.js" type="module"></script>
ここで何に注意を払うべきですか?
形状ごとに、必要なデータを入力するためのフィールドと、キャンバスに形状を描画するプロセスを開始するためのボタンを含む個別のセクションが作成されます。円のセクションの例の場合、そのようなデータは、開始座標、半径、および色です。入力フィールドを初期値に設定して、アプリケーションの状態をすばやくテストできるようにします。 「data-prop」属性は、スクリプトに入力するフィールドの値を取得するように設計されています。 「data-btn」属性は、どのボタンが押されたかを判別するために使用されます。最後のボタンは、キャンバスをクリアするために使用されます。
スクリプトがどのように接続されているかに注意してください。値が「module」の「type」属性が必要です。この場合、モジュールのロードはデフォルトで延期されるため(つまり、ページが完全にロードされた後)、「defer」属性は必要ありません。また、ページには「main.js」ファイルのみが含まれていることに注意してください。その他のファイルは、「main.js」内でモジュールとして使用されます。
モジュールの主な機能の1つは、各モジュールが「main.js」を含む独自のスコープ(コンテキスト)を持っていることです。一方では、これはグローバル名前付けの汚染を回避し、したがって同じ名前の変数と関数間の競合を防ぐので良いです。一方、異なるモジュールが同じDOM要素にアクセスする必要がある場合、たとえば、グローバル変数を使用して別のスクリプトを作成し、メインモジュールの前のページに接続するか、グローバル変数(window.variable = value)を明示的に作成する必要があります。各モジュール内で同じ変数を作成するか、モジュール間で変数を交換します(実際にはこれを行います)。
4番目のアプローチもあります。IDでDOM要素に直接アクセスします。この可能性について知っていましたか?たとえば、マークアップに「main」という識別子を持つ要素がある場合、最初に要素を定義(検索)することなく、単にmain(main.innerHTML = "<p> Some Awesome Content <p />")と呼ぶことができます。 「document.getElementById()」または同様のメソッドを使用します。ただし、この機会は個人的には非常に便利だと思いますが、このアプローチは非標準であり、将来サポートされるかどうかわからないため、使用をお勧めしません。
静的モジュールのもう1つの機能は、一度しかインポートできないことです。再インポートは無視されます。
最後に、モジュールの3番目の機能は、インポート後にモジュールコードを変更できないことです。つまり、モジュールで宣言された変数と関数は、インポートされたこのモジュールでのみ変更できます。これはできません。これは、プライベート変数と関数を含むオブジェクトを使用して、またはプライベートフィールドとメソッドを持つクラスを使用して実装された、モジュールの設計パターンにいくぶん似ています。
先に進みます。いくつかの最小限のスタイルを追加しましょう:
body {
max-width: 768px;
margin: 0 auto;
color: #222;
text-align: center;
}
canvas {
display: block;
margin: 1rem auto;
border: 1px dashed #222;
border-radius: 4px;
}
div {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
section {
padding: 1rem;
}
label {
display: block;
}
input {
margin: 0.25rem 0;
}
input:not([type="color"]) {
width: 50px;
}
button {
margin: 0.25rem auto;
cursor: pointer;
}
ul {
list-style: none;
}
li {
margin: 0.5rem auto;
width: 320px;
border-bottom: 1px dotted #222;
}
p {
margin: 0.25rem 0;
}
ここでは特別なことは何もありません。あなたの好みに美しさを加えることができます。
モジュールに移りましょう。
「canvas.js」ファイルには、キャンバスを作成およびレンダリングするためのクラスコードと、特定の形状を作成するときに表示されるメッセージのリストが含まれています(これらのメッセージは(従来の)形状の領域と周囲に関する情報を表します)。
// export default
// , IIFE ( , )
// , .. class ClassName..., export default ClassName
export default class Canvas {
// : ,
constructor(parent, width, height) {
this.parent = parent;
this.width = width;
this.height = height;
//
this.ctx = null;
//
this.listEl = null;
// ,
this.clearCanvas = this.clearCanvas.bind(this);
}
//
createCanvas() {
//
// ,
//
if (this.ctx !== null) {
console.log("Canvas already created!");
return;
} else {
// "canvas"
const canvasEl = document.createElement("canvas");
//
//
canvasEl.setAttribute("width", this.width);
canvasEl.setAttribute("height", this.height);
//
this.parent.append(canvasEl);
//
this.ctx = canvasEl.getContext("2d");
}
//
return this;
}
//
//
createReportList() {
if (this.listEl !== null) {
console.log("Report list already created!");
return;
} else {
const listEl = document.createElement("ul");
this.parent.append(listEl);
this.listEl = listEl;
}
return this;
}
//
clearCanvas() {
this.ctx.clearRect(0, 0, this.width, this.height);
this.listEl.innerHTML = "";
}
}
「convert.js」ファイルには、次数をラジアンに変換するための関数が含まれています。
//
export const convert = (degrees) => (degrees * Math.PI) / 180;
シェイプディレクトリ内の各ファイルは、特定のシェイプのモジュールです。一般に、これらのモジュールのコードは、描画方法、および形状の面積と周囲を計算するための式を除いて、同じです。円を描くためのコードを含むモジュール(circle.js)について考えてみます。
//
//
// - "Module"
import { convert } from "../helpers/convert.js";
//
//
export class Circle {
// ""
//
// , "" ctx listEl
constructor({ ctx, listEl, radius, x, y, color }) {
this.ctx = ctx;
this.listEl = listEl;
this.radius = radius;
this.x = x;
this.y = y;
this.color = color;
//
this.name = "Circle";
//
this.listItemEl = document.createElement("li");
}
//
draw() {
//
this.ctx.fillStyle = this.color;
//
this.ctx.beginPath();
// arc 6 :
// "x", "y", , ,
// ( "0, 2 * Math.PI")
// , :
this.ctx.arc(this.x, this.y, this.radius, convert(0), convert(360));
//
this.ctx.fill();
}
//
report() {
//
this.listItemEl.innerHTML = `<p>${this.name} area is ${Math.round(Math.PI * (this.radius * this.radius))}px squared.</p>`;
//
this.listItemEl.innerHTML += `<p>${this.name} circumference is ${Math.round(2 * Math.PI * this.radius)}px.</p>`;
this.listEl.append(this.listItemEl);
}
}
最後に、「main.js」ファイルで、「Canvas」モジュールクラスの静的なデフォルトインポートが実行され、このクラスのインスタンスが作成され、ボタンの押下が処理されます。これは、対応するFigureクラスモジュールを動的にインポートし、そのメソッドを呼び出すことで構成されます。
//
//
import Canvas from "./modules/canvas.js";
// ,
// :
// ,
const { ctx, listEl, clearCanvas } = new Canvas(document.body, 400, 300).createCanvas().createReportList();
// ""
// ,
// "async"
document.addEventListener("click", async (e) => {
//
if (e.target.tagName !== "BUTTON") return;
//
if (e.target.className === "draw_btn") {
//
// ,
//
const { btn: btnName } = e.target.dataset;
//
// -
const shapeName = `${btnName[0].toUpperCase()}${btnName.slice(1)}`;
//
const shapeParams = {};
//
const inputsEl = e.target.parentElement.querySelectorAll("input");
//
inputsEl.forEach((input) => {
//
//
const { prop } = input.dataset;
//
// , ,
const value = !isNaN(input.value) ? input.valueAsNumber : input.value;
//
shapeParams[prop] = value;
});
//
shapeParams.ctx = ctx;
shapeParams.listEl = listEl;
console.log(shapeParams);
//
// "Module"
const ShapeModule = await import(`./modules/shapes/${btnName}.js`);
// -
//
// "Module" ( )
const shape = new ShapeModule[shapeName](shapeParams);
//
shape.draw();
//
shape.report();
} else {
//
// "Canvas"
clearCanvas();
}
});
ここ のコードで遊ぶことができます。
ご覧のとおり、ES6モジュールは、コードを、すぐにまたはオンデマンドでロードできるアプリケーションの論理部分を含む比較的自己完結型のブロックに分割することに関連する、非常に興味深い機能をいくつか提供します。テンプレートリテラルと組み合わせて使用すると、一般的なフレームワークのコンポーネントの優れた代替手段になります。つまり、まず第一に、クライアント側でページをレンダリングします。さらに、このアプローチでは、変更が加えられたDOM要素のみを再レンダリングできるため、仮想DOMが不要になります。しかし、それについては次の記事の1つで詳しく説明します。
何か面白いものを見つけていただければ幸いです。清聴ありがとうございました。