グラフィックの描画 - ウェブ開発を学ぶ

グラフィックの描画
=========

* 前のページ
* Overview: Client-side web APIs
* 次のページ

ブラウザーには、 Scalable Vector Graphics (SVG) 言語から HTML の `<canvas>` 要素に描画するための API まで、とても強力なグラフィックプログラミングツールが含まれています（キャンバス API と WebGL を参照）。この記事では、キャンバスについて紹介し、さらに詳しく学ぶためのリソースを提供します。

|  |  |
| --- | --- |
| 前提知識: |
 JavaScript の基本
 （第一歩、
 構成要素,
 JavaScript のオブジェクト）、
 クライアントサイド API の基本 |
| 目標: | JavaScript　で`<canvas>`　要素に描画するための基本を学ぶこと。 |

ウェブでのグラフィック
-----------

HTML の マルチメディアと埋め込み モジュールで話したように、ウェブはもともとテキストだけでした、それはとても退屈だったので、画像が登場しました。最初は `<img>` 要素によって、後には `background-image` といった CSS のプロパティを経て、 SVG が導入されました。

しかし、これではまだ十分ではありません。 CSS や JavaScript を使用して、マークアップで表わされた SVG ベクター画像をアニメーションさせたり操作したりすることはできますが、ビットマップ画像に対して同じことをする方法はまだなく、利用できるツールもかなり限定されていたのです。ウェブでは、アニメーションやゲーム、 3D シーンなど、 C++ や Java といった低水準の言語が扱う要件を効果的に作成する方法がまだありませんでした。

ブラウザーが `<canvas>` 要素と関連するキャンバス API に対応し始めた頃から状況は改善されました。 Apple が 2004 年頃にこれを発明し、それ以降の数年間で、他のブラウザーもこれを実装して後に続きました。後述するように、キャンバスは 2D アニメーション、ゲーム、データの視覚化、その他の種類のアプリケーションを作成するための多くの有用なツールを提供し、特にウェブプラットフォームが提供する他の API と組み合わせたときにその威力を発揮します。

以下の例は、もともと JavaScript オブジェクト入門モジュールで出会った、キャンバスベースのシンプルな 2D の弾むボールアニメーションを示しています。

2006〜2007年頃、Mozilla は 3D キャンバスの実装を実験的に行うために動作を開始しました。これが WebGL となり、ブラウザーベンダーの間で評判となり、2009 年から 2010 年頃に標準化されました。WebGL を使うと、ウェブブラウザーの中で本格的な 3D グラフィックを作成することができます。以下の例では、単純な回転する WebGL 立方体を示しています。

この記事では、生の WebGL コードはとても複雑であるため、主に 2D キャンバスに焦点を当てます。しかし、WebGL ライブラリーを使用してより簡単に 3D シーンを作成する方法を紹介します。また、生の WebGL を使用するチュートリアルは他の場所にあります。 WebGL 入門を参照してください。

**メモ:** キャンバスの基本機能は、 2D キャンバスについては IE 8 以下、 WebGL については IE 11 以下を除き、すべてのブラウザーで十分に対応しています。

アクティブラーニング: <canvas>を始めよう
-------------------------

ウェブページに 2D *または* 3D シーンを作成する場合、HTML の `<canvas>` 要素から開始する必要があります。この要素は、ページ上で画像が描画される領域を定義するために使用されます。これは、ページ上に要素を記載するのと同じくらい簡単です。

```html
<canvas width="320" height="240"></canvas>
```

これにより、ページ上に 320 × 240 ピクセルの大きさのキャンバスが作成されます。

canvas タグの内部には、ユーザーのブラウザーが canvas に対応していない場合に表示される代替コンテンツを置くことができます。

```html
<canvas width="320" height="240">
  <p>Your browser doesn't support canvas. Boo hoo!</p>
</canvas>
```

もちろん、上記のメッセージは実に不親切です。実際の例では、代替コンテンツをキャンバスの内容に関連付けることが必要です。たとえば、常に更新される株価のグラフをレンダリングする場合、フォールバック コンテンツには最新の株価グラフの静止画像を使用し、代替テキストに株価を示すテキストを表示ようにすることができます。

### キャンバスの作成とサイズ変更

まずは、これからの実験を描く自分自身のキャンバスを作成するところから始めましょう。

1. まず、 0\_canvas\_start ディレクトリーのローカルコピーを作成します。これには 3 つのファイルが含まれています。
    * "index.html"
    * "script.js"
    * "style.css"
2. "index.html" を開き、冒頭の `<body>` タグのすぐ下に、以下のコードを追加してください。

```html
<canvas class="myCanvas">
  <p>Add suitable fallback here.</p>
</canvas>
```

ページ上に複数のキャンバスがある場合に選択しやすいように、`<canvas>` 要素に `class` を追加しましたが、`width` と `height` 属性は今のところ削除しています（使用した場合に追加することもできますが、以下の節で JavaScript を使用して設定することになります）。幅と高さが明示されていないキャンバスの既定値は、幅 300 ピクセル、高さ 150 ピクセルです。
3. 次に、 "script.js" を開き、以下の JavaScript の行を追加してください。

```js
const canvas = document.querySelector(".myCanvas");
const width = (canvas.width = window.innerWidth);
const height = (canvas.height = window.innerHeight);
```

ここでは、定数 `canvas` にキャンバスへの参照を格納しています。2 つ目の行では、新しい定数 `width` とキャンバスの `width` プロパティを `Window.innerWidth` （ビューポート幅に等しい値）に設定しています。3 行目では、新しい定数 `height` とキャンバスの `height` プロパティを `Window.innerHeight` （ビューポートの高さを指定）に等しくなるように設定しています。これで、ブラウザーウィンドウの幅と高さをすべて満たすキャンバスを保有することができます。
 また、複数の等号で代入を一斉に連結しているのがわかると思います。これは JavaScript で許可されており、複数の変数をすべて同じ値にしたい場合に有効なテクニックです。キャンバスの幅と高さは、変数 width/height で簡単にアクセスできるようにしたいと思いました。後で利用できるようにするために有用な値だからです（たとえば、キャンバスの幅のちょうど半分の大きさのものを描きたい場合など）。

**メモ:** 一般に、画像のサイズは、上記で説明したように、 HTML 属性または DOM プロパティを使用して設定する必要があります。 CSS を使用することもできますが、サイズ設定はキャンバスがレンダリングされた後に行われるため、他の画像と同様に（レンダリングされたキャンバスは単なる画像です）、画像がピクセル化したり歪んだりする可能性があるという問題があります。

### キャンバスのコンテキストと最終セットアップを取得する

キャンバステンプレートが完成したと考えることができるようになる前に、最後にもうひとつやっておくことがあります。キャンバスに描画するには、コンテキストと呼ばれる描画領域への特別な参照を取得する必要があります。これは `HTMLCanvasElement.getContext()` メソッドを使用して行います。基本的な使用法では、取得したいコンテキストの型名を表す 1 つの文字列を引数として受け取ります。

この場合、 2D キャンバスを取得したいので、 "script.js" の他の行の下に、以下の JavaScript を追加してください。

```js
const ctx = canvas.getContext("2d");
```

**メモ:** 他にも、 WebGL の場合は `webgl`、WebGL 2 の場合は `webgl2` などのコンテキスト値を選ぶことができますが、この記事では必要ありません。

これでキャンバスには下地ができ、描画する準備ができました。これで `ctx` 変数に `CanvasRenderingContext2D` オブジェクトが格納され、キャンバス上でのすべての描画処理にはこのオブジェクトの操作が必要となります。

次に移動する前に最後のことをしましょう。キャンバスの背景を黒く塗って、キャンバス API を最初に体験してもらいましょう。 JavaScript の一番下に以下の行を追加してください。

```js
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.fillRect(0, 0, width, height);
```

ここでは、キャンバスの `fillStyle` プロパティを使用して塗り色を設定し（これは、CSS のプロパティと同様に color values をとります）、次に `fillRect` メソッドを用いてキャンバスの全領域を占める矩形を描画しています（最初の 2 つの引数は矩形の左上隅の座標です。最後の 2 つは矩形を描く幅と高さで、変数 `width` と `height` は有用であることは既に示しました）。

さて、テンプレートが完成しましたので、次に移動させます。

2D キャンバスの基本
-----------

上で述べたように、すべての描画処理は `CanvasRenderingContext2D` オブジェクト（ここでは `ctx` ）に対する操作で行われます。多くの処理では、どこに何を描くかを正確に指定するために座標を与える必要があります。キャンバスの左上は点 (0, 0) で、水平 (x) 軸は左から右へ、垂直 (y) 軸は上から下へ走ります。

![小さな正方形がその領域を覆い、真ん中にスチールブルーの正方形がある格子状のグラフ用紙。キャンバスの左上隅は、キャンバスの X 軸と Y 軸の点 (0, 0) である。横軸 (x) は左から右に動作して幅を表し、縦軸 (y) は上から下に動作して高さを表す。青い正方形の左上隅は、 Y 軸から x 単位、 X 軸から y 単位の距離であることがラベル付けされている。](/ja/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics/canvas_default_grid.png)

図形を描くには、矩形図形プリミティブを使用するか、一定のパスに沿って線をなぞり、その図形を塗りつぶす方法を取る傾向があるようです。以下では、その両方の方法を示します。

### 簡単な矩形

まずは簡単な長方形から始めてみましょう。

1. まず最初に、新しくコーディングしたキャンバステンプレートのコピーを取得します（上記の手順に従わなかった場合は、1\_canvas\_template ディレクトリーのローカル コピーを作成します）。
2. 次に、 JavaScript の一番下に、以下の行を追加してください。

```js
ctx.fillStyle = "rgb(255, 0, 0)";
ctx.fillRect(50, 50, 100, 150);
```

保存して更新すると、キャンバスには赤い矩形が現れるはずです。その左上隅はキャンバスの上端と左端から 50 ピクセル離れており（最初の 2 つの引数で定義）、幅は 100 ピクセル、高さは 150 ピクセルです（3 番目と 4 番目の引数で定義）。
3. もうひとつ矩形を追加してみましょう。今度は緑色の矩形です。 JavaScript の一番下に以下のものを追加してください。

```js
ctx.fillStyle = "rgb(0, 255, 0)";
ctx.fillRect(75, 75, 100, 100);
```

保存して更新すると、新しい矩形が表示されます。この点は重要です。矩形や行を描くなどのグラフィック処理は、発生した順番に実行されます。たとえば、壁にペンキを塗るとき、ペンキが重なり合うと、その下にあるものが隠れてしまうことがあります。これは何らかの方法で変更することはできないので、グラフィックを描く順序を注意深く考える必要があります。
4. 半透明の色を指定することで、例えば `rgba()` を使用して半透明のグラフィックを描画することができることに注意してください。 `a` 値は「アルファチャンネル」と呼ばれるもの、つまり色が持つ透明度の量を定義します。この値が高いほど、その背後にあるものをより見えなくすることができます。あなたのコードに以下のように追加してください。

```js
ctx.fillStyle = "rgba(255, 0, 255, 0.75)";
ctx.fillRect(25, 100, 175, 50);
```

5. 今度は自分自身でもっと長方形を描いてみてください。楽しみましょう。
### ストロークと線の幅

これまで、塗りつぶされた長方形の描画について見てきましたが、輪郭だけの長方形（グラフィックデザインでは**ストローク**と呼びます）を描画することも可能です。ストロークに必要な色を設定するには、`strokeStyle` プロパティを使用します。ストロークの長方形を描くには `strokeRect` を使用して行われます。

1. 前の例に以下のものを追加してください。また、前の JavaScript の行の下に追加してください。

```js
ctx.strokeStyle = "rgb(255, 255, 255)";
ctx.strokeRect(25, 25, 175, 200);
```

2. ストロークの既定幅は 1 ピクセルです。これを変更するには `lineWidth` (en-US) プロパティの値を調整します（ストロークの幅のピクセル数を表す数値を受け取ります）。前の 2 行の間に以下の行を追加してください。

```js
ctx.lineWidth = 5;
```

これで、白い輪郭がかなり太くなったことがわかると思います。これで一旦終了です。この時点で、例はこのようになります。

**メモ:** 完成版のコードは GitHub で 2\_canvas\_rectangles にあります。

### パスの描画

長方形よりも複数の複雑なものを描画する場合は、パスを描画する必要があります。基本的には、描きたい図形をなぞるためにペンがキャンバス上で移動させるパスを正確に指定するためのコードを書くことになります。キャンバスには、直線、円、ベジェ曲線などを描くための関数が含まれています。

この節ではまず、新しい例を描くためにキャンバステンプレート (1\_canvas\_template) の新しいコピーを作成しましょう。

以下の節すべてにおいて、いくつかの共通のメソッドとプロパティを使用します。

* `beginPath()` — キャンバス上で現在ペンがあるこの点からパスの描画を開始します。新しいキャンバスには、ペンは (0, 0) から開始されます。
* `moveTo()` — ペンをキャンバス上の異なる点に移動させると、線を記録したりトレースしたりせずに、ペンは新しい位置に「ジャンプ」します。
* `fill()` (en-US) — これまでなぞったパスを埋めて、塗りつぶした図形を描きます。
* `stroke()` — これまでに描いたパスに沿ってストロークを描き、アウトライン図形を描きます。
* また、長方形だけでなく、パスに対しても `lineWidth` や `fillStyle`/`strokeStyle` のような機能を使用することができます。

典型的な、簡単なパス描画処理をすると、次のようになります。

```js
ctx.fillStyle = "rgb(255, 0, 0)";
ctx.beginPath();
ctx.moveTo(50, 50);
// draw your path
ctx.fill();
```

#### 線を描く

キャンバスには正三角形を描いてみましょう。

1. まずはじめに、以下のヘルパー関数をコードの一番下に追加してください。これは度数の値をラジアンに変換するもので、 JavaScript で角度の値を指定する必要があるときは常にラジアン単位で指定されますが、人間は通常度数で考えるので有用です。

```js
function degToRad(degrees) {
  return (degrees \* Math.PI) / 180;
}
```

2. 次に、先ほどの追加部分の下に従うことでパスを開始します。ここでは、三角形の色を設定し、パスを描き始め、何も描かずにペンを (50, 50) に移動させています。そこが三角形を描き始める場所です。

```js
ctx.fillStyle = "rgb(255, 0, 0)";
ctx.beginPath();
ctx.moveTo(50, 50);
```

3. 次に、スクリプトの末尾に以下の行を追加してください。

```js
ctx.lineTo(150, 50);
const triHeight = 50 \* Math.tan(degToRad(60));
ctx.lineTo(100, 50 + triHeight);
ctx.lineTo(50, 50);
ctx.fill();
```

では、順番に動作させてみましょう。
 最初に、(150, 50) へ直線を引きます。このパスは、x 軸に沿って右へ 100 ピクセル進みます。
 次に、この正三角形の高さを、簡単な三角法を使用して算出します。基本的には、三角形は下向きに描かれています。正三角形の角度は常に 60 度です。高さを計算するには、正三角形を真ん中から 2 つに分割し、それぞれ 90 度、 60 度、 30 度の角度を持つようにすればいいのです。それぞれの辺は次のように呼びます。
    * 一番長い辺は、**斜辺 (hypotenuse)** と呼ばれます。
    * 60度の角度の横にある辺は、**隣辺 (adjacent)** と呼ばれています。これは、先ほど引いた線の半分なので、50ピクセルであることが分かっています。
    * 60度の角と反対側にある辺は、**対辺 (opposite)** と呼ばれています。これが計算したい三角形の高さになります。![角と辺にラベル付けされた下向きの正三角形。上部の水平線は「隣辺」とラベル付けされている。隣接する線の中央から「対辺」とラベル付けされた垂直な点線が三角形を分割し、2つの等しい直角三角形が作成される。三角形の右辺は、「対辺」と書かれた行によって形成された直角三角形の斜辺であるため、「斜辺」と書かれる。三角形のすべての三辺の長さが同じであるのに対し、斜辺は直角三角形の最も長い辺となる。](/ja/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics/trigonometry.png)
 三角測量の基本的な公式の1つは、隣接する角度の長さに角度の正接（タンジェント）を掛けたものは、その反対側に等しいという状態です。したがって、 `50 * Math.tan(degToRad(60))` と結論づけられます。 `Math.tan()` はラジアン単位の入力値を想定しているので、 60 度をラジアンに変換するために `degToRad()` 関数を使用しています。
4. 高さが計算できたので、 `(100, 50 + triHeight)` へもう一本線を引きます。X 座標は単純で、前に設定した 2 つの X 値の中間の値でなければなりません。一方 Y 値は、三角形の頂点がキャンバスの頂点から 50 ピクセル離れていることが分かっているため、50 に三角形の高さを足した値でなければなりません。
5. 次の行は、三角形の開始点まで戻る線を描きます。
6. 最後に、 `ctx.fill()` を実行してパスを終了させ、図形を塗りつぶします。

#### 円を描く

では、キャンバスにはどのように円を描くのかを見てみましょう。これは `arc()` メソッドを使用して実現します。このメソッドは、指定した点に円の全体または一部を描画するものです。

1. キャンバスには円弧を追加してみましょう。コードの一番下に以下のように追加してください。

```js
ctx.fillStyle = "rgb(0, 0, 255)";
ctx.beginPath();
ctx.arc(150, 106, 50, degToRad(0), degToRad(360), false);
ctx.fill();
```

`arc()` は 6 つの引数を取ります。最初の 2 つは円弧の中心の位置を指定します (それぞれ X と Y)。3 番目は円の半径、4 番目と 5 番目は円を描く開始角度と終了角度を指定し（つまり 0 と 360 度を指定すると完全な円になります）、6 番目は円を反時計回りに描くか（反時計回り）時計回りに描くか（`false` は時計回り）を定義します。

**メモ:** 0度は水平方向で右側です。
2. もうひとつ、円弧を加えてみましょう。

```js
ctx.fillStyle = "yellow";
ctx.beginPath();
ctx.arc(200, 106, 50, degToRad(-45), degToRad(45), true);
ctx.lineTo(200, 106);
ctx.fill();
```

こちらのパターンもとてもよく似ていますが、 2 つの違いがあります。
    * `arc()` の最後の引数を `true` に設定しています。これは、反時計回りに弧を描くという意味で、たとえ弧が -45 度から始まって 45 度で終わるように指定されていても、この部分の内側ではなく 270 度の周囲に弧を描くということになります。もし、 `true` を `false` に変更してからコードを再実行すると、 90 度の輪切りだけが描画されます。
    * fill()`を呼び出す前に、円の中心に線を引いています。これは、パックマンスタイルの切り抜きレンダリングを得ることを意味しています。この行を削除して（試してみてください！）コードを再実行すると、円弧の開始点と終了点の間で円の端が切り落とされただけの状態になります。これは、キャンバスのもう一つの重要な点を示しています。不完全なパス（つまり、閉じられていないパス）を塗りつぶそうとすると、ブラウザーは始点と終点の間を直線で埋めてから、それを塗りつぶします。

最終的には以下のような例となります。

**メモ:** 完成したコードは、 GitHub で 3\_canvas\_paths として利用可能です。

**メモ:** ベジェ曲線などの高度なパス描画機能については、キャンバスでの図形の描画チュートリアルをご覧ください。

### テキスト

キャンバスには、テキストを描画するための機能も保有されています。これらを簡単に調べてみましょう。キャンバステンプレート（1\_canvas\_template）を新しくコピーし、そこに新しい例を描画することから始めましょう。

テキストは2つのメソッドを使用して描画されます。

* `fillText()` — 中身を塗りつぶしたテキストを描く。
* `strokeText()` — テキストの輪郭線を描く。

どちらも基本的な使い方として、描画する文字列と、テキストの描画を開始する点の X 座標と Y 座標の 3 つのプロパティを取ります。これは、**テキストボックス**（文字通り、描画するテキストを囲むボックス）の**左下**隅となりますが、他の描画処理は左上隅から開始する傾向があるので混乱するかもしれません。気を付けてください。

また、テキスト レンダリングをコントロールするのに役立つプロパティもいくつかあります。このプロパティは、CSSの`font`プロパティと同じ構文を値として受け取ります。

以下のブロックを JavaScript の最下部に追加してみてください。

```js
ctx.strokeStyle = "white";
ctx.lineWidth = 1;
ctx.font = "36px arial";
ctx.strokeText("Canvas text", 50, 50);

ctx.fillStyle = "red";
ctx.font = "48px georgia";
ctx.fillText("Canvas text", 50, 150);
```

ここでは、アウトラインとストロークの 2 つのテキストを描きます。最終的な例はこのようになるはずです。

**メモ:** 完成したコードは、 GitHub で 4\_canvas\_text として利用することができます。

どんなものが出来上がるか、遊んでみてください。キャンバスのテキストで利用できるオプションについては、テキストの描画に詳細な情報があります。

### キャンバスへの画像の描画

キャンバスには、外部の画像を描画することができます。これには、単純な画像、動画のフレーム、他にもキャンバスのコンテンツが使用できます。ここでは、キャンバス上に簡単な画像を使用する場合について説明します。

1. 前回と同様に、新しい例を描くために、キャンバステンプレート（1\_canvas\_template）を新しくコピーします。
 画像は `drawImage()` メソッドを使用してキャンバスに描画します。最も単純なものは、描画する画像の参照と、画像の左上隅の X 座標と Y 座標の 3 つの引数を取ります。
2. まず、キャンバスには埋め込む画像ソースを取得することから開始しましょう。以下の行を JavaScript の最下部に追加してください。

```js
const image = new Image();
image.src = "firefox.png";
```

ここでは `Image()` コンストラクターを使用して、新しい `HTMLImageElement` オブジェクトを作成しています。返されるオブジェクトは、既存の `<img>` 要素への参照を取得したときに返されるオブジェクトと同じ型です。次に、その `src` 属性を、Firefox ロゴ画像と同じになるように設定します。この時点で、ブラウザーは画像の読み込みを開始します。
3. ここで、`drawImage()` を使用して画像を埋め込むことができますが、最初に画像ファイルが読み込まれたことを確認する必要があり、そうでなければコードは失敗してしまいます。これは `load` イベントを使用して実現することができます。このイベントは画像の読み込みが完了したときにのみ発行されます。前のブロックの下に、以下のブロックを追加してください。

```js
image.addEventListener("load", () => ctx.drawImage(image, 20, 20));
```

今、ブラウザーで例を読み込むと、キャンバスに埋め込まれた画像が表示されるはずです。
4. でも、まだあります。 画像の一部分だけを表示したり、サイズを変更したりしたい場合はどうすればよいのでしょうか。 `drawImage()` のより複雑なバージョンを使用すれば、どちらも可能です。 `ctx.drawImage()` の行を次のように更新してください。

```js
ctx.drawImage(image, 20, 20, 185, 175, 50, 50, 185, 175);
```

    * 最初の引数は、前回と同じく画像の参照です。
    * 2 番目と 3 番目の引数は、読み込んだ画像から切り出したい領域の左上隅の座標を、画像自体の左上隅を基準にして定義します。 2 番目の引数より左側や 3 番目の引数より上側は何も描画されません。
    * 4 番目と 5 番目の引数は、読み込んだ元画像から切り出したい領域の幅と高さを定義します。
    * 6 番目と 7 番目の引数は、画像の切り出し部分の左上隅をキャンバスの左上隅と関連させて描画する座標を定義します。
    * 8 番目と 9 番目の引数は、画像の切り出し領域を描画するための幅と高さを定義するものです。今回は、元のスライスと同じ寸法を保有しましたが、異なる値を指定することでリサイズすることも可能です。

最終的な例はこのようになるはずです。

**メモ:** 完成したコードは、 GitHub で 5\_canvas\_images として利用することができます。

ループとアニメーション
-----------

ここまで、2D キャンバスのとても基本的な使用方法を説明しましたが、実際に何らかの方法で更新したりアニメーションさせたりしない限り、キャンバスの能力をフルに体験することはできません。キャンバスにはスクリプト可能な画像が指定されています。もし、何も変更しないのであれば、静止画像を使用して、すべての作業を省く方がよいでしょう。

### ループの作成

キャンバスでループを使うのはなかなか楽しいものです。他の JavaScript コードと同じように、`for` （または他の種類のの）ループの中でキャンバスのコマンドを実行することができます。

簡単な例を作ってみましょう。

1. キャンバスのテンプレート（1\_canvas\_template）を新たにコピーし、コードエディターで開くためのコードを作成します。
2. 以下の行を JavaScript の一番下に追加します。これには新しいメソッド `translate()` (en-US) が含まれていて、キャンバスの原点を移動させます。

```js
ctx.translate(width / 2, height / 2);
```

これにより、座標原点 (0, 0) は左上隅ではなく、キャンバスの中央に移されます。これは、今回のようにキャンバスの中心から相対的にデザインを描画させたい多くの状況でとても有用です。
3. ここで、 JavaScript の一番下に、以下のコードを追加してください。

```js
function degToRad(degrees) {
  return (degrees \* Math.PI) / 180;
}

function rand(min, max) {
  return Math.floor(Math.random() \* (max - min + 1)) + min;
}

let length = 250;
let moveOffset = 20;

for (let i = 0; i < length; i++) {}
```

ここでは、上の三角形の例で見たのと同じ `degToRad()` 関数、指定された下限と上限の間の乱数を返す `rand()` 関数、変数 `length` と `moveOffset` （これについては後で詳しく説明します）、そして空の `for` ループを実装しています。
4. ここでのアイディアは、 `for` ループの中でキャンバス上に何かを描くことができ、それを毎回反復処理することで、何か面白いものを作成することができるというものです。以下のコードを `for` ループの中に追加してください。

```js
ctx.fillStyle = `rgba(${255 - length},0,${255 - length},0.9)`;
ctx.beginPath();
ctx.moveTo(moveOffset, moveOffset);
ctx.lineTo(moveOffset + length, moveOffset);
const triHeight = (length / 2) \* Math.tan(degToRad(60));
ctx.lineTo(moveOffset + length / 2, moveOffset + triHeight);
ctx.lineTo(moveOffset, moveOffset);
ctx.fill();

length--;
moveOffset += 0.7;
ctx.rotate(degToRad(5));
```

つまり、各反復処理において、
    * `fillStyle` にやや透明な紫色のシェードを設定します。このシェードは `length` の値に基づいて毎回変更されます。後で分かるように、長さはループを実行するたびに小さくなるので、ここでの効果は、三角形を連続して描画するたびに色が明るくなることです。
    * パスを開始します。
    * ペンを座標 `(moveOffset, moveOffset)` に移動します。この変数は、新しい三角形を描画するたびに、どれだけの距離を移動するかを定義するものです。
    * 座標 `(moveOffset+length, moveOffset)` に直線を描画します。これは、長さ `length` の直線を X 軸に平行に描画します。
    * 先ほどと同様に、三角形の高さを計算します。
    * 三角形の下向きの角まで線を引き、それから三角形の始点に戻る線を描きます。
    * `fill()` を呼び出して、三角形を塗りつぶします。
    * 三角形の並びを記述する変数を更新し、次の三角形を描画する準備ができるようにします。 長さ`の値を 1 ずつ減らしていくので、三角形は毎回少しずつ小さくなります。 そして、`rotate()` (en-US) という別の新しい関数を使用して、キャンバス全体を回転させることができるようにします。次の三角形を描画する前に 5 度回転させています。

これで完了です。最終的な例はこのようになるはずです。

この点については、ぜひこの例で遊んでみて、自分自身で作ってみてください。例えば、

* 三角形の代わりに長方形や円弧を描画したり、画像を埋め込んだりする。
* length`と`moveOffset` の値で遊んでみる。
* 上で記載したが使用しなかった `rand()` 関数を使用して乱数を導入する。

**メモ:** 完成したコードは、GitHub で 6\_canvas\_for\_loop として利用することができます。

### アニメーション

上で作成したループの例は楽しいものでしたが、実際のところ、本格的なキャンバスアプリケーション（ゲームやリアルタイムの視覚化など）には、継続的に続く一定のループが必要です。キャンバスを映画のようなものと考えると、フレームごとに表示を更新して更新されたビューを表示する必要があります。

JavaScript で、関数を 1 秒間に数回繰り返し実行することができる関数がいくつかありますが、ここでの目的に最適なのは `window.requestAnimationFrame()` です。これは 1 つの引数を取ります - 各フレームに対して実行したい関数の名前です。次にブラウザーが画面の内側へ更新する準備ができたとき、その関数が呼び出されます。その関数がアニメーションの新しい更新を描画し、関数の終わりの直前に再び `requestAnimationFrame()` を呼び出すと、アニメーションループが実行され続けます。ループは `requestAnimationFrame()` の呼び出しをやめるか、呼び出した後、フレームが呼び出される前に `window.cancelAnimationFrame()` を呼び出すと終わりになります。

**メモ:** アニメーションを使用し終わったら、メインコードから `cancelAnimationFrame()` を呼び出して、まだ実行されるのを待っている更新がないことを確認するのは良い習慣です。

ブラウザーは、アニメーションを一定の速度で実行したり、見えないものをアニメーションさせるのに無駄なリソースを使わないようにするなど、複雑な細部にわたって作業しています。

これらがどのように動作するかを確認するために、弾むボールの例を手早くもう一度見てみましょう（ライブを見る、ソースコードも参照してください）。すべてのものが移動し続けるループのコードは次のようになります。

```js
function loop() {
  ctx.fillStyle = "rgba(0, 0, 0, 0.25)";
  ctx.fillRect(0, 0, width, height);

  for (const ball of balls) {
    ball.draw();
    ball.update();
    ball.collisionDetect();
  }

  requestAnimationFrame(loop);
}

loop();
```

コードの一番下で `loop()` 関数を一度実行してサイクルを開始し、最初のアニメーションフレームを描画します。`loop()` 関数は次に `requestAnimationFrame(loop)` を呼び出して、アニメーションの次のフレームを何度も何度も実行する役割を果たします。

各フレームでキャンバスを完全にクリアして、すべてを再描画していることに注意してください。存在するすべてのボールについて描画し、位置を更新して、他のボールと衝突していないかどうかを調べています。キャンバスにグラフィックを描画したら、DOM 要素のようにそのグラフィックを個別に操作する方法はありません。描画されたボールはキャンバスの一部であり、個別にアクセスできる要素やオブジェクトではないため、キャンバス上でそれぞれのボールを移動させることはできません。フレーム全体を消去してすべてを再描画するか、どの部分を消去する必要があるかを正確に把握し、キャンバスの必要最小限の領域だけを消去して再描画するコードを持つか、どちらかの方法で消去と再描画を行う必要があります。

グラフィックのアニメーションの最適化は、プログラミングの一つの専門分野であり、たくさんの賢いテクニックが利用できます。しかし、この例では、そのような技術は必要ありません。

一般に、キャンバスでアニメーションを行うには、以下のような段階を踏みます。

1. キャンバスの中身を消去します（例： `fillRect()` や `clearRect()` などで）。
2. （必要に応じて）`save()` で状態を保存します。これは、キャンバス上で更新した設定を保存してから継続したい場合に必要で、より高度なアプリケーションに有用です。
3. アニメーションさせるグラフィックを描画します。
4. 手順 2 で保存した設定を `restore()` (en-US) を使用して復元します。
5. `requestAnimationFrame()` を呼び出して、アニメーションの次のフレームを描画するように予約します。

**メモ:** ここでは `save()` と `restore()` は取り上げませんが、これらは座標変換のチュートリアル (とそれに続くもの) でうまく説明されています。

### 簡単なキャラクターのアニメーション

それでは、自分自身で簡単なアニメーションを作ってみましょう。ある素晴らしいレトロコンピュータゲームのキャラクターが、画面内を歩き回るようにします。

1. キャンバスのテンプレート (1\_canvas\_template) の新しいコピーを作成し、コードエディターで開いてください。
2. JavaScript の一番下に、以下の行を追加して、座標原点が再びキャンバスの中央に来るようにします。

```js
ctx.translate(width / 2, height / 2);
```

3. では、新しい `HTMLImageElement` オブジェクトを作成し、その `src` に読み込ませたい画像を設定しましょう。そして、`onload` イベントハンドラーを追加して、画像を読み込んだら `draw()` 関数が起動するよう設定しましょう。

```js
const image = new Image();
image.src = "walk-right.png";
image.onload = draw;
```

4. 次に、スプライトが画面上に描画される位置と、表示したいスプライトの番号を記録するための変数を追加します。

```js
let sprite = 0;
let posX = 0;
```

スプライトシートの画像（Mike Thomas 氏の Walking cycle using CSS animation CodePen から謹んで拝借しました）について説明します。画像はこのような感じです。

 ![歩く人を模したドット絵のキャラクターを、右側から一歩ずつ異なる手順で描いたスプライト画像 6 枚を収録したスプライト・シートです。キャラクターは、水色のボタンのついた白いシャツ、黒いズボン、黒い靴を保有しています。各スプライトは、幅 102 ピクセル、高さ 148 ピクセルです。](/ja/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics/walk-right.png)
 これには 6 つのスプライトが含まれており、それぞれが幅 102 ピクセル、高さ 148 ピクセルで、歩行シーケンス全体を構成しています。それぞれのスプライトをきれいに表示するためには、上で Firefox のロゴに対して行ったように、 `drawImage()` を使用してスプライトシートから単一のスプライト画像を切り出し、その部分のみを表示する必要があります。スライスの X 座標は 102 の倍数でなければならず、Y 座標は常に 0 でなければなりません。
5. では、コードの一番下に空の `draw()` 関数を挿入して、いくつかのコードで埋められるようにしましょう。

```js
function draw() {}
```

6. この節の残りのコードは `draw()` の中に記述します。まず、以下の行を追加します。これはキャンバスで各フレームを描画するための準備をするものです。長方形の左上隅を `-(width/2), -(height/2)` と指定しなければならないことに注意してください先ほど原点位置を `width/2, height/2` と指定したためです。

```js
ctx.fillRect(-(width / 2), -(height / 2), width, height);
```

7. 次に、drawImage の引数 9 個版を使用して画像を描画します。従うことで以下が追加されます。

```js
ctx.drawImage(image, sprite \* 102, 0, 102, 148, 0 + posX, -74, 102, 148);
```

見ての通りです。
    * 埋め込む画像として `image` を指定します。
    * 2 番目と 3 番目の引数は、ソース画像から切り出すスライスの左上隅を指定します。 X 値は `sprite` に 102 を掛けた値（ `sprite` は 0 から 5 までのスプライト番号）、 Y 値は常に 0 を指定します。
    * 4 番目と 5 番目の引数は、切り出すスライスの大きさ（102 ピクセル× 148 ピクセル）を指定します。
    * 6 番目と 7 番目の引数は、キャンバス上にスライスを描画するボックスの左上隅を指定します。 X 位置は 0 + `posX` で、`posX` 値を変更することにより描画位置を変更できることを意味しています。
    * 8 番目と 9 番目の引数は、キャンバスで表示する画像の大きさを指定します。元の大きさを維持したいだけなので、幅と高さを 102 と 148 に指定しています。
8. 今度は、描画するたびに `sprite` の値を変更します。まあ、とにかくいくつかの描画の後で。以下のブロックを `draw()` 関数の末尾に追加してください。

```js
if (posX % 13 === 0) {
  if (sprite === 5) {
    sprite = 0;
  } else {
    sprite++;
  }
}
```

ブロック全体を `if (posX % 13 === 0) { }` で囲んでいます。モジュロ演算子(`%`)（剰余演算子とも呼ばれます）を使用して、 `posX` の値が余りなく 13 で正確に割り切れるかどうかを調べています。もしそうなら、 `sprite` を増加して次のスプライトに移動します（スプライト #5 が終わったら 0 に戻ります）。これは事実上、 13 フレームごとにしかスプライトを更新していないことを意味し、およそ 1 秒間に 5 フレーム程度です（`requestAnimationFrame()` では、可能であれば 1 秒間に 60 フレームまで呼び出されます）。フレームレートを意図的に遅くしているのは、作業するスプライトが 6 つしかないためで、 60 分の 1 秒ごとに 1 つずつ表示すると、キャラクターの移動が速くなりすぎてしまうからです。
 外側のブロックの中では `if...else` 文を使用して、 `sprite` の値が 5 （0 から 5 までのスプライト番号が実行されている最後のスプライト）であるかどうかを調べています。もし、すでに最後のスプライトを示していれば、 `sprite` を 0 に戻し、そうでなければ、 1 だけ増加させます。
9. 次に、各フレームで `posX` 値を変更する方法を作業する必要があります。次のコードブロックを、前回のコードのすぐ下に追加してください。

```js
if (posX > width / 2) {
  let newStartPos = -(width / 2 + 102);
  posX = Math.ceil(newStartPos);
  console.log(posX);
} else {
  posX += 2;
}
```

別の `if...else` 文を使用して、 `posX` の値が `width/2` よりも大きくなったかどうか、つまりキャラクターが画面の右端から歩き出したかどうかを確認しています。もしそうなら、キャラクターが画面の左側の辺のすぐ左に来るように位置を計算します。
 キャラクターがまだ画面の端に達していない場合は、 `posX` を 2 だけ増加させます。これは、次に彼を描画するときに、彼を少し右側に移動させます。
10. 最後に、アニメーションをループさせるために、 `draw()` 関数の一番下に `requestAnimationFrame()` を呼び出す必要があります。

```js
window.requestAnimationFrame(draw);
```

これで完了です。最終的な例はこのようになるはずです。

**メモ:** The finished code is available on GitHub as 7\_canvas\_walking\_animation.

### 簡単なお絵描きアプリ

最後のアニメーションの例として、とてもシンプルな描画アプリケーションを示し、アニメーションループとユーザー入力（この場合はマウスの動きなど）をどのように組み合わせられるかを説明したいと思います。このアプリケーションを実際に作ってみることはせず、コードの最も注目される部分だけを見ていきます。

この例は GitHub で 8\_canvas\_drawing\_app として提供されており、以下からライブで動かすことができます。

最も興味深い部分を見ていきましょう。まず最初に、マウスの X と Y の座標と、クリックされているかどうかを `curX`、`curY`、`pressed` という 3 つの変数で記録しています。マウスが移動したら、 `mousemove` イベントハンドラーとして設定された関数を発行し、現在の X と Y の値を取得します。また、`mousedown` と `mouseup` イベントハンドラーを使用して、マウスボタンが押されたときに `pressed` の値を `true` に変更し、離したときに再び `false` に戻しています。

```js
let curX;
let curY;
let pressed = false;

// マウスポインターの座標を更新する
document.addEventListener("mousemove", (e) => {
  curX = e.pageX;
  curY = e.pageY;
});

canvas.addEventListener("mousedown", () => (pressed = true));

canvas.addEventListener("mouseup", () => (pressed = false));
```

"Clear canvas" ボタンが押されたら、キャンバス全体を黒に戻す簡単な関数を実行します。これは、これまでと同じ方法です。

```js
clearBtn.addEventListener("click", () => {
  ctx.fillStyle = "rgb(0,0,0)";
  ctx.fillRect(0, 0, width, height);
});
```

描画ループは今回はとてもシンプルです。pressed が `true` の場合、カラーピッカーの値に等しい塗りつぶしスタイルと、範囲入力で設定した値に等しい半径を持つ円を描画します。垂直方向の測定はビューポートの上部から行われるため、円を測定した位置から 85 ピクセル上に描画する必要がありますが、キャンバスの上部から相対的に円を描いているので、ツールバーの高さ 85 ピクセルよりも下から始まっています。もし、 y 座標として `curY` だけで描画した場合、マウス位置より 85 ピクセル低い位置に現れることになります。

```js
function draw() {
  if (pressed) {
    ctx.fillStyle = colorPicker.value;
    ctx.beginPath();
    ctx.arc(
      curX,
      curY - 85,
      sizePicker.value,
      degToRad(0),
      degToRad(360),
      false,
    );
    ctx.fill();
  }

  requestAnimationFrame(draw);
}

draw();
```

**メモ:** `<input>` の `range` および `color` 型は、 Internet Explorer のバージョン 10 より前を除いて、すべてのブラウザーが対応しています。また、 Safari も `color` に対応していません。もしブラウザーがこれらの入力型に対応していない場合は、単純なテキストフィールドに戻り、有効な色や数の値を自分で入力する必要があります。

WebGL
-----

さて、そろそろ 2D を離れ、3D キャンバスについて簡単に見てみましょう。3D キャンバスの中身は WebGL API を使用して指定します。これは 2D キャンバス API とは完全に別の API ですが、どちらも `<canvas>` 要素に描画されます。

WebGL は OpenGL (Open Graphics Library) に基づいており、コンピューターの GPU と直接通信することが可能です。そのため、生の WebGL を書くことは、通常の JavaScript よりも C++ のような低レベル言語に近く、非常に複雑ですが非常に強力です。

### ライブラリーの使用

その複雑さから、ほとんどの人は Three.js, PlayCanvas, Babylon.js などのサードパーティの JavaScript ライブラリーを使用して 3D グラフィックのコードを記述しています。これらのほとんどは同じように動作し、基本的な図形やカスタム図形を作成する機能、表示カメラや照明を配置する機能、テクスチャで表面を覆う機能などを提供します。これは、あなたに代わって WebGL を処理し、より高いレベルで作業できるようにしてくれます。

これらの API を使用することは、別の新しい API（この場合はサードパーティーの API）を学習することを意味しますが、生の WebGL を使用するよりもはるかに簡単な方法です。

### 立方体の作成

WebGLライブラリーを使って何かを作成する簡単な例を見てみましょう。ここでは、最も人気のあるものの 1 つである Three.js を選んでみましょう。このチュートリアルでは、先ほど見た 3D の回転する立方体を作成します。

1. まず、新しいフォルダーに threejs-cube/index.html をローカルにコピーし、同じフォルダーに metal003.png をコピーして保存してください。これは、後で立方体の表面テクスチャとして使用する画像です。
2. 次に、 `script.js` というファイルを新規に作成し、これも先ほどと同じフォルダーに保存します。
3. 次に、 three.min.js ライブラリーをダウンロードして、先ほどと同じディレクトリーに保存する必要があります。
4. これで `three.js` をページに持つことができたので、これを使用する JavaScript を `script.js` に書き始めることができます。まず、新しいシーンを作成することから始めましょう。 main.js ファイルに以下のように追加してください。

```js
const scene = new THREE.Scene();
```

`Scene()` コンストラクターは、表示しようとする 3D の世界全体を表す新しいシーンを作成します。
5. 次に、シーンを見るための**カメラ**が必要です。 3D 画像の用語では、カメラは世界における視聴者の位置を表します。カメラを作成するには、以下の行を次に追加してください。

```js
const camera = new THREE.PerspectiveCamera(
  75,
  window.innerWidth / window.innerHeight,
  0.1,
  1000,
);
camera.position.z = 5;
```

`PerspectiveCamera()` コンストラクターは 4 つの引数を取ります。
    * 視野角です。カメラの前方で画面に表示されるべき領域の広さを度数で表します。
    * アスペクト比です。通常、これはシーンの幅をシーンの高さで割った比率です。別の値を使用すると、シーンが歪みます（それが狙いかもしれませんが、通常はそうではありません）。
    * 前方平面です。カメラにどれだけ近づけば、オブジェクトを画面に描画しなくなるか、を指定します。指先を目と目の間の空間にどんどん移動させていくと、やがて見えなくなることを思い浮かべてください。
    * 後方平面です。カメラからどのくらい離れれば描画されなくなるかを指定します。また、カメラの位置は CSS と同じく、画面から視聴者である自分に向けて、 Z 軸から距離単位で 5 つ外側になるように設定しています。
6. 3 つ目の重要な要素は「レンダラー」です。これは指定されたカメラを通して見た、指定されたシーンをレンダリングするオブジェクトです。 `WebGLRenderer()` コンストラクターを使ってとりあえず作成しておきますが、これは後ほど使用することにします。以下の行を次に追加します。

```js
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
```

最初の行では新しいレンダラーを作成し、 2 つ目の行ではレンダラーがカメラのビューを描画するサイズを設定し、 3 つ目の行ではレンダラーが作成した `<canvas>` 要素を文書内の `<body>` に追加しています。これで、レンダラーが描画した何らかのものがウィンドウに表示されるようになります。
7. 次に、キャンバス上に表示する立方体を作成します。JavaScript で以下のコードを追加してください。

```js
let cube;

const loader = new THREE.TextureLoader();

loader.load("metal003.png", (texture) => {
  texture.wrapS = THREE.RepeatWrapping;
  texture.wrapT = THREE.RepeatWrapping;
  texture.repeat.set(2, 2);

  const geometry = new THREE.BoxGeometry(2.4, 2.4, 2.4);
  const material = new THREE.MeshLambertMaterial({ map: texture });
  cube = new THREE.Mesh(geometry, material);
  scene.add(cube);

  draw();
});
```

まだ少しやることがあるので、順を追って考えてみましょう。
    * 最初に `cube` グローバル変数を作成し、コード内の任意の場所から立方体にアクセスできるようにします。
    * 次に、新しい `TextureLoader` オブジェクトを作成し、 `load()` を呼び出します。 `load()`はこの場合、 2 つの引数を取ります（もっと取ることもできますが）。読み込むテクスチャ (PNG) と、テクスチャが読み込まれたときに実行される関数です。
    * この関数の内部では、`texture` オブジェクトのプロパティを使用して、立方体のすべての辺に 2 x 2 の画像の繰り返しを回り込ませるように指定しています。次に、新しい `BoxGeometry` オブジェクトと新しい `MeshLambertMaterial` オブジェクトを作成し、それらを `Mesh` で結合して立方体を作成しています。オブジェクトは通常、形状（どのような形か）と素材（表面がどのように見えるか）を必要とします。
    * 最後に、立方体をシーンに追加し、 `draw()` 関数を呼び出して、アニメーションを開始します。
8. `draw()` の定義に入る前に、シーンを少し盛り上げるために、ライトをいくつか追加します。以下、ブロックを追加していきます。

```js
const light = new THREE.AmbientLight("rgb(255,255,255)"); // soft white light
scene.add(light);

const spotLight = new THREE.SpotLight("rgb(255,255,255)");
spotLight.position.set(100, 1000, 1000);
spotLight.castShadow = true;
scene.add(spotLight);
```

`AmbientLight` オブジェクトは、シーン全体を少し明るくするソフトライトのようなもので、外にいるときの太陽のようなものです。一方、`SpotLight` オブジェクトは、指向性のある光線で、懐中電灯やトーチ（実際にはスポットライト）のようなものです。
9. 最後に、コードの一番下に `draw()` 関数を追加しましょう。

```js
function draw() {
  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  renderer.render(scene, camera);

  requestAnimationFrame(draw);
}
```

これはかなり直感的です。各フレームで立方体を X 軸と Y 軸でわずかに回転させ、カメラで見たシーンを描画し、最後に `requestAnimationFrame()` を呼び出して次のフレームを描くスケジュールを決めています。

もう一度、完成品のイメージをすばやく把握しておきましょう。

完成したコードは GitHub で見つけることができます。

**メモ:** GitHub リポジトリーには、もうひとつ興味深い 3D 立方体の例である Three.js Video Cube (see it live also) も掲載されています。これは `getUserMedia()` を使用して、コンピューターのウェブカメラから動画ストリームを受け取り、それをテクスチャとして立方体の横に投影しています。

まとめ
---

この時点で、Canvas と WebGL を使用したグラフィック プログラミングの基礎と、これらの API を使用してできることについて有益な考えを持ち、さらに詳しい情報を得るにはどこに行けばよいかについても十分な考えを持つことができるようになっているはずです。楽しんでください。

関連情報
----

キャンバスには、まだまだ学ぶべきことがたくさんあります。この記事は、その先を導いてくれるでしょう。

* キャンバスのチュートリアル — 2D キャンバスについて知っておくべきことを、ここで扱った内容よりもとても詳しく説明しているチュートリアルシリーズです。必読です。
* WebGL のチュートリアル — 生の WebGL プログラミングの基本を学ぶシリーズ。
* Three.js を使った基本的なデモの作成 — Three.js の基本的なチュートリアルです。 PlayCanvas や Babylon.js にも同等のガイドがあります。
* ゲーム開発 — は、 MDN でウェブゲームを開発するためのランディングページです。ここには、 2D および 3D キャンバスに関する実に有益なチュートリアルやテクニックがあります。「テクニックとチュートリアル」メニューオプションをご覧ください。
例
-

* Violent theremin — 音声を生成するためにウェブオーディオ API を使用し、それと一緒に美しく視覚化するためにキャンバスを使用します。
* Voice change-o-matic — ウェブオーディオ API からのリアルタイムの音声データを視覚化するためにキャンバスを使用します。
* 前のページ
* Overview: Client-side web APIs
* 次のページ


クライアント側ストレージ - ウェブ開発を学ぶ

クライアント側ストレージ
============

* 前のページ
* Overview: Client-side web APIs

現代のブラウザーは、ウェブサイトがユーザーの許可を得た上で、ユーザーのコンピューターにデータを格納し、必要なときにそれを取得するためのさまざまな方法に対応しています。これにより、データを長期保存したり、サイトや文書をオフラインで使用するために保存したり、サイトのユーザー固有の設定を保持したりと、さまざまなことが可能になります。この記事では、これらがどのように動作するのか、ごく基本的なことを説明します。

|  |  |
| --- | --- |
| 前提知識: |
 JavaScript の基本
 （第一歩、
 構成要素,
 JavaScript のオブジェクト）、
 クライアントサイド API の基本 |
| 目標: | アプリケーションデータを格納するために、クライアント側ストレージ API を使用する方法について学ぶ。 |

クライアント側ストレージとは
--------------

MDN 学習領域の他の場所で、静的サイトと動的サイトの違いについて説明しました。現代の主要なウェブサイトのほとんどは動的です。何らかのデータベース（サーバー側ストレージ）を使用してサーバーにデータを格納し、サーバー側コードを動作させて必要なデータを取得し、静的なページテンプレートに挿入し、結果の HTML をクライアントに提供してユーザーのブラウザーで表示させています。

クライアント側ストレージは、同様の原理で動作しますが、用途は異なります。これは、クライアント（つまりユーザーのマシン）上にデータを格納し、必要なときにそれを取得することを可能にする JavaScript API で構成されています。これには、以下のような多くの明確な用途があります。

* サイトの環境設定のパーソナライズ（ユーザーが選択したカスタムウィジェット、配色、フォントサイズの表示など）。
* 前回のサイト活動の維持（前回のセッションからショッピングカートの内容を格納する、ユーザーが以前ログインしていたかどうかを記憶する、など）。
* データや資産をローカルに保存することで、サイトのダウンロードを高速化（低コスト化）したり、ネットワーク接続がなくても使用できるようにしたりする。
* ウェブアプリケーションで作成した文書をオフラインで使用するための、ローカルへの保存

クライアント側とサーバー側のストレージが一緒に使用されることがよくあります。例えば、音楽ファイル（ウェブゲームや音楽プレーヤーアプリケーションで使用できる）を一括してダウンロードし、クライアント側のデータベースに格納し、必要に応じて再生できるようにすることができます。ユーザーが音楽ファイルをダウンロードするのは最初だけで、その後でアクセスした場合はデータベースから取得することになります。

**メモ:** クライアント側ストレージ API を使用して格納できるデータの量には上限があります（おそらく個々の API ごとと、総量の両方）。正確な上限はブラウザーによって異なり、またユーザーの設定に基づく可能性もあります。詳細な情報はブラウザーのストレージ制限と削除基準を参照してください。

### 古い流儀: クッキー

クライアント側ストレージの概念は、昔からありました。ウェブの初期から、サイトはクッキーを使用して、ウェブサイトでのユーザー操作をパーソナライズするために情報を格納してきました。これは、ウェブで一般的に使用されているクライアント側ストレージの最も初期の形態です。

最近では、クライアント側のデータを格納するために利用できるより簡単な仕組みがあるため、この記事ではクッキーを使用する方法を教えません。しかし、これはクッキーが現代のウェブで完全に無用であるという意味ではありません。クッキーは、セッション ID やアクセストークンのような、ユーザーの個人設定や状態に関連するデータを格納するために、今でも一般的に使用されています。クッキーについての詳細情報は、 HTTP クッキーの使用の記事を参照してください。

### 新しい流儀: ウェブストレージと IndexedDB

上で触れた「簡単な」機能には、以下のものがあります。

* ウェブストレージ API は、名前とそれに対応する値からなる、より小さなデータ項目を格納し、取得するための仕組みを提供します。これは、ユーザーの名前、ログインしているかどうか、画面の背景で使う色など、簡単なデータだけを格納する必要がある場合に有用です。
* IndexedDB API は、複雑なデータを格納するための完全なデータベースシステムをブラウザーに提供します。これは、顧客レコードの完全な集合から、音声ファイルや動画ファイルのような複雑なデータ型にまで使用することができます。

以下ではこれらの API について学ぶことになります。

### キャッシュ API

キャッシュ API は、特定のリクエストに対する HTTP レスポンスを格納するために設計されており、ウェブサイトの資産をオフラインで格納し、その後ネットワーク接続なしでサイトを使用できるようにするようなことを行うのに、とても有用です。 Cache は通常、サービスワーカー API と組み合わせて使用されますが、必ずしもそうでなければならないというわけではありません。

キャッシュとサービスワーカーの使用は高度なトピックであるため、この記事ではあまり詳しく説明しませんが、以下のオフライン資産ストレージのセクションで例を示す予定です。

単純なデータの保存 — ウェブストレージ
--------------------

ウェブストレージ API はとても簡単に使えます。（文字列や数などに限定された）データからなる単純な名前／値のペアを保存し、必要なときにその値を取り出します。

### 基本的構文

以下に方法を示しましょう。

1. まず、GitHub 上のウェブストレージの空白テンプレートへ行ってください（新規タブで開いてください）。
2. ブラウザーの開発者ツールの JavaScript コンソールを開いてください。
3. ウェブストレージのデータはすべて、ブラウザーの中にある 2 つのオブジェクト風の構造、 `sessionStorage` と `localStorage` の中に入っています。前者は、ブラウザーを開いている間だけデータを維持し（ブラウザーを閉じるとデータは失われる）、後者はブラウザーを閉じてから再び開いた後でもデータを維持するものです。一般的には後者の方が有用なので、この記事では後者を使用することにします。
 `Storage.setItem()` メソッドによって、ストレージ内にデータ項目を保存できます。このメソッドは 2 つの引数をとります。すなわち、その項目の名前と、その値です。JavaScript コンソールに以下のように打ち込んでみてください（もし良ければ、値は自分の名前に変更してください）。

```js
localStorage.setItem("name", "Chris");
```

4. `Storage.getItem()` メソッドは 1 つの引数をとります。つまり、取り出したいデータ項目の名前です。そして、このメソッドは、その項目の値を返します。今度は JavaScript コンソールに以下の行を打ち込んでください。

```js
let myName = localStorage.getItem("name");
myName;
```

2 行目を入力すると、`myName` という変数が今や `name` というデータ項目の値を保有していることが分かるはずです。
5. `Storage.removeItem()` メソッドは 1 つの引数をとります。つまり、削除したいデータ項目の名前です。このメソッドは、ウェブストレージからその項目を削除します。 JavaScript コンソールに以下の行を打ち込んでください。

```js
localStorage.removeItem("name");
myName = localStorage.getItem("name");
myName;
```

3 行目は、今度は `null` を返すはずです。というのも、もはや `name` という項目はウェブストレージ内に存在しないからです。
### データの持続

ウェブストレージの一つの重要な特徴は、ページの読み込みまたいで（さらに、`localStorage` の場合には、ブラウザーを終了させた場合も）データが持続する、という点です。この点を見てみましょう。

1. もう一度、ウェブストレージの空白テンプレートを開いてください。ただし今回は、本チュートリアルを開いたのとは別のブラウザーで開いてください。こうすることで、扱いやすくなります。
2. 以下の行をブラウザーの JavaScript コンソールに打ち込んでください。

```js
localStorage.setItem("name", "Chris");
let myName = localStorage.getItem("name");
myName;
```

name という項目が返されるのが分かるはずです。
3. さてここでブラウザーを終了させてから再び起動して開いてください。
4. 再び、以下の行を入力してください。

```js
let myName = localStorage.getItem("name");
myName;
```

ブラウザーを終了させてから再び開いたのに、それでも値が利用可能である、ということが分かるはずです。
### ストレージはドメインごとに独立

データストアはドメインごと（ブラウザーに読み込まれるウェブアドレスが別個のものごと）に用意されています。 2 つのウェブサイト（例えば google.com と amazon.com）を読み込んで、 1 つのウェブサイトにアイテムを格納しようとすると、それは他のウェブサイトでは利用できないことが分かるでしょう。

これは理にかなっています。もしウェブサイトがお互いのデータを見ることができたら、セキュリティ上の問題が発生することは想像に難くありません。

### より踏み込んだ例

この新しく得た知識を応用して、ウェブストレージがどのように使用できるかを知るために、 動作する例を書いてみましょう。この例では、名前を入力すると、ページが更新された後でパーソナライズされた挨拶ができるようになります。名前はウェブストレージに格納されているので、この状態はページやブラウザーが再読み込みされたときにも維持されます。

この例の HTML を personal-greeting.html で入手できます。これは、ヘッダーとコンテンツとフッターを備えた簡素なウェブサイトと、名前を入力するためのフォームとを含みます。

![ヘッダー、コンテンツ、フッターのセクションがあるウェブサイトのスクリーンショットです。ヘッダーは、左側にウェルカムテキスト、右側に「忘れる」とラベル付けされたボタンがあります。コンテンツには、見出しと、それに続く2段落のダミーテキストがあります。フッターには、 'Copyright nobody. Use the code as you like' とあります。](/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/web-storage-demo.png)

この例を組み上げましょう。すると、これがどのように機能するのか理解できるでしょう。

1. まず、自分のコンピューターの新しいディレクトリーに、 personal-greeting.html ファイルのローカルコピーを作成してください。
2. 次に、 HTML が JavaScript ファイルの `index.js` を `<script src="index.js" defer></script>` のような行で参照している方法に注意してください。このファイルを作成し、 JavaScript のコードをそこに書く必要があります。 HTML ファイルと同じディレクトリーに `index.js` ファイルを作成してください。
3. まず、この例で操作する必要のあるすべての HTML の機能への参照を作成します。これらの参照はアプリのライフサイクルで変更する必要がないため、すべて定数として作成します。以下の行を JavaScript ファイルに追加してください。

```js
// 必要な定数を作成
const rememberDiv = document.querySelector(".remember");
const forgetDiv = document.querySelector(".forget");
const form = document.querySelector("form");
const nameInput = document.querySelector("#entername");
const submitBtn = document.querySelector("#submitname");
const forgetBtn = document.querySelector("#forgetname");

const h1 = document.querySelector("h1");
const personalGreeting = document.querySelector(".personal-greeting");
```

4. 次は、送信ボタンが押されたときにフォームが実際に送信されるのを阻止するために、小さなイベントリスナーを記載する必要があります。これは私たちが望むような動作ではないからです。前回のコードの下に、次のスニペットを追加してください。

```js
// ボタンが押されたときにフォームを送信しないようにする
form.addEventListener("submit", (e) => e.preventDefault());
```

5. 次に、イベントリスナーを追加する必要があります。このイベントリスナーのハンドラー関数は、"Say hello" ボタンがクリックされたときに動作するようにします。コメントでそれぞれが何を行うか詳細に説明していますが、要するにここではユーザーがテキスト入力ボックスに入力した名前を受け取り、 `setItem()` を使用してウェブストレージに保存し、次に `nameDisplayCheck()` という関数を呼び出して実際のウェブサイトの更新を処理しているのです。これをコードの一番下に追加してください。

```js
// 'Say hello' ボタンがクリックされたら関数を実行する
submitBtn.addEventListener("click", () => {
  // 入力された名前をウェブストレージに保存
  localStorage.setItem("name", nameInput.value);
  // nameDisplayCheck() を動作させ、パーソナライズされた挨拶の表示と、
  // フォームの表示を更新する
  nameDisplayCheck();
});
```

6. この時点で、"Forget" ボタンがクリックされたときに関数を実行するためのイベントハンドラーも必要です。 "Forget" ボタンは、 "Say hello" ボタンがクリックされた後にのみ表示されます（二つのフォーム状態が行ったり来たり切り替わります）。この関数では、`removeItem()` を用いてウェブストレージから `name` という項目を削除し、その後、表示を更新するために `nameDisplayCheck()` を再び実行します。これを末尾に付け加えてください。

```js
// 'Forget' ボタンがクリックされたら関数を実行する
forgetBtn.addEventListener("click", () => {
  // 保存してある名前をウェブストレージから削除
  localStorage.removeItem("name");
  // 再び nameDisplayCheck() を実行して、一般的な挨拶を表示するとともに
  // フォーム表示を更新する
  nameDisplayCheck();
});
```

7. 次に `nameDisplayCheck()` 関数そのものを定義する時が来ました。ここでは、条件テストとして `localStorage.getItem('name')` を使用して、 name 項目がウェブストレージに格納されているかどうかを調べています。 name が格納されていれば、この呼び出しを `true` と評価し、格納されていなければ、`false` と評価します。呼び出しが `true` と評価された場合、パーソナライズされた挨拶を表示し、フォームの "forget" 部分を表示し、フォームの "Say hello" 部分は非表示にします。呼び出した結果が `false` ならば、一般的な挨拶を表示し、その逆の処理を行います。再び、以下に示すコードを一番下に記述する。

```js
// nameDisplayCheck() という関数を定義する
function nameDisplayCheck() {
  // 'name' というデータ項目がウェブストレージに保存されているかどうかを調べる
  if (localStorage.getItem("name")) {
    // もし保存されていたら、個人に合わせた挨拶を表示
    const name = localStorage.getItem("name");
    h1.textContent = `Welcome, ${name}`;
    personalGreeting.textContent = `Welcome to our website, ${name}! We hope you have fun while you are here.`;
    // フォームのうち 'remember' の部分を隠し、'forget' の部分を表示
    forgetDiv.style.display = "block";
    rememberDiv.style.display = "none";
  } else {
    // もし保存されていなければ、一般的な挨拶を表示
    h1.textContent = "Welcome to our website ";
    personalGreeting.textContent =
      "Welcome to our website. We hope you have fun while you are here.";
    // フォームのうち 'forget' の部分を隠し、'remember' の部分を表示
    forgetDiv.style.display = "none";
    rememberDiv.style.display = "block";
  }
}
```

8. 最後になりますが、ページが読み込まれたときに `nameDisplayCheck()` 関数を実行する必要があります。これを行わないと、パーソナライズされた挨拶文はページの再読み込み時に維持されません。コードの一番下に以下のように追加してください。

```js
nameDisplayCheck();
```

例が完成しました。よくできましたね! 現時点で残っているのは、コードを保存して HTML ページをブラウザーでテストすることだけです。ライブ実行される完成版をここで見られます。

**メモ:** ウェブストレージ API の使用のところには、探究するにはほんの少しだけ更に複雑な別の例もあります。

**メモ:** 完成版のソースのうち `<script src="index.js" defer></script>` という行では、`defer` 属性により、ページを読み込み終わるまでは `<script>` 要素の中身を実行しないように指定しています。

複雑なデータを保存する — IndexedDB
-----------------------

IndexedDB API （ときには IDB と省略します）は、ブラウザーで利用可能であり、複雑で関係性のあるデータを保存できる、完全なデータベースシステムです。そのデータの型は、文字列または数値のような単純な値に限定されません。動画や静止画像、そして、その他のものもほとんどすべて、 IndexedDB インスタンスに保存することができます。

しかし、これには代償があります。IndexedDB はウェブストレージ API よりも使うのがはるかに複雑です。この節では、実際にそれが可能なことの表面に触れるだけですが、 始めるには十分な情報を提供します。

### メモのストレージの例での作業

ここでは、ブラウザーの中にメモを格納し、好きな時に閲覧・削除できるようにする例を実行し ましょう。自分で構築してもらい、IDB の最も基本的な部分を説明しながら、進めていきます。

アプリは、次のような外見です。

![IndexDBメモのデモ画面では、4つのセクションがあります。最初のセクションはヘッダーです。2つ目のセクションは、作成されたすべてのメモを掲載しています。2 つのメモがあり、それぞれに削除ボタンがあります。3 つ目のセクションはフォームで、'Note title' と 'Note text' の 2 つの入力欄と 'Create new note' と書かれたボタンがあります。一番下のセクションのフッターには、'Copyright nobody. Use the code as you like' とあります。](/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/idb-demo.png)

メモ書きの各々には題名と何らかの本文があり、題名と本文のそれぞれは別々に編集できます。以下で見てゆく JavaScript コードには、何が起きているのかを理解する手助けとなる詳しいコメントがあります。

### 始めましょう

1. まず、`index.html`、 `style.css`、 `index-start.js` というファイルのローカルコピーを、ローカルマシンの新規ディレクトリー内に作成してください。
2. ファイルを見てください。 HTML は、ヘッダーとフッター、そしてメモを表示する場所とデータベースに新しいメモを入力するフォームを含むメインコンテンツエリアを持つウェブサイトを定義していることがわかるでしょう。 CSS は、何が起こっているかを明確にするために、いくつかのスタイル設定を提供しています。 JavaScript ファイルには、メモが表示される `<ul>` 要素、タイトルと本文の `<input>` 要素、 `<form>` 自体、および `<button>` への参照を含む 5 つの宣言済み定数が含まれています。
3. JavaScript ファイルの名前を `index.js` に変更してください。コードをそこに追加し始める準備がこれで整いました。
### データベースの初期設定

では、実際にデータベースを設定するには、最初の段階で何をしなければならないかを見てみましょう。

1. 定数の宣言の下に、以下の行を追加してください。

```js
// 開いているデータベースを格納するために、 db オブジェクトのインスタンスを作成します。
let db;
```

ここでは、`db` と呼ばれる変数を宣言しています。これは後に、データベースを表すオブジェクトを記憶するのに使われます。この変数を何箇所かで使うつもりなので、物事を容易にするために、ここでこの変数を大域的に宣言しておきました。
2. 次に、以下のものをコードの末尾に加えてください。

```js
// データベースを開きます。データベースがまだ存在しない場合は作成されます。
// （後述の upgradeneed ハンドラーを参照）。
const openRequest = window.indexedDB.open("notes\_db", 1);
```

この行は、 `notes_db` というデータベースのバージョン `1` を開くためのリクエストを作成します。もしこれがまだ存在しない場合は、後続のコードで作成されます。このリクエストパターンは、 IndexedDB 全体でとても多く使用されるでしょう。データベース処理には時間がかかります。結果を待つ間にブラウザーがハングアップするのは避けたいので、データベースの操作は非同期です。つまり、すぐに実行するのではなく、将来のある時点で実行し、実行が完了したら通知を受け取ります。
 IndexedDB でこれを扱うには、リクエストオブジェクトを作成します（これは好きなように名付けることができます。ここでは `openRequest` と名付けているので、何のためのオブジェクトかは明らかでしょう）。そして、リクエストが完全に完了したり、失敗したりしたときにコードを実行するために、イベントハンドラーを使用します。

**メモ:** バージョン番号は重要です。もし、データベースをアップグレードしたい場合（例えば、テーブル構造を変更するなど）、バージョンを上げ、 `upgradeneeded` ハンドラー（下記参照）の内部で異なるスキーマを指定するなどして、コードを再度実行する必要があります。このチュートリアルでは、データベースのアップグレードについては触れません。
3. 前回追加した部分のすぐ下に、次のイベントハンドラーを追加してください。

```js
// error ハンドラーは、データベースがうまく開けなかったことを意味します。
openRequest.addEventListener("error", () =>
  console.error("Database failed to open"),
);

// success ハンドラーは、データベースがうまく開けたことを意味します。
openRequest.addEventListener("success", () => {
  console.log("Database opened successfully");

  // 開いたデータベースオブジェクトを db という変数に記憶します。これは以降で頻繁に使われます。
  db = openRequest.result;

  // IDB 内の既存のメモ書きを表示するために、 displayData() 関数を実行します。
  displayData();
});
```

`error` イベントハンドラーは、システムがリクエストに失敗したと言って戻ってきたときに実行されます。これによって、この問題に対応することができます。この例では、 JavaScript のコンソールにメッセージを表示するだけです。
 `success` イベントハンドラーは、リクエストが正常に返された場合、つまりデータベースが正常に開かれた場合に実行されます。この場合、開いたデータベースを表すオブジェクトが `openRequest.result` プロパティで利用できるようになり、データベースを操作することができるようになります。後で使用するために、先に作成した `db` 変数にこの値を格納しておきます。また、 `displayData()` という関数も呼び出し、 `<ul>` の中にデータベースのデータを表示します。ここでは、ページを読み込むとすぐにデータベースにあるメモが表示されるように、この関数を実行しています。後で `displayData()` を定義したものを見ることになります。
4. この節の最後に、データベースを設定するためのおそらく最も重要なイベントハンドラーを追加します。 `upgradeneed` です。このハンドラーは、データベースがまだ設定されていない場合、またはデータベースが既存の格納されたデータベースよりも大きなバージョン番号で開かれた場合（アップグレードを実行する場合）実行されます。前回のハンドラーの下に、以下のコードを追加してください。

```js
// データベースのテーブルがまだ存在しない場合は、それを設定します。
openRequest.addEventListener("upgradeneeded", (e) => {
  // 開いたデータベースの参照を取得します。
  db = e.target.result;

  // 自動増加するキーを含んだ、メモを格納するための objectStore を
  // 作成します（基本的に単一の表のように）。
  const objectStore = db.createObjectStore("notes\_os", {
    keyPath: "id",
    autoIncrement: true,
  });

  // objectStore にどのようなデータ項目を格納するかを定義します。
  objectStore.createIndex("title", "title", { unique: false });
  objectStore.createIndex("body", "body", { unique: false });

  console.log("Database setup complete");
});
```

ここでは、データベースのスキーマ（構造）、つまり、データベースに含まれるカラム（またはフィールド）の集合を定義します。ここでは、まず既存のデータベースへの参照を、イベントのターゲット (`e.target.result`) の `result` プロパティ (`request` オブジェクト) から取得します。これは `success` イベントハンドラーの `db = openRequest.result;` という行と同じですが、ここでは別個に行う必要があります。それは、`upgradeneeded` イベントハンドラーが（必要なら） `success` イベントハンドラーの前に実行されるためです。つまり、この処理を行わない場合は `db` の値を利用することはできないのです。
 次に `IDBDatabase.createObjectStore()` を使用して、呼び出されたデータベースの中に `notes_os` という名前の新しいオブジェクトストアを作成します。これは、従来のデータベースシステムにおける単一の表に相当します。これに notes という名前をつけて、 `id` という `autoIncrement` キーフィールドも指定しました。新しいレコードが作成されるたびに、このフィールドに自動的に値が増加するので、開発者は明示的にこのフィールドを設定する必要はありません。キーである `id` フィールドは、レコードを削除するときや表示するときなど、レコードを一意に識別するために使用されます。
 他にも `IDBObjectStore.createIndex()` メソッドを使用して、`title` （それぞれのメモのタイトルを格納）と `body` （メモの本文を格納）の 2 つのインデックス（フィールド）も作成しています。

このデータベーススキーマを設定した上で、データベースにレコードを追加し始めると、各レコードはこのようなオブジェクトとして表わされることになります。

```json
{
  "title": "Buy milk",
  "body": "Need both cows milk and soy.",
  "id": 8
}
```

### データをデータベースに追加

では、データベースにレコードを追加する方法を見てみましょう。これは、ページ上のフォームを使用して行われます。

前のイベントハンドラーの下に、次の行を追加してください。これは、 `submit` イベントハンドラーを設定し、 `addData()` という関数を、フォームが送信されたとき（送信ボタン (`<button>`) が押されてフォーム送信が成功したとき）に実行するようにします。

```js
// submit イベントハンドラーを作成し、フォームが送信されたときに addData() 関数が実行されるようにします。
form.addEventListener("submit", addData);
```

では、`addData()` 関数を定義しましょう。上記の行の下に、以下のものを追加してください。

```js
// addData() 関数を定義します。
function addData(e) {
  // 既定の動作を抑止します。従来通りの方法でフォームを送信したくはないからです。
  e.preventDefault();

  // フォームのフィールドに入力された値を取得し、 DB に挿入できるようにオブジェクトに格納します。
  const newItem = { title: titleInput.value, body: bodyInput.value };

  // 読み書きのデータベーストランザクションを開いて、データの追加に備えます。
  const transaction = db.transaction(["notes\_os"], "readwrite");

  // すでにデータベースに追加されているオブジェクトストアを呼び出すします。
  const objectStore = transaction.objectStore("notes\_os");

  // newItem オブジェクトをオブジェクトストアに追加するリクエストを行います。
  const addRequest = objectStore.add(newItem);

  addRequest.addEventListener("success", () => {
    // フォームをクリアして、次の項目の追加に備えます。
    titleInput.value = "";
    bodyInput.value = "";
  });

  // トランザクションが完了し、完全に終了した場合の成功を報告します。
  transaction.addEventListener("complete", () => {
    console.log("Transaction completed: database modification finished.");

    // 表示データの更新を行い、新しく追加された項目を表示するために、再度 displayData() を実行します。
    displayData();
  });

  transaction.addEventListener("error", () =>
    console.log("Transaction not opened due to error"),
  );
}
```

これはかなり複雑です。分解すると、以下のことを行います。

* イベントオブジェクトに対して `Event.preventDefault()` を実行し、従来の方法で実際にフォームを送信しないようにします（これではページの更新が発生し、使い勝手が損なわれてしまいます）。
* データベースに入力するレコードを表すオブジェクトを作成し、フォームの入力値からそのオブジェクトに値を入力します。明示的に `id` 値を記載する必要はないことに注意してください。先ほど説明したように、この値は自動で含まれます。
* `readwrite` トランザクションを `notes_os` オブジェクト ストアに対して、 `IDBDatabase.transaction()` メソッドを使用して格納するために開いてください。このトランザクションオブジェクトによって、オブジェクトストアにアクセスすることができ、新しいレコードを追加するなど、何かを行うことができます。
* `IDBTransaction.objectStore()` メソッドを用いてオブジェクトストアにアクセスし、その結果を `objectStore` 変数に格納します。
* 新しいレコードを `IDBObjectStore.add()` を使用してデータベースに追加します。これは、前に見たのと同じ方法で、リクエストオブジェクトを作成します。
* ライフサイクルの重要なポイントでコードを実行するために、 `request` および `transaction` オブジェクトにいくつものイベントハンドラーを追加します。リクエストが成功したら、次のメモを入力するためにフォームの入力をクリアします。トランザクションが完了したら、 `displayData()` 関数を再び実行して、ページ上のメモの表示を更新しています。
### データの表示

すでにコードの中で `displayData()` を 2 回も参照しているので、定義しておいたほうがよさそうです。前回の関数定義の下に、次のコードを追加してください。

```js
// displayData() 関数を定義します。
function displayData() {
  // ここでは、表示が更新されるたびに、リスト要素の中身を空にしています。
  // これを行わないと、新しいメモが追加されるたびに、重複して掲載されてしまいます。
  while (list.firstChild) {
    list.removeChild(list.firstChild);
  }

  // オブジェクトストアを開き、カーソルを取得します。カーソルはストア内の
  // 異なるデータ項目をすべて反復処理します。
  const objectStore = db.transaction("notes\_os").objectStore("notes\_os");
  objectStore.openCursor().addEventListener("success", (e) => {
    // カーソルへの参照を取得します。
    const cursor = e.target.result;

    // 反復処理を行うべき別のデータ項目がまだあれば、このコードを実行し続けます。
    if (cursor) {
      // リスト項目、h3、p を作成し、表示する際に各データ項目を中に入れます。
      // HTML フラグメントを構成し、リストの中に追加します。
      const listItem = document.createElement("li");
      const h3 = document.createElement("h3");
      const para = document.createElement("p");

      listItem.appendChild(h3);
      listItem.appendChild(para);
      list.appendChild(listItem);

      // h3 および para の内部に、カーソルからのデータを入れます。
      h3.textContent = cursor.value.title;
      para.textContent = cursor.value.body;

      // listItem の属性内部に、このデータ項目の ID を保存します。こうすると、
      // どの項目に対応しているのかがわかります。これは、後で項目を削除したくなったときに有用です。
      listItem.setAttribute("data-note-id", cursor.value.id);

      // ボタンを作成し、それを各 listItem の内部に設置します。
      const deleteBtn = document.createElement("button");
      listItem.appendChild(deleteBtn);
      deleteBtn.textContent = "Delete";

      // ボタンがクリックされたら deleteItem() 関数が実行されるように、
      // イベントハンドラーを設定します。
      deleteBtn.addEventListener("click", deleteItem);

      // カーソルの次の項目へ反復処理を行います。
      cursor.continue();
    } else {
      // Again, if list item is empty, display a 'No notes stored' message
      if (!list.firstChild) {
        const listItem = document.createElement("li");
        listItem.textContent = "No notes stored.";
        list.appendChild(listItem);
      }
      // 反復処理をすべきカーソルの項目がこれ以上ない場合、そのように示します。
      console.log("Notes all displayed");
    }
  });
}
```

改めて、分解してみましょう。

* 最初に `<ul>` 要素の中身を空にしてから、更新された内容で埋めています。これを行わないと、更新のたびに追加される重複した内容の膨大なリストが出来上がってしまいます。
* 次に、 `notes_os` オブジェクトストアへの参照を取得します。これは `addData()`で行ったように `IDBDatabase.transaction()` と `IDBTransaction.objectStore()` を使用しますが、ここでは 1 行にまとめて記述しています。
* 次の段階では、カーソルのリクエストを開くために `IDBObjectStore.openCursor()` メソッドを使用します。これは、オブジェクトストア内のレコードを反復処理するために使用できる構造体です。コードを簡潔にするために、この行の終わりに `success` イベントハンドラーを連結しています。カーソルが正常に返されたときに、ハンドラーが実行されます。
* カーソル自身への参照（`IDBCursor` オブジェクト）は、 `const cursor = e.target.result` を使用して取得します。
* 次に、カーソルにデータストアのレコードがあるかどうかを調べます (`if (cursor){ }`)。もしあれば、 DOM フラグメントを作成し、レコードのデータを入れて、ページへ挿入します（`<ul>` 要素の中）。また、削除ボタンも追加しています。このボタンをクリックすると、次の節で説明する `deleteItem()` 関数が実行され、そのメモが削除されます。
* `if` ブロックの最後では `IDBCursor.continue()` メソッドを使用して、データストアの次のレコードにカーソルを進め、再び `if` ブロックの中身を実行しています。反復処理する別のレコードがあれば、それがページに挿入され、再び `continue()` が実行され、以下同様となります。
* 反復処理するレコードがなくなると、 `cursor` は `undefined` を返しますので、 `if` ブロックの代わりに `else` ブロックが実行されます。このブロックは `<ul>` に何かメモが挿入されたかどうかを調べます。もし挿入されていない場合は、メモが格納されていなかったというメッセージを挿入します。
### メモの削除

上述のとおり、メモの削除ボタンが押されると、そのメモは削除されます。これは、 `deleteItem()` 関数により実現できます。この関数は以下のようなものです。

```js
// deleteItem() 関数を定義します。
function deleteItem(e) {
  // 削除したいタスクの名前を取得します。
  // IDB で使用する前に、これを数値に変換する必要があります。
  // IDB のキー値は型が重視されます。
  const noteId = Number(e.target.parentNode.getAttribute("data-note-id"));

  // データベーストランザクションを開き、タスクを削除します。上で取得した id を使用してタスクを探します。
  const transaction = db.transaction(["notes\_os"], "readwrite");
  const objectStore = transaction.objectStore("notes\_os");
  const deleteRequest = objectStore.delete(noteId);

  // データ項目を削除したことを報告します。
  transaction.addEventListener("complete", () => {
    // ボタンの親、すなわちリスト項目を削除します。
    // すると、それは表示されなくなります。
    e.target.parentNode.parentNode.removeChild(e.target.parentNode);
    console.log(`Note ${noteId} deleted.`);

    // この場合も、リスト項目が空であれば、「メモが格納されていません」というメッセージを表示します。
    if (!list.firstChild) {
      const listItem = document.createElement("li");
      listItem.textContent = "No notes stored.";
      list.appendChild(listItem);
    }
  });
}
```

* 最初の部分は少し説明が必要かもしれません。削除するレコードの ID を `Number(e.target.parentNode.getAttribute('data-note-id'))` を使用して取得します。レコードの ID は `<li>` が最初に表示されたときに `data-note-id` 属性で保存されていたことを思い出しましょう。しかし、この属性はグローバルな組み込みの `Number()` オブジェクトを通して渡す必要があります。これはデータ型が文字列なので、数字を期待するデータベースからは認識されないためです。
* 次に、前回と同じパターンでオブジェクトストアへの参照を取得し、 `IDBObjectStore.delete()` メソッドを使用して、 ID を渡しながらデータベースからレコードを削除しています。
* データベーストランザクションが完了したら、メモの `<li>` を DOM から削除し、再び `<ul>` が空になったかどうかを調べて、適切なメモを挿入します。

というわけで、これで完了です。この例はこれでうまく動作するはずです。

もし問題があれば、気軽に ライブ例と突き合わせてみてください （ソースコード も参照してください）。

### IndexedDB を通じて複雑なデータを格納

上で述べたように、IndexedDB は文字列以外のものも格納するために使用することができます。動画や画像 Blob のような複雑なオブジェクトを含め、ほぼ全てのものを格納することができます。そして、それを実現するのは、他の型のデータよりもそれほど難しいことではありません。

その方法を示すために、私たちは IndexedDB 動画ストレージ という別の例を書きました（これをここでライブ実行するもご覧ください）。この例を最初に実行すると、ネットワークからすべての動画をダウンロードして IndexedDB データベースに格納し、UI の `<video>` 要素の中に動画を表示します。2 回目に実行したときは、データベース内の動画を見つけて、代わりにそこから取得してから表示します。これにより、その後の読み込みがはるかに速くなり、帯域幅を消費することもなくなります。

この例で最も注目される部分を見ていきましょう。すべてを見ていくわけではありません。多くは前回の例と同様で、コードには十分なコメントがつけられています。

1. この例では、取得する動画の名前をオブジェクトの配列に格納しました。

```js
const videos = [
  { name: "crystal" },
  { name: "elf" },
  { name: "frog" },
  { name: "monster" },
  { name: "pig" },
  { name: "rabbit" },
];
```

2. 始めるには、データベースを正常に開くために、`init()`関数を実行します。これは、さまざまな動画の名前をループして、 `videos` データベースからそれぞれの名前で指定されたレコードを読み込もうとするものです。
 それぞれの動画がデータベースで見つかった場合（`request.result` の評価値が `true` かどうかで確認します。記録が存在しない場合は `undefined` となります）、その動画ファイル（blob として格納されています）と動画名が `displayVideo()` 関数に直接渡され、UI に配置されます。存在しない場合、動画名は `fetchVideoFromNetwork()` 関数に渡され、ご想像のとおり、ネットワークから動画を取得します。

```js
function init() {
  // 動画の名前を一つずつループ処理してゆきます。
  for (const video of videos) {
    // トランザクションを開き、オブジェクトストアを取得し、名前によって各動画を get() します。
    const objectStore = db.transaction("videos\_os").objectStore("videos\_os");
    const request = objectStore.get(video.name);
    request.addEventListener("success", () => {
      // もし結果がデータベース内に存在したら（存在しなければ undefined）
      if (request.result) {
        // displayVideo() を用いて、動画を IDB から取り出して表示します。
        console.log("taking videos from IDB");
        displayVideo(
          request.result.mp4,
          request.result.webm,
          request.result.name,
        );
      } else {
        // 動画をネットワークから取ってきます。
        fetchVideoFromNetwork(video);
      }
    });
  }
}
```

3. 以下のスニペットは、`fetchVideoFromNetwork()` 内から引用したものです。ここでは、2 つの別々の `fetch()` 要求を用いて MP4 版と WebM 版の動画を読み込んでいます。次に `Response.blob()` メソッドを使用してそれぞれのレスポンスの本文を blob として抽出し、後で格納したり表示したりできる動画のオブジェクト表現を得ています。
 しかし、ここで問題があります。これらの二つの要求はどちらも非同期的なのですが、双方のプロミスが履行された (fulfilled) 場合にだけ動画を表示もしくは保存しようと試みたいのです。幸い、そうした問題を扱う組み込みメソッドがあります。すなわち `Promise.all()` です。これは一つの引数 — 成立したかどうかを調べたい個々のプロミスすべてに対する参照を配列に入れたもの — をとり、これ自体がプロミスに基づいています。
 このプロミスの `then()` ハンドラー内で、先ほどと同様に `displayVideo()` 関数を呼び出して動画を UI に表示し、さらに `storeVideo()` 関数を呼び出して動画をデータベース内に格納しています。

```js
// fetch() 関数を使用して MP4 版と WebM 版の動画を取得し、そのレスポンス本体を blob として公開します。
const mp4Blob = fetch(`videos/${video.name}.mp4`).then((response) =>
  response.blob(),
);
const webmBlob = fetch(`videos/${video.name}.webm`).then((response) =>
  response.blob(),
);

// 両方のプロミスが履行されたときのみ、次のコードを実行します。
Promise.all([mp4Blob, webmBlob]).then((values) => {
  // ネットワークから取ってきた動画を、 displayVideo() により表示します。
  displayVideo(values[0], values[1], video.name);
  // storeVideo() を用いて、その動画を IDB に保存します。
  storeVideo(values[0], values[1], video.name);
});
```

4. まず `storeVideo()` を見ましょう。これは、データベースにデータを追加するための上記の例で見たパターンに、とてもよく似ています。つまり、`readwrite` (読み書き) トランザクションを開き、`videos_os` に対するオブジェクトストアへの参照を求め、データベースに追加すべきレコードを表すオブジェクトを作成し、それから、 `IDBObjectStore.add()` を用いてそのオブジェクトを単純に追加しています。

```js
// storeVideo() 関数を定義します。
function storeVideo(mp4, webm, name) {
  // トランザクションを開き、オブジェクトストアを取得し、IDB に書き込めるように読み書きできるようにします。
  const objectStore = db
    .transaction(["videos\_os"], "readwrite")
    .objectStore("videos\_os");

  // add() を使ってレコードを IDB に追加します。
  const request = objectStore.add({ mp4, webm, name });

  request.addEventListener("success", () =>
    console.log("Record addition attempt finished"),
  );
  request.addEventListener("error", () => console.error(request.error));
}
```

5. 最後に、`displayVideo()` があります。これは、UI に動画を挿入するのに必要な DOM 要素を作成してから、それらの DOM 要素をページに追加します。これの一番面白い部分は、以下に示した箇所です。`<video>` 要素内に動画ブロブを実際に表示するには、`URL.createObjectURL()` メソッドを使って、オブジェクト URL (メモリに記憶されている動画ブロブを指し示す内部 URL) を作成せねばならないのです。それが済んだら、オブジェクト URL を `<source>` 要素の `src` 属性の値として設定できて、物事がうまく機能します。

```js
// displayVideo() 関数を定義します。
function displayVideo(mp4Blob, webmBlob, title) {
  // Blob からオブジェクト URL を作成する
  const mp4URL = URL.createObjectURL(mp4Blob);
  const webmURL = URL.createObjectURL(webmBlob);

  // ページに動画を埋め込むための DOM 要素を作成する
  const article = document.createElement("article");
  const h2 = document.createElement("h2");
  h2.textContent = title;
  const video = document.createElement("video");
  video.controls = true;
  const source1 = document.createElement("source");
  source1.src = mp4URL;
  source1.type = "video/mp4";
  const source2 = document.createElement("source");
  source2.src = webmURL;
  source2.type = "video/webm";

  // DOM 要素のページへの埋め込み
  section.appendChild(article);
  article.appendChild(h2);
  article.appendChild(video);
  video.appendChild(source1);
  video.appendChild(source2);
}
```

オフライン資産ストレージ
------------

上記の例では、大きな資産を IndexedDB データベースに格納し、複数回ダウンロードする必要を避けるアプリを作成する方法を既に示しました。これはすでにユーザー体験の大きな改善ですが、まだ一つ足りないことがあります。メインの HTML、CSS、JavaScript ファイルは、サイトにアクセスするたびにダウンロードする必要があり、ネットワーク接続がないときには動作しないことを意味します。

![Firefoxのオフライン画面の内側へ、右手に2ピンプラグ、左手に2ピンソケットを持った漫画のキャラクターのイラストが描かれています。右側の辺には、オフラインモードのメッセージと「再試行」と書かれたボタンがあります。](/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage/ff-offline.png)

ここは、 サービスワーカーおよびそれと緊密に関連した キャッシュ API の出番です。

サービスワーカーとは、ブラウザーからアクセスされたときに、特定のオリジン（ウェブサイト、または特定のドメインにあるウェブサイトの一部）に対して登録される JavaScript のファイルです。登録されると、そのオリジンで利用できるページを制御することができます。読み込まれたページとネットワークの間に位置し、そのオリジンを対象としたネットワークリクエストに介入することでこれを行います。

リクエストに介入した場合、望むことは何でもできますが（用途のアイディアを参照）、よくある例は、ネットワークのレスポンスをオフラインで保存して、ネットワークからのレスポンスの代わりにリクエストに応答するものを提供することです。事実上、これはウェブサイトを完全にオフラインで動作させることを可能にします。

Cache API もクライアント側のストレージ機構ですが、少し異なる点があります。 HTTP レスポンスを保存するように設計されているため、サービスワーカーと一緒に非常にうまく動作します。

### サービスワーカーの例

例を見て、これがどのように見えるかを少し考えてみましょう。前の章で見たビデオストアの例の別バージョンを作成しました。この例は、HTML、CSS、JavaScript をサービスワーカー経由で Cache API に保存し、例をオフラインで実行できるようにした以外は、まったく同じように機能します。

サービスワーカーを用いた IndexedDB ビデオストアがライブで実行中のところ をご覧ください。また、ソースコードも参照してください。

#### サービスワーカーの登録

最初に注意すべきことは、メインの JavaScript ファイルに余分なコードが格納されていることです（index.js を参照してください）。最初に機能検出テストを行うために、 `serviceWorker` メンバーが `Navigator` オブジェクトで利用できるかどうかを確認します。これが true を返した場合、少なくともサービスワーカーの基本は対応していることが分かります。この内部では `ServiceWorkerContainer.register()` メソッドを使用して、 `sw.js` ファイルに含まれるサービスワーカーをそれが存在するオリジンに対して登録し、それと同じディレクトリーまたはサブディレクトリーのページを制御できるようにします。プロミスが履行されると、サービスワーカーは登録されたものとみなされます。

```js
// サイトがオフラインで動くようにする処理を制御するために、サービスワーカーを登録します。
if ("serviceWorker" in navigator) {
  navigator.serviceWorker
    .register(
      "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js",
    )
    .then(() => console.log("Service Worker Registered"));
}
```

**メモ:** 指定された `sw.js` ファイルへのパスは、コードを含む JavaScript ファイルではなく、サイトのオリジンからの相対パスです。サービスワーカーは `https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js` にあります。オリジンは `https://mdn.github.io` なので、指定されたパスは `/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js` である必要があります。もし、この例を自分自身でホスティングしたい場合は、これを適宜変更する必要があります。これはかなり紛らわしいですが、セキュリティ上の理由からこのように作業する必要があるのです。

#### サービスワーカーのインストール

次にサービスワーカーが制御するページがアクセスされたとき（例：リロードされたとき）、サービスワーカーはそのページに対してインストールされます。つまり、そのページを制御し始めることになります。このとき、サービスワーカーに対して `install` イベントが発行されます。サービスワーカー自身の内部で、インストールに応答するコードを書くことができます。

例として、sw.js ファイル（サービスワーカー）の中を見てみましょう。インストールを待ち受けするために、`self`というキーワードが登録されているのがわかります。この `self` キーワードは、サービスワーカーファイルの内部から、サービスワーカーのグローバルスコープを参照するためのものです。

`install` ハンドラーの内部では、イベントオブジェクトで利用できる `ExtendableEvent.waitUntil()` メソッドを使用して、ブラウザーはサービスワーカーのインストールを、イベント内のプロミスが正常に履行されるまで完了すべきでないことを通知しています。

ここで、キャッシュ API の動作を確認します。Domxref("CacheStorage.open()")}} メソッドを使用して、レスポンスを格納できる新しいキャッシュオブジェクトを開いています（IndexedDB オブジェクトストアのようなものです）。このプロミスは `video-store` キャッシュを表す `Cache` オブジェクトで履行されます。次に `Cache.addAll()` メソッドを使用して、一連のアセットをフェッチし、そのレスポンスをキャッシュに追加しています。

```js
self.addEventListener("install", (e) => {
  e.waitUntil(
    caches
      .open("video-store")
      .then((cache) =>
        cache.addAll([
          "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/",
          "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.html",
          "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js",
          "/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/style.css",
        ]),
      ),
  );
});
```

さて、これで終わりです。インストールが済みました。

#### さらなるリクエストへの応答

サービスワーカーが登録され、 HTML ページに対してインストールされ、関連するアセットがすべてキャッシュに追加されたので、ほぼ準備が整いました。やるべきことはもう一つあります。さらなるネットワークリクエストに応答するためのコードを書くことです。

これが `sw.js` の 2 つ目のコードの役割です。サービスワーカーのグローバルスコープに別のリスナーを追加して、`fetch` イベントが発生したときにハンドラー関数を実行するようにします。これは、ブラウザーが、サービスワーカーが登録されているディレクトリーにある資産に対してリクエストを行うたびに発生します。

ハンドラーの内部では、まずリクエストされた資産の URL をログ出力します。次に、`FetchEvent.respondWith()` メソッドを使用して、リクエストに対する独自のレスポンスを提供しています。

このブロックの中では `CacheStorage.match()` を使用して、一致するリクエスト（すなわち URL に一致する）がいずれかのキャッシュで得られるかどうかを調べています。このプロミスは、一致するものが得られた場合は一致するレスポンスで履行され、そうでない場合は `undefined` となります。

一致するものが見つかった場合、それを独自のレスポンスとして返します。そうでない場合は、ネットワークからのレスポンスを fetch() して、代わりにそれを返します。

```js
self.addEventListener("fetch", (e) => {
  console.log(e.request.url);
  e.respondWith(
    caches.match(e.request).then((response) => response || fetch(e.request)),
  );
});
```

 これでサービスワーカーは終わりです。
 サービスワーカーを使ってできることはまだたくさんあります。詳しくは service worker cookbook を参照してください。
 この例のインスピレーションを与えてくれた Paul Kinlan の記事 Adding a Service Worker and Offline into your Web App に多くの感謝を捧げます。

#### この例をオフラインで試す

サービスワーカーの例を試すには、それが確実にインストールされるように、2、3 度読み込む必要があるでしょう。それが済んだら、以下のことができます。

* ネットワーク接続ケーブルを抜いてみましょう。あるいは、Wi-Fi を切ってみましょう。
* Firefox を使っているなら、[ファイル] > [オフライン作業] を選択してください。
* Chrome を使っているなら、[デベロッパーツール] へ進み、 [*Application] > [Service Workers]* を選び、それから、[*Offline]* のチェックボックスをチェックしてください。

例のページをもう一度更新しても、問題なく読み込まれるはずです。ページ資産はキャッシュに、動画は IndexedDB データベースにと、すべてオフラインで格納されています。

まとめ
---

これで終わりです。クライアント側での保存の技術についてのこの概要を、皆さんが有用だと思ってくださったのであれば良いな、と望んでいます。

関連情報
----

* ウェブストレージ API
* IndexedDB API
* クッキー
* ウェブワーカー API
* 前のページ
* Overview: Client-side web APIs


あなたのスキルをテストしよう: 数学 - ウェブ開発を学ぶ

あなたのスキルをテストしよう: 数学
==================

このスキルテストの目的は、JavaScript での基本演算 — 数値と演算子 の記事を理解しているかどうかを評価することです。

**メモ:** 下記のインタラクティブエディターのソリューションで試してみることができますが、コードをダウンロードし、CodePen、jsFiddle、Glitch などのオンラインツールを利用すると、タスクに取り組むのに役立つかもしれません。

行き詰まったなら、私たちに助けを求めてください。 — このページの下部の Assessment or further help セクションを参照

**メモ:** 下記の作成では、あなたのコードにエラーがあれば、このページの結果パネル（または、ダウンロード可能なバージョンでは、ブラウザーの JavaScript コンソール）に出力され、答えにたどり着くための助けとなります。

数学 1
----

基本演算子の知識をテストすることから始めましょう。4 つの数値を作成し、最初の 2 つを加算してください。そして 3 番目から 4 番目を減算してください。そうして出来た 2 つの 2 次結果を乗算して、48 になるようにしてください。最後に、この値が偶数であることを証明するテストを記述することも必要ですね。

では、完成イメージを再構築するために、下記のライブコードを更新してみましょう。次の手順に従ってください:

1. 数値の変数を 4 つ作成してください。変数にわかりやすい名前を付けてください。
2. 最初の 2 つの変数を加算し、その結果を別の変数に格納してください。
3. 3 番目の変数から 4 番目の変数を減算し、その結果を別の変数に格納してください。
4. 上記 2 つの工程の結果を乗算し、`finalResult` という変数に格納してください。その結果は 48 になるはずです。もしならなければ、初期の設定数値を調整する必要があります。
5. 最後に、`finalResult` が偶数であるかどうかをチェックする計算式を記述してください。結果を `evenOddResult` という変数に格納してください。

**メモ:** このタスクのための準備済みファイルをダウンロード して、自身のエディターまたはオンラインエディターで動かせるようにしてください。

数学 2
----

2 番目のタスクでは、すでに結果が変数 `result` と `result2` に格納されている 2 つの計算式が提供されています。しかし、これらの結果は私たちが求めているものではありません。望む結果にするために、2 つの計算式を変更する必要がでてきます。

私たちは何を求めている？ 2 つの結果を乗算し、その結果を小数第 2 位までにフォーマットすると、最終結果は 10.42 になるようにしたいのです。

この完成イメージを再構築するために、下記のライブコードを更新してみましょう。次の手順に従ってください:

1. `result` と `result2` を乗算し、結果を `result` に代入し戻す計算式を記述してください。これは、代入演算子の略記形を使いましょう。
2. その結果を小数第 2 位までにフォーマットし、そして `finalResult` という変数に格納する 1 行コードを記述してください。
3. `typeof` を使用して `finalResult` のデータ型をチェックしてください。実際には `string` 型であることがわかるでしょう！それを `number` 型に変換し、結果を `finalNumber` という変数に格納する 1 行コードを記述してください。
4. `finalNumber` の値は `10.42` にならなければいけません。戻って、この最終結果が得られるように最初に与えられていた計算式を更新してください。数値や演算子は更新しないでくださいね。

**メモ:** このタスクのための準備済みファイルをダウンロード して、自身のエディターまたはオンラインエディターで動かせるようにしてください。

数学 3
----

この記事の最後のタスクでは、いくつかのテストを記述していきましょう。各々がステートメントと 2 つの変数で構成されてる、3 つのグループが与えられています。各グループについて、与えられているステートメントを証明または反証するテストを記述してください。これらのテスト結果を、各々 `weightComparison`、`heightComparison`、そして `pwdMatch` という変数に格納してください。

**メモ:** このタスクのための準備済みファイルをダウンロード して、自身のエディターまたはオンラインエディターで動かせるようにしてください。

評価またはさらなる助け
-----------

上記のインタラクティブエディターでこれらの作成を練習することができます。

あなたの制作物を評価してもらいたい、または行き詰まって助けを求めたい場合:

1. あなたの制作物を、CodePen、jsFiddle、または Glitch のような共有可能なオンラインエディターに置いてください。いちから自身でコードを記述することもできますし、上記のセクションでリンク付けされている準備済みファイルを利用するのもよいでしょう。
2. MDN Discourse forum Learning category で、評価を希望したり、助けを求めるポストを投稿してください。ポストには以下を含みましょう:
    * "Assessment wanted for Math 1 skill test" のような説明的なタイトル
    * あなたがこれまで試したことの詳細、そしてあなたが私たちに何をしてほしいか。例えば、行き詰まって助けが必要だとか、評価をしてほしいなど。
    * 評価してほしかったり、助けが必要な作成物へのリンク。共有可能なオンラインエディター（上記ステップ 1 で述べたもの）を使って。これは分かりやすくするためのよい実践方法でしょう — コードが見れない状態で、コーディング問題を抱えている人を助けることはとても難しいですよね。
    * 実際のタスクまたは評価ページのリンク。そうすれば、あなたがどの質問に助けが必要かがわかります。


配列 - ウェブ開発を学ぶ

配列
==

* 前のページ
* Overview: First steps
* 次のページ

このモジュールの最後に、配列について見ていきましょう。配列は 1 つの変数名でリスト形式のデータを保持するのに好都合です。ここでは、どう便利なのか、どのように作るのか、そして配列の項目を追加したり削除したり取得したりする方法について学びます。

| 前提条件: | 基礎的なコンピューターの知識、HTML と CSS への基本的な理解、JavaScript についての理解。 |
| --- | --- |
| 目的: | JavaScript において、配列とは何か、どのように操作するのかを理解する。 |

配列とは何？
------

配列はたいてい「リストのようなオブジェクトである」と説明され、単一のオブジェクト内に複数の値をリストとして持っています。配列オブジェクトは変数に格納され、その他の型と同様に扱われます。異なるのはリスト内の値に個別にアクセスすることができ、繰り返しを用いて全ての値に同じことをするといった、リストとして便利で効率的に扱うことができます。商品とその価格の一覧を配列に保持し、その値をループして合計額を計算しつつ請求書にそれぞれの価格を表示して一番下に合計額を表示することもできるでしょう。

もし配列がなかったとしたら、別々の変数にそれぞれの値を格納しなければならず、各変数を表示するのと計算するので別々に呼び出さなければならなかったでしょう。こうなると、書き出すのがとても長く、非効率的でエラーを起こしやすいプログラムとなるでしょう。例えば、10 個の項目を請求書に出すだけでも最悪ですが、それが 100 個や 1000 個だったらどうでしょう。この記事の最後に実例としてやってみましょう。

前回までの記事と同様に、JavaScript コンソールに配列の基礎となるコード例をブラウザーの開発者コンソールに入力しながら学びましょう。

### 配列を作る

配列を作るには、角括弧の中にカンマで区切ったリストの形式で項目を並べます。

1. 買い物リストを配列に保持したいとき、次のようにします。下のコードをコンソールに入力してみましょう。

```js
let shopping = ["パン", "牛乳", "チーズ", "ハム", "麺"];
shopping;
```

2. この例では、配列の各項目は文字列ですが、配列には何でも格納することができることを頭に入れておきましょう。文字列、数値、オブジェクト、その他の変数、さらには別の配列ですら格納することができます。そして混ぜ合わせることも。すべて同じデータ型である必要はありません。

```js
let sequence = [1, 1, 2, 3, 5, 8, 13];
let random = ["tree", 795, [0, 1, 2]];
```

3. いくつか配列を作ってみましょう。それから先に進みます。
### 配列の項目を取得し変更する

配列の各項目は文字列中の文字を取得したときのように列で角括弧 (`[]`) を使用して取得することができます。

1. 次のコードをコンソールに入力してください。

```js
shopping[0];
// "パン"という値が戻ります
```

2. そして、配列中の項目は単に新しい値を代入することで変更することが出来ます。

```js
shopping[0] = "タヒーニ";
shopping;
// ショッピングリストは[ "タヒーニ", "牛乳", "チーズ", "ハム", "麺" ]に変更されています。
```

**メモ:** 前にも言いましたが、重要なことなのでもう一度言います。コンピューターは 0 から数を数えます！
3. 配列の中に配列があるとき、その配列は多次元配列と呼ばれます。配列中の配列に含まれる項目には角括弧を繋げて書くことでアクセスすることができます。例えば、先ほどの例で出てきた `random` 変数に格納された配列の 3 番目の項目のさらに 3 番目の項目を取得するには以下のようにします。

```js
random[2][2];
```

4. 移動する前に、配列の例をいくつか変更してみてください。遊んでみて、何が動いて何がそうでないかを見てください
### 配列の長さを調べる

ある配列の長さ (いくつの項目が配列中に存在するか) を知るには、文字列の (文字の) 長さを調べた時と全く同じようにします。`length`プロパティを使用して長さを取得することができます。

```js
sequence.length;
// 7 が返る
```

このプロパティはいろいろな使われ方をするのですが、最もよく使われるのは、配列中の全項目をループするために使用されます。次の例を見てください。

```js
let sequence = [1, 1, 2, 3, 5, 8, 13];
for (let i = 0; i < sequence.length; i++) {
  console.log(sequence[i]);
}
```

ループについてはそのうち (Looping code の記事で) しっかりと学ぶ機会があるでしょうが、ここで簡単に説明すると上のコードは次のように動きます。

1. 配列中の項目 0 番よりループを開始します。
2. 項目番号が配列の長さと同じ数になったら、繰り返しを終了します。これで配列中の要素がいくつであっても動くようになります。今回の場合は、項目の番号が 7 のときに終了します(ループでカバーしたい最後の項目番号は 6 なので、これで問題ありません)。
3. そして、各項目を `console.log()` メソッドを使用してブラウザーのコンソールに出力しています。
便利な配列メソッド
---------

このセクションでは、文字列を配列にしたり、配列を文字列にしたり、配列に項目を追加したり、ちょっと便利な配列関連のメソッドを見てみましょう。

### 文字列と配列を相互に変換する

データが長い長い文字列の中に含まれていて、それを使いやすい形に分割して操作したいときがあります。例えばデータを表形式で表示するというようなときです。それには`split()` メソッドが使用できます。このメソッドは文字列を分割するのに使用する文字を引数として取り、文字列をその文字で区切った部分文字列の配列に分割します。

**メモ:** 実際のところ、これは配列ではなく文字列のメソッドです。しかし、ここで紹介するのが一番だと思い取り上げています。

1. それではどのように動くか試してみましょう。まずは文字列をコンソールに作ります。

```js
let myData = "札幌,仙台,東京,名古屋,大阪,博多";
```

2. この文字列をカンマで区切ります。

```js
let myArray = myData.split(",");
myArray;
```

3. そして、できた配列の長さを確認して、その中身を見てみましょう。

```js
myArray.length;
myArray[0]; // 配列の最初の項目
myArray[1]; // 配列の二番目の項目
myArray[myArray.length - 1]; // 配列の最後の項目
```

4. `join()` メソッドを使うことで、逆のことができます。

```js
let myNewString = myArray.join(",");
myNewString;
```

5. 配列を文字列にするもう一つの方法は、`toString()` メソッドを使うことです。引数を取らない `toString()` は `join()` と比べ簡単でしょうが、制限があります。`join()` を使えば、他の区切り文字を指定することもできます (4. の例をカンマ以外の他の文字を指定して試してください)。

```js
let dogNames = ["ポチ", "ハチ", "タロウ", "モコ"];
dogNames.toString(); //ポチ,ハチ,タロウ,モコ
```

### 項目の追加と削除

まだ配列への項目の追加と削除をやっていませんでしたね。次はこれをやりましょう。先ほどの例にあった `myArray` 配列を使用します。先ほどの例をまだ実行していなければ、以下のコードをコンソールに入力して配列を作ってください。

```js
let myArray = ["札幌", "仙台", "東京", "名古屋", "大阪", "博多"];
```

まず、配列の最後に項目を追加したり、最後の項目を削除するには、それぞれ `push()` と `pop()` を使います。

1. 先に `push()` を使ってみます。配列の最後に項目を追加するには 1 つ以上の項目を引数に指定します。

```js
myArray.push("横浜");
myArray;
myArray.push("神戸", "広島");
myArray;
```

2. メソッドの呼び出しが終わると、新しい配列の長さが返ります。もし新しい配列の長さを変数に格納したければ、次のようにできます。

```js
let newLength = myArray.push("京都");
myArray;
newLength;
```

3. 配列の最後の要素を削除するには `pop()` を呼び出すだけです。

```js
myArray.pop();
```

4. メソッドの呼び出しが終わると、削除された項目それ自体が返ります。

```js
let removedItem = myArray.pop();
myArray;
removedItem;
```

`unshift()` と `shift()` はそれぞれ `push()` や `pop()` と同様の動作ですが、配列の末尾ではなく先頭において動作します。

1. まずは、`unshift()` を次のように実行します。

```js
myArray.unshift("奈良");
myArray;
```

2. 今度は `shift()` でやってみましょう！

```js
let removedItem = myArray.shift();
myArray;
removedItem;
```

アクティブラーニング: 商品を印字しよう！
---------------------

商品の名前と価格、合計金額を請求書に印字するという最初の話に戻りましょう。以下に示す編集可能なコードのコメントの中に数字が書かれています。この数字はコードを書くべき場所を示しています。各数字の場所に次のようなコードを書いてください。

1. `// No.1` というコメントの下に、商品の名前と価格をコロン (:) で繋げた、いくつか文字列が並んでいます。これを `products` という名前の配列にしてください。
2. `// No.2` というコメントの行から for ループが始まっています。この行には `i <= 0` と書かれています。「`i` の値が 0 以下でなくなれば終了」と書かれており、`i` は 0 から始まるので、この for ループは一度しか実行されません。この条件を `i` が `products` 配列の長さより小さくなくなった場合に終了するような条件に置き換えて下さい。
3. `// No.3` のコメントの直下に 1 行で、現在の配列の項目 (`name:price`) を 2 つに分割するコードを書いてください。一方は商品の名前、もう一方は価格です。もしどうすればいいのかわからなければ、便利な文字列のメソッドの記事を参照してください。さらに文字列と配列を相互に変換するも役に立つでしょう。
4. 上記のコードの一部として、価格を文字列から数値に変換する必要もあるでしょう。どのようにすべきか思い出せなければ、文字列の最初の記事を確認してみましょう。
5. コードの先頭に `total` という名前の変数が宣言されて、0 で初期化されています。ループの中で (`// No.4` *の*下) 繰り返している現在の項目の価格を `total` 変数に加算するコードを一行で書いてください。そうすれば、コードの最後で正しい合計が請求書に印字されます。恐らく代入演算子が役に立つでしょう。
6. `// No.5` のコメントの直下のコードを `itemText` 変数が「現在の項目の商品名 — $現在の項目の価格」となるように変更してください。「靴 — $23.99」という感じです。そうすれば正しい情報が請求書に印字されます。これはもう慣れたはずの単純な文字列結合で実現できます。
```

<h2>出力結果</h2>

<div class="output" style="min-height: 150px;">
  <ul></ul>

  <p></p>
</div>

<h2>コードエディター</h2>

<p class="a11y-label">
  コードエディターから抜けるには Esc
  キーを押して下さい(タブキーではタブ文字を挿入します)。
</p>

<textarea id="code" class="playable-code" style="height: 410px;width: 95%">
const list = document.querySelector('.output ul');
const totalBox = document.querySelector('.output p');
let total = 0;
list.innerHTML = '';
totalBox.textContent = '';
// No.1
                'パンツ:6.99'
                '靴下:5.99'
                'T シャツ:14.99'
                'ズボン:31.99'
                '靴:23.99';

for (let i = 0; i <= 0; i++) { // No.2
  // No.3

  // No.4

  // No.5
  let itemText = 0;

  const listItem = document.createElement('li');
  listItem.textContent = itemText;
  list.appendChild(listItem);
}

totalBox.textContent = '合計: $' + total.toFixed(2);
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="リセット" />
  <input id="solution" type="button" value="答えを見る" />
</div>
```

```
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
let code = textarea.value;
let userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "Show solution";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "答えを見る") {
    textarea.value = solutionEntry;
    solution.value = "答えを隠す";
  } else {
    textarea.value = userEntry;
    solution.value = "答えを見る";
  }
  updateCode();
});

const jsSolution =
  "const list = document.querySelector('.output ul');\nconst totalBox = document.querySelector('.output p');\nlet total = 0;\nlist.innerHTML = '';\ntotalBox.textContent = '';\n\nlet products = ['パンツ:6.99',\n '靴下:5.99',\n 'T シャツ:14.99',\n 'ズボン:31.99',\n '靴:23.99'];\n\nfor(let i = 0; i < products.length; i++) {\n let subArray = products[i].split(':');\n let name = subArray[0];\n let price = Number(subArray[1]);\n total += price;\n itemText = name + ' — $' + price;\n\n let listItem = document.createElement('li');\n listItem.textContent = itemText;\n list.appendChild(listItem);\n}\n\ntotalBox.textContent = '合計: $' + total.toFixed(2);";
let solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// タブキーでテキストエリアから抜けてしまうのを防ぎ、
// 代わりにカーソル位置にタブ文字を挿入する

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  const scrollPos = textarea.scrollTop;
  const caretPos = textarea.selectionStart;
  const front = textarea.value.substring(0, caretPos);
  const back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );

  textarea.value = front + text + back;
  caretPos = caretPos + text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// ユーザーがテキストエリアのコードを書き換える度に userCode を毎回更新する

textarea.onkeyup = function () {
  // ユーザーのコードが表示されているときのみ状態を保存し、
  // 答えのコードでユーザーコードが上書きされないようにする
  if (solution.value === "答えを見る") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};
```

```
html {
  font-family: sans-serif;
}

h2 {
  font-size: 16px;
}

.a11y-label {
  margin: 0;
  text-align: right;
  font-size: 0.7rem;
  width: 98%;
}

body {
  margin: 10px;
  background-color: #f5f9fa;
}
```

アクティブラーニング: 上位 5 件の検索
---------------------

`push()` や`pop()` といったメソッドを使用するよい例は、ウェブアプリでデータの中で有効な項目だけを抜き出して維持したいときなどです。例えば、アニメーションのあるシーンで、現在表示中の背景イメージを保持している配列があり、一度に表示するイメージをパフォーマンスなどの理由で 50 に制限したいとします。その際、新しいオブジェクトを配列に追加したと同時に、古いオブジェクトを削除して配列を希望のサイズに維持します。

次の例では、もう少し単純に、検索ボックスのある、ダミーの検索サイトを用意しました。検索ボックスに文字が入力されたら、直前 5 つの検索語がリストに表示されます。検索された語が 5 を超えたら最後の検索語が削除されるようになります。新しい検索語が先頭に追加されるので、常に 5 つの検索語が表示されます。

**メモ:** 本当の検索アプリでは、前回の検索語をクリックすることでその検索に戻れて、実際に検索結果が表示されることでしょう！ただし、今は単純にしておきましょう。

アプリを完成させるには...

1. `// No.1` コメントの下に、検索ボックスに入力された検索語を配列の先頭に追加するコードを書いてください。検索語は `searchInput.value` と書いて取得します。
2. `// No.2` コメントの下に、配列の最後の項目を削除するコードを書いてください。
```

<h2>出力結果</h2>
<div class="output" style="min-height: 150px;">
  <input type="text" /><button>検索</button>

  <ul></ul>
</div>

<h2>コードエディター</h2>

<p class="a11y-label">
  コードエディターから抜けるには Esc
  キーを押して下さい(タブキーではタブ文字を挿入します)。
</p>

<textarea id="code" class="playable-code" style="height: 370px; width: 95%">
const list = document.querySelector('.output ul');
const searchInput = document.querySelector('.output input');
const searchBtn = document.querySelector('.output button');

list.innerHTML = '';

let myHistory = [];

searchBtn.onclick = function() {
  // 検索ボックスが空ではない場合のみ検索語を受け付けるようにします
  if (searchInput.value !== '') {
    // No.1

    // 表示中の一覧を空にして、同じ項目が表示されないようにして、
    // 結果は検索語が入力される度に毎回作り直されます。
    list.innerHTML = '';

    // 配列をループして、リスト内の全ての検索語を表示します
    for (let i = 0; i < myHistory.length; i++) {
      itemText = myHistory[i];
      const listItem = document.createElement('li');
      listItem.textContent = itemText;
      list.appendChild(listItem);
    }

    // 配列の長さが 5以上になったら一番古い検索語を削除します
    if (myHistory.length >= 5) {
      // No.2

    }

    // 次の検索語を受け付けるため、検索ボックスを空にしてフォーカスします
    searchInput.value = '';
    searchInput.focus();
  }
}
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="リセット" />
  <input id="solution" type="button" value="答えを見る" />
</div>
```

```
html {
  font-family: sans-serif;
}

h2 {
  font-size: 16px;
}

.a11y-label {
  margin: 0;
  text-align: right;
  font-size: 0.7rem;
  width: 98%;
}

body {
  margin: 10px;
  background: #f5f9fa;
}
```

```
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
let code = textarea.value;
let userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "Show solution";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "答えを見る") {
    textarea.value = solutionEntry;
    solution.value = "答えを隠す";
  } else {
    textarea.value = userEntry;
    solution.value = "答えを見る";
  }
  updateCode();
});

const jsSolution =
  "const list = document.querySelector('.output ul');\nconst searchInput = document.querySelector('.output input');\nconst searchBtn = document.querySelector('.output button');\n\nlist.innerHTML = '';\n\nlet myHistory= [];\n\nsearchBtn.onclick = function() {\n if(searchInput.value !== '') {\n myHistory.unshift(searchInput.value);\n\n list.innerHTML = '';\n\n for(let i = 0; i < myHistory.length; i++) {\n itemText = myHistory[i];\n const listItem = document.createElement('li');\n listItem.textContent = itemText;\n list.appendChild(listItem);\n }\n\n if(myHistory.length >= 5) {\n myHistory.pop();\n }\n\n searchInput.value = '';\n searchInput.focus();\n }\n}";
let solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// タブキーでテキストエリアから抜けてしまうのを防ぎ、
// 代わりにカーソル位置にタブ文字を挿入する

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  const scrollPos = textarea.scrollTop;
  const caretPos = textarea.selectionStart;
  const front = textarea.value.substring(0, caretPos);
  const back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );
  textarea.value = front + text + back;
  caretPos = caretPos + text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// ユーザーがテキストエリアのコードを書き換える度に userCode を毎回更新する

textarea.onkeyup = function () {
  // ユーザーのコードが表示されているときのみ状態を保存し、
  // 答えのコードでユーザーコードが上書きされないようにする
  if (solution.value === "答えを見る") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};
```

スキルテスト
------

この記事の最後に達しましたが、最も大切な情報を覚えていますか？次に進む前に、この情報が身に付いたかどうかを確認するテストがあります。Test your skills: Arrays を見てください。

結論
--

この記事を読んで、配列がとても使えるものであることがお分かりいただけたのではないでしょうか。配列は JavaScript の至るところで見かけます。特に配列の各項目に対して同じことをする際にループと一緒に使われます。便利なループの基本については次のモジュールで教えますが、今は自分自身を褒めて、十分に休みましょう！このモジュールのすべての記事をやり終えました！

ただし、次のモジュールの前に、理解度を確認するため、このモジュールの課題をやりましょう。残っているのはそれだけです。

関連情報
----

* インデックス付きコレクション — 配列とそのいとこにあたる型付き配列についての高度なガイドです。
* `Array` — `Array` オブジェクトのリファレンスページです。詳細なガイドと機能については、このリファレンスページで紹介されています。
* 前のページ
* Overview: First steps
* 次のページ


便利な文字列メソッド - ウェブ開発を学ぶ

便利な文字列メソッド
==========

* 前のページ
* Overview: First steps
* 次のページ

文字列の基本についてはすでに見ました。ここからはレベルを上げて、組み込みのメソッドを使用して文字列に対して実行できる便利な操作について考えてみましょう。例えば、文字列の長さを調べたり、繋げたり、分割したり、ある文字を他の文字に置き換えたりなどです。

| 前提条件: | 基礎的なコンピュータの知識、HTML と CSS の基本の理解、JavaScript が何かを知っていること。 |
| --- | --- |
| 目的: | 文字列がオブジェクトであることを理解して、そのオブジェクトで使用できる基本的なメソッドを使って文字列を操作する方法を身に付ける。 |

オブジェクトとしての文字列
-------------

JavaScript ではほとんどのものはオブジェクトです。たとえば、次のように文字列を作った時も、

```js
let string = "This is my string";
```

この変数は文字列オブジェクトのインスタンスになり、大量のプロパティとメソッドが使用可能となります。`String` オブジェクトのページに行って、横にある一覧を眺めてみてください！

\*\*また、脳が溶け出しそうかもしれませんが、安心してください！\*\*先ほどのページにある、ほとんどのメソッドは学習の初期に覚える必要はありません。しかし、今から紹介する、文字列操作のメソッドはとてもよく使いますのでぜひ覚えましょう。

ブラウザーの開発者コンソールにいくつかの例を入力してみましょう。

### 文字列の長さを調べる

ただ `length` プロパティを使用すればよいので、とても簡単です。次の行を入力してみましょう。

```js
let browserType = "mozilla";
browserType.length;
```

これで 7 という数値が戻ります。なぜなら "mozilla" は 7 文字だからです。例えば、文字の長さによって文字列を並べたいときや、ユーザーがある長さ以上のユーザー名をフォームの入力フィールドに入れたことを知らせたりするような場面で便利です。

### 特定の文字列を扱う

前の例と関連していますが、文字列に対して**角括弧記法**を使用することで文字列中の任意の 1 文字が得られます。つまり角括弧 (`[]`) を変数名の後ろに付け、その中に数値を入れることで、その番目にある文字が返ってきます。例えば最初の一文字を取得するには次のようにします。

```js
browserType[0];
```

コンピューターは 1 からではなく、0 から数えます！例えば、文字列から最初の文字を見つけてアルファベット順に並べるときに使うことができます。

文字列の一番最後の文字を取得したいときは、先ほどの `length` プロパティと組み合わせて以下のようにします。

```js
browserType[browserType.length - 1];
```

"mozilla" の長さは 7 ですが、0 から始まるので、最後の文字の位置は 6 になります。よって `length-1` とする必要があるのです。

### 部分文字列を探して抽出する

長い文字列の中に、ある文字列が存在するか調べたいと思うことがあります (よく文字列中に部分文字列が存在するなどといいます)。これには、探したい部分文字列を引数に取る `indexOf()` メソッドを使用することで可能です。

もし対象の文字列中に部分文字列が*見つかった*場合、このメソッドは部分文字列のインデックス位置を表す数値 (対象の文字列上で部分文字列が始まる文字数) を返します。もし対象の文字列中に部分文字列が*見つからなかった*場合は、`-1` の値を返します。

1. 以下を試してみましょう。

```js
browserType.indexOf("zilla");
```

このコードは 2 を返します。なぜならば、"mozilla"という文字列中で、部分文字列"zilla"の開始位置が 2 (0、1、2 — つまり 3 文字目)であるからです。このようなコードは文字列を絞り込むときなどに使います。例えば、ウェブサイトのアドレスの一覧があったとき、その中から"mozilla"を含むものだけを表示したい場合などです。
2. 部分文字列が含まれてることを確認するもう一つの方法があり、こちらのほうが効果的な場合があります。

```js
browserType.indexOf("vanilla");
```

上記のコードは `-1` を返します。これは部分文字列 (この場合は 'vanilla') がメインの文字列の中に見つからなかった場合に返されます。
 これを使って、部分文字列 'mozilla' を**含まない** (あるいは否定演算子 `!==` を使うなら**含む**) 文字列のすべてのインスタンスを見つけることができます。

```js
if (browserType.indexOf("mozilla") === -1) {
  // もし部分文字列 'mozilla' が含まれていない場合は、
  // 文字列で何かをします。
}

if (browserType.indexOf("mozilla") !== -1) {
  // もし部分文字列 'mozilla' が含まれている場合は、
  // 文字列で何かをします。
}
```

3. 部分文字列がその文字列のどこから始まるかが分かっており、どこで終わっているかがわかれば、その部分文字列を `slice()` メソッドを使用することで抽出することができます。

```js
browserType.slice(0, 3);
```

このコードは "moz" という文字列を返します。最初の引数は抽出を始める最初の位置で、2 番目の引数が抽出する最後の文字の直後の位置です。つまり、この場合先頭から 4 番目の手前までの文字列が切り出されたということです。言い換えると、この場合は 2 番目の引数と同じ 3 文字が切り出されました。
4. また、ある文字以降の文字列の残りの文字をすべて抽出したいとわかっている場合は、2 番目のパラメータを含める必要はありません！その代わり、文字列内の残りの文字を抽出したい文字の位置を含める必要があるだけです。次のようにしてみてください。

```js
browserType.slice(2);
```

この例は "zilla" という文字列を返します。なぜなら、2 の位置にある文字は z であり、2 番目の引数を指定していないため、3 文字目から最後までが部分文字列として戻ったのです。

**メモ:** `slice()` の 2 番目の引数はオプション (任意指定) です。もし指定しなければ、元々の文字列の最後まで切り出しを行います。他にもオプションがありますので、さらに詳しく知りたければ`slice()`のページで調べてみましょう。

### 大文字・小文字の切り替え

String には`toLowerCase()`と`toUpperCase()`という 2 つのメソッドがあり、引数として渡された文字列のすべての文字の大文字・小文字を切り替えます。これは例えば、ユーザーの入力を DB に保存する場合に、値を正規化するのに便利です。

それでは次の行を入力し、何が起きるか見てみましょう。

```js
let radData = "My NaMe Is MuD";
radData.toLowerCase();
radData.toUpperCase();
```

### 文字列の一部分を書き換える

`replace()`メソッドを使用することで、ある部分文字列を他の文字列に置き換えることができます。基本レベルではとても単純に動作しますが、`replace()` メソッドは高度な機能を持っています (今回はその機能は紹介しませんが)。

検索する文字列と、置き換える文字列の 2 つを引数に取ります。次の例を実行してみてください。

```js
browserType.replace("moz", "van");
```

これはコンソールで"vanilla"を返しますが、`browserType` の値は、"mozilla"のままです。プログラムで変数 `browserType` の値を実際に更新するには、演算の結果を変数に設定し直す必要があります。つまりそれ変数に設定されている部分文字列を自動的には更新してくれないのです。従って実際に変数の内容を更新するためには `browserType = browserType.replace('moz','van');` のように書きます。

アクティブ学習の実例
----------

この章では、文字列を操作するコードを書いてもらいます。以下のどの問題も文字列の配列をループで回して、順序なしリスト形式で表示しますが、配列やループに関して今は理解する必要はありません。配列については今後の記事で説明します。必要なのは、どのような書式で出力するかという要求を満たすコードを書くことです。

どの例も「リセット」ボタンがあるので、コードが動かなくなった場合は使用してみてください。もし本当に行き詰まってしまった場合には、「答えを見る」ボタンを押すことで、回答を見ることができます。

### 挨拶を選別する

まずは簡単な問題から始めます。挨拶状に使用するメッセージの配列の中から、クリスマスのメッセージのみを選別し、並べ替えてください。やるべきことは、メッセージがクリスマスメッセージであることを判定しその場合のみ表示されるよう、`if ( ... )` 文の条件式を埋めることです。

1. まず、各メッセージがどうであればクリスマス(Christmas)メッセージとみなせるかを考えてください。こういうメッセージ中にはどんな文字列があり、それが存在するか判定するのにどんなメソッドが使えるでしょうか？
2. それから、オペランド 1 演算子 オペランド 2 の形で条件を書いてください。その条件は左辺と右辺が等しいかというものでしょうか？もしくは左辺のメソッドの戻り値が、右辺であるかというものでしょうか。
3. ヒント: 今回の場合、メソッドの戻り値の結果が何でないかを判定するとよいでしょう。
```

<h2>出力結果</h2>

<div class="output" style="min-height: 125px;">
  <ul></ul>
</div>

<h2>コードエディタ</h2>
<p class="a11y-label">
  コードエディタから抜けるには Esc
  キーを押して下さい(タブキーではタブ文字を挿入します)。
</p>

<textarea id="code" class="playable-code" style="height: 290px; width: 95%">
const list = document.querySelector('.output ul');
list.innerHTML = '';
let greetings = ['Happy Birthday!',
                 'Merry Christmas my love',
                 'A happy Christmas to all the family',
                 'You\'re all I want for Christmas',
                 'Get well soon'];

for (let i = 0; i < greetings.length; i++) {
  let input = greetings[i];
  // 以下の条件の括弧内の内容を書き換えて下さい
  if (greetings[i]) {
    let listItem = document.createElement('li');
    listItem.textContent = input;
    list.appendChild(listItem);
  }
}
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="リセット" />
  <input id="solution" type="button" value="答えを見る" />
</div>
```

```
html {
  font-family: sans-serif;
}

h2 {
  font-size: 16px;
}

.a11y-label {
  margin: 0;
  text-align: right;
  font-size: 0.7rem;
  width: 98%;
}

body {
  margin: 10px;
  background: #f5f9fa;
}
```

```
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
let code = textarea.value;
let userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "答えを見る";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "答えを見る") {
    textarea.value = solutionEntry;
    solution.value = "答えを隠す";
  } else {
    textarea.value = userEntry;
    solution.value = "答えを見る";
  }
  updateCode();
});

const jsSolution =
  "const list = document.querySelector('.output ul');" +
  "\nlist.innerHTML = '';" +
  "\nlet greetings = ['Happy Birthday!'," +
  "\n 'Merry Christmas my love'," +
  "\n 'A happy Christmas to all the family'," +
  "\n 'You\\'re all I want for Christmas'," +
  "\n 'Get well soon'];" +
  "\n" +
  "\nfor (let i = 0; i < greetings.length; i++) {" +
  "\n let input = greetings[i];" +
  "\n if (greetings[i].indexOf('Christmas') !== -1) {" +
  "\n let result = input;" +
  "\n let listItem = document.createElement('li');" +
  "\n listItem.textContent = result;" +
  "\n list.appendChild(listItem);" +
  "\n }" +
  "\n}";

let solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// タブキーでテキストエリアから抜けてしまうのを防ぎ、
// 代わりにカーソル位置にタブ文字を挿入する

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  const scrollPos = textarea.scrollTop;
  const caretPos = textarea.selectionStart;
  const front = textarea.value.substring(0, caretPos);
  const back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );

  textarea.value = front + text + back;
  caretPos = caretPos + text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// ユーザーがテキストエリアのコードを書き換える度に userCode を毎回更新する

textarea.onkeyup = function () {
  // ユーザーのコードが表示されているときのみ状態を保存し、
  // 答えのコードでユーザーコードが上書きされないようにする
  if (solution.value === "答えを見る") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};
```

### 単語の最初の文字を大文字に直す

この課題ではイギリスの都市名を題材にします。ところが、単語の大文字と小文字がぐちゃぐちゃになっています。そこで最初の文字を大文字にし、残りをすべて小文字にしてみましょう。こういう手順でやってみます:

1. `input` 変数に入っている文字列全体を小文字に変換し、それを新しい変数に格納します。
2. この新しい変数にある最初の文字を取り出し、さらに別の変数に格納します。
3. その新しい変数を部分文字列とし、小文字ばかりの文字列にある最初の文字を置換して、大文字化します。この置換処理の結果を別の新しい変数に格納します。
4. `input` 変数ではなく、最終結果が `result` 変数の値に代入されるよう修正します。

**メモ:** ヒント — 文字列メソッドのパラメーターは文字列リテラルである必要はありません。変数でもよいですし、メソッドが呼び出される変数であっても構いません。
```

<h2>出力結果</h2>

<div class="output" style="min-height: 125px;">
  <ul></ul>
</div>

<h2>コードエディタ</h2>
<p class="a11y-label">
  コードエディタから抜けるには Esc
  キーを押して下さい(タブキーではタブ文字を挿入します)。
</p>

<textarea id="code" class="playable-code" style="height: 250px; width: 95%">
const list = document.querySelector('.output ul');
list.innerHTML = '';
let cities = ['lonDon', 'ManCHESTer', 'BiRmiNGHAM', 'liVERpoOL'];
for(var i = 0; i < cities.length; i++) {
  var input = cities[i];
  // この下にコードを書いて下さい

  let result = input;
  let listItem = document.createElement('li');
  listItem.textContent = result;
  list.appendChild(listItem);
}
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="リセット" />
  <input id="solution" type="button" value="答えを見る" />
</div>
```

```
html {
  font-family: sans-serif;
}

h2 {
  font-size: 16px;
}

.a11y-label {
  margin: 0;
  text-align: right;
  font-size: 0.7rem;
  width: 98%;
}

body {
  margin: 10px;
  background: #f5f9fa;
}
```

```
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
let code = textarea.value;
let userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "答えを見る";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "答えを見る") {
    textarea.value = solutionEntry;
    solution.value = "答えを隠す";
  } else {
    textarea.value = userEntry;
    solution.value = "答えを見る";
  }
  updateCode();
});

const jsSolution =
  "const list = document.querySelector('.output ul');" +
  "\nlist.innerHTML = '';" +
  "\nlet cities = ['lonDon', 'ManCHESTer', 'BiRmiNGHAM', 'liVERpoOL'];" +
  "\n" +
  "\nfor (let i = 0; i < cities.length; i++) {" +
  "\n let input = cities[i];" +
  "\n let lower = input.toLowerCase();" +
  "\n let firstLetter = lower.slice(0,1);" +
  "\n let capitalized = lower.replace(firstLetter,firstLetter.toUpperCase());" +
  "\n let result = capitalized;" +
  "\n let listItem = document.createElement('li');" +
  "\n listItem.textContent = result;" +
  "\n list.appendChild(listItem);" +
  "\n" +
  "\n}";

let solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// タブキーでテキストエリアから抜けてしまうのを防ぎ、
// 代わりにカーソル位置にタブ文字を挿入する

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  const scrollPos = textarea.scrollTop;
  const caretPos = textarea.selectionStart;
  const front = textarea.value.substring(0, caretPos);
  const back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );

  textarea.value = front + text + back;
  caretPos = caretPos + text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// ユーザーがテキストエリアのコードを書き換える度に userCode を毎回更新する

textarea.onkeyup = function () {
  // ユーザーのコードが表示されているときのみ状態を保存し、
  // 答えのコードでユーザーコードが上書きされないようにする
  if (solution.value === "答えを見る") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};
```

### 既存の部分から新しい文字列を作成する

最後の課題では、北イングランドの駅に関する情報が入っている文字列を格納している配列を扱います。この文字列にはデータ項目として、アルファベット 3 文字の駅コード、次にコンピューター読み取り用のデータ、次にセミコロン、そして人間が読める駅名が入っています。例:
```

MAN675847583748sjt567654;Manchester Piccadilly
```

駅コードと駅名を抽出し、それを文字列にまとめてこのような形にします:
```

MAN: Manchester Piccadilly
```

このようにプログラムしてみましょう:

1. アルファベット 3 文字のコードを抽出し、新しい変数に格納します。
2. セミコロンの文字インデックス番号を検索します。
3. セミコロンの文字インデックス番号を参照位置にして、人が読める駅名を抽出し、新しい変数に格納します。
4. この 2 つの新しい変数と文字列リテラルとを連結し、最終的な文字列を作成します。
5. `input` 変数ではなく、この最終結果が `result` 変数の値に代入されるよう修正します。
```

<h2>出力結果</h2>

<div class="output" style="min-height: 125px;">
  <ul></ul>
</div>

<h2>コードエディタ</h2>
<p class="a11y-label">
  コードエディタから抜けるには Esc
  キーを押して下さい(タブキーではタブ文字を挿入します)。
</p>

<textarea id="code" class="playable-code" style="height: 285px; width: 95%">
const list = document.querySelector('.output ul');
list.innerHTML = '';
let stations = ['MAN675847583748sjt567654;Manchester Piccadilly',
                'GNF576746573fhdg4737dh4;Greenfield',
                'LIV5hg65hd737456236dch46dg4;Liverpool Lime Street',
                'SYB4f65hf75f736463;Stalybridge',
                'HUD5767ghtyfyr4536dh45dg45dg3;Huddersfield'];

for (var i = 0; i < stations.length; i++) {
  let input = stations[i];
  // この下にコードを書いてください

  let result = input;
  let listItem = document.createElement('li');
  listItem.textContent = result;
  list.appendChild(listItem);
}
</textarea>

<div class="playable-buttons">
  <input id="reset" type="button" value="リセット" />
  <input id="solution" type="button" value="答えを見る" />
</div>
```

```
html {
  font-family: sans-serif;
}

h2 {
  font-size: 16px;
}

.a11y-label {
  margin: 0;
  text-align: right;
  font-size: 0.7rem;
  width: 98%;
}

body {
  margin: 10px;
  background: #f5f9fa;
}
```

```
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
let code = textarea.value;
let userEntry = textarea.value;

function updateCode() {
  eval(textarea.value);
}

reset.addEventListener("click", function () {
  textarea.value = code;
  userEntry = textarea.value;
  solutionEntry = jsSolution;
  solution.value = "答えを見る";
  updateCode();
});

solution.addEventListener("click", function () {
  if (solution.value === "答えを見る") {
    textarea.value = solutionEntry;
    solution.value = "答えを隠す";
  } else {
    textarea.value = userEntry;
    solution.value = "答えを見る";
  }
  updateCode();
});

const jsSolution =
  "const list = document.querySelector('.output ul');" +
  "\nlist.innerHTML = '';" +
  "\nlet stations = ['MAN675847583748sjt567654;Manchester Piccadilly'," +
  "\n 'GNF576746573fhdg4737dh4;Greenfield'," +
  "\n 'LIV5hg65hd737456236dch46dg4;Liverpool Lime Street'," +
  "\n 'SYB4f65hf75f736463;Stalybridge'," +
  "\n 'HUD5767ghtyfyr4536dh45dg45dg3;Huddersfield'];" +
  "\n" +
  "\nfor (let i = 0; i < stations.length; i++) {" +
  "\n let input = stations[i];" +
  "\n let code = input.slice(0,3);" +
  "\n let semiC = input.indexOf(';');" +
  "\n let name = input.slice(semiC + 1);" +
  "\n let result = code + ': ' + name;" +
  "\n let listItem = document.createElement('li');" +
  "\n listItem.textContent = result;" +
  "\n list.appendChild(listItem);" +
  "\n}";

let solutionEntry = jsSolution;

textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);

// タブキーでテキストエリアから抜けてしまうのを防ぎ、
// 代わりにカーソル位置にタブ文字を挿入する

textarea.onkeydown = function (e) {
  if (e.keyCode === 9) {
    e.preventDefault();
    insertAtCaret("\t");
  }

  if (e.keyCode === 27) {
    textarea.blur();
  }
};

function insertAtCaret(text) {
  const scrollPos = textarea.scrollTop;
  const caretPos = textarea.selectionStart;
  const front = textarea.value.substring(0, caretPos);
  const back = textarea.value.substring(
    textarea.selectionEnd,
    textarea.value.length,
  );

  textarea.value = front + text + back;
  caretPos = caretPos + text.length;
  textarea.selectionStart = caretPos;
  textarea.selectionEnd = caretPos;
  textarea.focus();
  textarea.scrollTop = scrollPos;
}

// ユーザーがテキストエリアのコードを書き換える度に userCode を毎回更新する

textarea.onkeyup = function () {
  // ユーザーのコードが表示されているときのみ状態を保存し、
  // 答えのコードでユーザーコードが上書きされないようにする
  if (solution.value === "答えを見る") {
    userEntry = textarea.value;
  } else {
    solutionEntry = textarea.value;
  }

  updateCode();
};
```

スキルテスト
------

この記事の最後に達しましたが、最も大切な情報を覚えていますか？次に進む前に、この情報が身に付いたかどうかを確認するテストがあります。Test your skills: Strings (en-US) を見てください。

結論
--

プログラミング、特に JavaScript では単語や文を処理できるようになることが大事であるという現実から逃れることはできません。それはウェブサイトは人とのコミュニケーションがすべてとなるためです。この記事では、文字列を処理するのに当面知っておくべき基礎を解説しました。今後もっと複雑なトピックに進んで行くときに役立つはずです。次に、配列を見てみます。重要なデータ型のうち、短期間で押さえておくべき最後の 1 つです。

* 前のページ
* Overview: First steps
* 次のページ


スキルテスト: 配列 - ウェブ開発を学ぶ

スキルテスト: 配列
==========

このスキルテストの目的は、配列 の記事を理解しているかどうかを評価することです。

**メモ:** 下記のインタラクティブエディターで回答を試すことができますが、コードをダウンロードし、 CodePen、 jsFiddle、 Glitch などのオンラインツールを利用すると、タスクに取り組むのに役立つかもしれません。

行き詰まったなら、私たちに助けを求めてください。 — このページの下部の 評価またはさらなる支援 の節を参照

**メモ:** 下記の作成では、あなたのコードにエラーがあれば、このページの結果パネル（または、ダウンロード可能なバージョンでは、ブラウザーの JavaScript コンソール）に出力され、答えにたどり着くための助けとなります。

配列 1
----

まずは配列の基礎から始めましょう。このタスクでは、 3 つの要素を含む配列を作成し、 `myArray` という変数に格納します。要素は何でも構いません。好きな食べ物やバンドにしてはいかがでしょうか？

次に、単純な括弧の記法による代入で、最初の 2 つの要素を変更してください。そのあと、配列の先頭に新しい要素を追加します。

完成例を再現するために、以下のライブコードを更新してみてください。

このタスクのための準備済みファイルをダウンロード して、自分のエディターまたはオンラインエディターで動かせるようにしましょう。

配列 2
----

さて、別のタスクに移りましょう。ここでは、取り組むべき文字列が与えられています。すべきことは次の通りです。

1. 文字列を配列に変換してください。その過程で、 `+` という文字を取り除きます。結果を `myArray` という変数に格納します。
2. 配列の要素数を、 `arrayLength` という変数に格納します。
3. 配列の末尾の要素を、 `lastItem` という変数に格納します。

完成例を再現するために、以下のライブコードを更新してみてください:

このタスクのための準備済みファイルをダウンロード して、自分のエディターまたはオンラインエディターで動かせるようにしましょう。

配列 3
----

このタスクでは、我々が用意した配列を出発点として、やや逆行する作業を行います。具体的には、

1. 配列の末尾の要素を削除します。
2. 配列の末尾に新たに 2 つの名前を追加します。
3. 配列の各要素を調べて、要素のインデックス番号を丸括弧で囲み、名前の後ろに追記します。例: `Ryu (0)`。 なお、このやり方は配列の記事では教えていませんので、自分で調べる必要があるでしょう。
4. 最後に、配列の各要素を "`-`" で区切って 1 つの文字列に結合し、 `myString` という変数に格納します。

完成例を再現するために、以下のライブコードを更新してみてください。

このタスクのための準備済みファイルをダウンロード して、自分のエディターまたはオンラインエディターで動かせるようにしましょう。

配列 4
----

このタスクでは、出発点として鳥の名前を列挙した配列が与えられています。

* `"Eagles"` 要素のインデックスを特定し、そのインデックスを用いて `"Eagles"` 要素を削除してください。
* この配列から、名前が"E"で始まる鳥だけを含む `eBirds` という名前の新しい配列を作成してください。ちなみに、 `startsWith()` は文字列が特定の文字で始まるかどうかを調べるのにとても役立ちます。

`"Emus,Egrets"` が表示されたら成功です。

このタスクのための準備済みファイルをダウンロード して、自分のエディターまたはオンラインエディターで動かせるようにしましょう。

評価またはさらなる支援
-----------

上記のインタラクティブエディターで、これらの例を練習することができます。

作品を評価してもらいたい、または行き詰まって助けを求めたい場合は次のようにしてください。

1. 作品を、CodePen, jsFiddle, または Glitch. のような共有可能なオンラインエディターに置いてください。いちから自身でコードを記述することもできますし、上記のセクションでリンク付けされている準備済みファイルを利用するのもよいでしょう。
2. MDN Discourse forum Learning category で評価を希望したり、助けを求める投稿をしてください。（英語）投稿には以下のことを書きましょう。
    * "Assessment wanted for Variables 1 skill test"のような説明的なタイトル
    * これまで試したことの詳細、そして私たちに何をしてほしいか。例えば、行き詰まって助けが必要だとか、評価をしてほしいなど。
    * 評価してほしかったり、助けが必要な作品へのリンク。共有可能なオンラインエディター（上記ステップ 1 で述べたもの）を使って。これは分かりやすくするためのよい手法です。 — コードが見えない状態で、コーディングの問題を抱えている人を助けることはとても難しいことです。
    * 実際のタスクまたは評価ページのリンク。そうすれば、どの質問に助けが必要かがわかります。