diff options
author | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
---|---|---|
committer | Peter Bengtsson <mail@peterbe.com> | 2020-12-08 14:40:17 -0500 |
commit | 33058f2b292b3a581333bdfb21b8f671898c5060 (patch) | |
tree | 51c3e392513ec574331b2d3f85c394445ea803c6 /files/ja/learn/javascript/client-side_web_apis | |
parent | 8b66d724f7caf0157093fb09cfec8fbd0c6ad50a (diff) | |
download | translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.gz translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.tar.bz2 translated-content-33058f2b292b3a581333bdfb21b8f671898c5060.zip |
initial commit
Diffstat (limited to 'files/ja/learn/javascript/client-side_web_apis')
8 files changed, 3730 insertions, 0 deletions
diff --git a/files/ja/learn/javascript/client-side_web_apis/client-side_storage/index.html b/files/ja/learn/javascript/client-side_web_apis/client-side_storage/index.html new file mode 100644 index 0000000000..da7b36abbf --- /dev/null +++ b/files/ja/learn/javascript/client-side_web_apis/client-side_storage/index.html @@ -0,0 +1,796 @@ +--- +title: クライアント側ストレージ +slug: Learn/JavaScript/Client-side_web_APIs/Client-side_storage +tags: + - API + - Article + - Beginner + - CodingScripting + - Guide + - IndexedDB + - JavaScript + - Learn + - Storage + - Web Storage + - ウェブストレージ + - ガイド + - 保存 + - 初心者 + - 学習 +translation_of: Learn/JavaScript/Client-side_web_APIs/Client-side_storage +--- +<p>{{LearnSidebar}}</p> + +<div>{{PreviousMenu("Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</div> + +<p class="summary">モダン・ウェブブラウザーは、ユーザーの許可のもとにウェブサイトがユーザーのコンピューター上にデータを保存して必要なときにそのデータを取得するための、いくつもの方法をサポートしています。このことにより、長期記憶のためにデータを存続させること、オフライン利用のためにサイトまたは文書を保存すること、サイトについてのユーザー独自の設定を保持すること、などなどが可能になります。本記事では、これらがどのようにして機能するのかについてのごく基本的な点を説明します。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提知識:</th> + <td>JavaScript の基本 (<a href="/ja/docs/Learn/JavaScript/First_steps">JavaScript の第一歩</a>、<a href="/ja/docs/Learn/JavaScript/Building_blocks">JavaScript の構成要素</a>、 <a href="/ja/docs/Learn/JavaScript/Objects">JavaScript オブジェクト入門</a> を参照)、<a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">ウェブ APIの紹介</a></td> + </tr> + <tr> + <th scope="row">学習目標:</th> + <td>アプリケーション・データを保存するためのクライアント側のストレージ API の使い方を学ぶこと</td> + </tr> + </tbody> +</table> + +<h2 id="Client-side_storage" name="Client-side_storage">クライアント側での保存って?</h2> + +<p>MDN 学習エリアの他の箇所で、<a href="/ja/docs/Learn/Server-side/First_steps/Client-Server_overview#Static_sites">静的なサイト</a> と <a href="/ja/docs/Learn/Server-side/First_steps/Client-Server_overview#Dynamic_sites">動的なサイト</a> の違いについて述べました。ほとんどの主要なモダン・ウェブサイトは動的です。つまり、ある種のデータベース (サーバー側のストレージ) を使ってデータをサーバー上に記憶し、必要なデータを取得するために<a href="/ja/docs/Learn/Server-side">サーバー側</a> のコードを実行し、そのデータを静的なページ雛型に挿入し、結果として出来上がった HTML をクライアントに提供して、それがユーザーのブラウザーによって表示されるようにします。</p> + +<p>クライアント側での保存は類似の原理に基づいて機能しますが、これにはいくつかの異なる使い道があります。クライアント側での保存は、クライアント上に (つまりユーザーのマシン上に) データを保存して必要なときにそのデータを取得できるようにしてくれる、いくつかの JavaScript API から構成されています。クライアント側での保存には、たとえば以下のように多くの異なる用途があります。</p> + +<ul> + <li>サイトの環境設定を個人に合わせる (たとえば、カスタム・ウィジェット、カラースキーム、またはフォントサイズについて、ユーザーが選択したものを表示する、など)</li> + <li>以前のサイト上の行動を存続させる (たとえば、前回のセッションからの買い物かごの中身を記憶しておく、ユーザーが以前ログインしたかどうかを憶えておく、など)</li> + <li>サイトをより速く (かつ、潜在的にはより費用をかけずに) ダウンロードできるように、または、ネットワーク接続なしでサイトが利用可能となるように、データと資産をローカルに保存する</li> + <li>ウェブ・アプリケーションが生成した文書を、オフラインで利用するために、ローカルに保存する</li> +</ul> + +<p>クライアント側での保存とサーバ側での保存は、しばしば共に使われます。たとえば、複数の音楽ファイル (おそらくウェブゲームまたは音楽プレーヤー・アプリに使われる) をダウンロードし、それらの音楽ファイルをクライアント側のデータベース内に保存し、必要に応じて再生する、といったことが可能でしょう。ユーザーは、それらの音楽ファイルをただ一度ダウンロードするだけで済むでしょう。その後の訪問では、音楽ファイルは、ダウンロードされる代わりにデータベースから取得されるでしょう。</p> + +<div class="note"> +<p><strong>注</strong>: クライアント側のストレージ API を使って保存できるデータの量には、上限があります (もしかすると、個別の API ごとの上限と、累積的な上限の双方があるかもしれません)。正確な上限は、ブラウザーごとに異なりますし、もしかすると、ユーザーの設定によることもあるかもしれません。より詳しくは、<a href="/ja/docs/Web/API/IndexedDB_API/Browser_storage_limits_and_eviction_criteria">ブラウザーのストレージ制限と削除基準</a> を参照。</p> +</div> + +<h3 id="Old_school_Cookies" name="Old_school_Cookies">旧式な方法: クッキー</h3> + +<p>クライアント側での保存という考え方には、長い歴史があります。ウェブの初期から、ウェブサイト上でのユーザー体験を個別化するための情報を記憶するべく、サイトは <a href="/ja/docs/Web/HTTP/Cookies">クッキー</a> を使ってきました。そうしたクッキーは、ウェブ上で一般的に使われるクライアント側での保存の、最初期の形式です。</p> + +<p>最近では、クライアント側のデータを保存するためのより簡単な仕組みが利用できるため、この記事ではクッキーの使用方法については説明しません。ただし、これはクッキーが現代のウェブで完全に役に立たないことを意味するわけではありません。クッキーは、ユーザーの個別化や状態に関連するデータを保存するために今でも一般的に使用されています。たとえば、セッション ID やアクセストークンです。クッキーの詳細については、<a href="/ja/docs/Web/HTTP/Cookies">HTTP cookies</a> の記事を参照してください。</p> + +<h3 id="New_school_Web_Storage_and_IndexedDB" name="New_school_Web_Storage_and_IndexedDB">新方式派: ウェブストレージと IndexedDB</h3> + +<p>前述の「簡単な」機能には次のものがあります:</p> + +<ul> + <li><a href="/ja/docs/Web/API/Web_Storage_API">Web Storage API</a> は、名前とそれに対応する値とからなる小規模なデータ項目を保存したり取り出したりするための、とても簡潔な構文を提供しています。これは、ユーザーの名前、ユーザーがログインしているかどうか、画面の背景にどの色を使うべきか、といったような、何らかの単純なデータを記憶するだけでよい場合に有用です。</li> + <li><a href="/ja/docs/Web/API/IndexedDB_API">IndexedDB API</a> は、複雑なデータを保存するための完全なデータベース・システムをブラウザーに提供しています。これは、顧客レコードの完全な集合から、音声ファイルまたは動画ファイルのような複雑なデータ型にいたるまでの、種々の物事に対して使えます。</li> +</ul> + +<p>以下ではこれらの API について学ぶことになります。</p> + +<h3 id="The_future_Cache_API" name="The_future_Cache_API">将来: キャッシュ API</h3> + +<p>いくつかのモダン・ブラウザーは、新しい {{domxref("Cache")}} API をサポートしています。この API は、特定の要求に対する HTTP 応答を記憶しておくために設計されています。 また、ネットワーク接続なしに後でサイトを利用できるように、ウェブサイト資産をオフラインに記憶しておく、といったようなことをするうえで非常に有用です。キャッシュは通常、<a href="/ja/docs/Web/API/Service_Worker_API">サービスワーカー API</a> と組み合わせて利用します。もっとも、必ずそうしなくてはならないというわけではありません。</p> + +<p>キャッシュとサービスワーカーの利用は先進的な話題であり、この記事ではそれほど詳しくは扱いません。とは言うものの、後述の {{anch("Offline asset storage")}} の節では、簡単な例をお見せします。</p> + +<h2 id="Storing_simple_data_—_web_storage" name="Storing_simple_data_—_web_storage">単純なデータを保存する——ウェブストレージ</h2> + +<p><a href="/ja/docs/Web/API/Web_Storage_API">Web Storage API</a> は大変使いやすいものです。(文字列や数などに限定された) データからなる単純な名前/値のペアを保存し、必要なときにその値を取り出します。</p> + +<h3 id="Basic_syntax" name="Basic_syntax">基本的構文</h3> + +<p>以下に方法を示しましょう。</p> + +<ol> + <li> + <p>まず、GitHub 上の <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/index.html">ウェブストレージの空白テンプレート</a> へ行ってください (新規タブで開いてください)。</p> + </li> + <li> + <p>ブラウザーのデベロッパー・ツールの JavaScript コンソールを開いてください。</p> + </li> + <li> + <p>ウェブストレージ・データのすべては、ブラウザー内部の二つのオブジェクト的な構造体の中に含まれます。つまり、{{domxref("Window.sessionStorage", "sessionStorage")}} と {{domxref("Window.localStorage", "localStorage")}} の中です。前者は、ブラウザーが開いている限り、データを存続させます (ブラウザーを閉じるとデータは失われます)。後者は、ブラウザーを閉じて、それから再びブラウザーを開いた後でさえも、データを存続させます。一般的には後者の方がより有用なので、本記事では後者を使います。</p> + + <p>{{domxref("Storage.setItem()")}} メソッドによって、ストレージ内にデータ項目を保存できます。このメソッドは二つの引数をとります。すなわち、その項目の名前と、その値です。JavaScript コンソールに以下のように打ち込んでみてください (もしお望みなら、値は御自分のお名前に変更してくださいね!)</p> + + <pre class="brush: js notranslate">localStorage.setItem('name','Chris');</pre> + </li> + <li> + <p>{{domxref("Storage.getItem()")}} メソッドは一つの引数をとります。つまり、取り出したいデータ項目の名前です。そして、このメソッドは、その項目の値を返します。今度は JavaScript コンソールに以下の行を打ち込んでください。</p> + + <pre class="brush: js notranslate">let myName = localStorage.getItem('name'); +myName</pre> + + <p>2 行目を入力すると、<code>myName</code> という変数が今や <code>name</code> というデータ項目の値を保有していることが分かるはずです。</p> + </li> + <li> + <p>{{domxref("Storage.removeItem()")}} メソッドは一つの引数をとります。つまり、削除したいデータ項目の名前です。このメソッドは、ウェブストレージからその項目を削除します。JavaScript コンソールに以下の行を打ち込んでください。</p> + + <pre class="brush: js notranslate">localStorage.removeItem('name'); +let myName = localStorage.getItem('name'); +myName</pre> + + <p>3 行目は、今度は <code>null</code> を返すはずです。というのも、もはや <code>name</code> という項目はウェブストレージ内に存在しないからです。</p> + </li> +</ol> + +<h3 id="The_data_persists!" name="The_data_persists!">データが存続する!</h3> + +<p>ウェブストレージの一つの重要な特徴は、ページ・ロードをまたいで (さらに、<code>localStorage</code> の場合には、ブラウザーを終了させてさえも) データが存続する、という点です。この特徴が機能しているところを見てみましょう。</p> + +<ol> + <li> + <p>もう一度、ウェブストレージの空白テンプレートを開いてください。ただし今回は、本チュートリアルを開いたのとは別のブラウザーで開いてください! こうすることで、取り扱いがしやすくなるでしょう。</p> + </li> + <li> + <p>以下の行をブラウザーの JavaScript コンソールに打ち込んでください。</p> + + <pre class="brush: js notranslate">localStorage.setItem('name','Chris'); +let myName = localStorage.getItem('name'); +myName</pre> + + <p><code>name</code> という項目が返されるのが分かるはずです。</p> + </li> + <li> + <p>さてここでブラウザーを終了させてから再び起動して開いてください。</p> + </li> + <li> + <p>再び、以下の行を入力してください。</p> + + <pre class="brush: js notranslate">let myName = localStorage.getItem('name'); +myName</pre> + + <p>ブラウザーを終了させてから再び開いたというのに、それでも依然として値が利用可能である、ということが分かるはずです。</p> + </li> +</ol> + +<h3 id="Separate_storage_for_each_domain" name="Separate_storage_for_each_domain">ドメインごとに別々のストレージ</h3> + +<p>ドメインごとに (ブラウザーにロードされた別々のウェブ・アドレスごとに)、別々のデータストアがあります。二つのウェブサイト (たとえば google.com と amazon.com) をロードして、一方のウェブサイトで項目を保存してみると、その項目は他方のウェブサイトでは利用できない、と分かるでしょう。</p> + +<p>これには意義があります。もしウェブサイト同士がお互いのデータを見ることが可能であったら起こるであろうセキュリティ問題を想像できますよね!</p> + +<h3 id="A_more_involved_example" name="A_more_involved_example">さらに込み入った例</h3> + +<p>どのようにウェブストレージを使えるのかについてお教えするために、簡単で基礎的な事例を書くことによって、(ドメインごとのストレージという) この新たに得た知識を応用してみましょう。この事例では、名前を入力できるようにします。その入力の後、個人に合わせた挨拶を表示するべく、ページが更新されます。この状態は、ページ/ブラウザーのリロードをまたいでも存続するでしょう。なぜなら、名前がウェブストレージに記憶されているからです。</p> + +<p>この例の HTML を <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/web-storage/personal-greeting.html">personal-greeting.html</a> で入手できます。これは、ヘッダーとコンテンツとフッターを備えた簡素なウェブサイトと、名前を入力するためのフォームとを含みます。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/15735/web-storage-demo.png" style="display: block; margin: 0 auto;"></p> + +<p>この例を組み上げましょう。すると、これがどのように機能するのか理解できるでしょう。</p> + +<ol> + <li> + <p>まず、御自分のコンピュータ上の新規ディレクトリに、<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/web-storage/personal-greeting.html">personal-greeting.html</a> というファイルのローカルコピーを作ってください。</p> + </li> + <li> + <p>次に、<code>index.js</code> と呼ばれる JavaScript ファイルを、HTML がどのように参照しているのかに注意してください (40 行目を参照)。これ (<code>index.js</code>) を作成して、そこに JavaScript コードを書き込む必要があります。HTML ファイルと同じディレクトリに <code>index.js</code> というファイルを作成してください。</p> + </li> + <li> + <p>この例で操作する必要のある HTML 項目 (features) のすべてに対する参照を作るところから取り掛かりましょう。それらの参照のすべてを定数として作ります。なぜなら、これらの参照は、アプリのライフサイクル内で変化する必要がないからです。以下の行を JavaScript ファイルに追加してください。</p> + + <pre class="brush: js notranslate">// 必要な定数を作ります。 +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');</pre> + </li> + <li> + <p>次に、送信ボタンが押されたときにフォームが実際にこのフォーム自体を送信することをやめさせるための、小規模なイベント・リスナーを含める必要があります。というのも、こうした送信は所望の振る舞いではないからです。以下に示すスニペットを、前のコードに追加してください。</p> + + <pre class="brush: js notranslate">// ボタンが押されたときにフォームが送信することをやめさせます。 +form.addEventListener('submit', function(e) { + e.preventDefault(); +});</pre> + </li> + <li> + <p>さてここで、イベント・リスナーを追加せねばなりません。そのイベント・リスナーのハンドラー関数は、"Say hello" ボタンがクリックされたときに実行されます。それぞれの断片が何を行うのかはコメントで詳しく説明してありますが、本質的にここでは、ユーザーがテキスト入力ボックスに入力した名前をとってきて、<code>setItem()</code> を用いてその名前をウェブストレージに保存し、その後、実際のウェブサイト上のテキストの更新を扱う <code>nameDisplayCheck()</code> と呼ばれる関数を実行しています。これをコードの末尾に加えてください。</p> + + <pre class="brush: js notranslate">// 'Say hello' ボタンがクリックされたら関数を実行します。 +submitBtn.addEventListener('click', function() { + // 入力された名前をウェブストレージに保存します。 + localStorage.setItem('name', nameInput.value); + // 個人に合わせた挨拶を表示するとともにフォーム表示を更新する + // 措置をとるべく、nameDisplayCheck() を実行します。 + nameDisplayCheck(); +});</pre> + </li> + <li> + <p> この時点で、"Forget" ボタンがクリックされたときに関数を実行するためのイベント・ハンドラーも必要です。"Forget" ボタンは、"Say hello" ボタンがクリックされた後にのみ表示されます (二つのフォーム状態が行ったり来たり切り替わります)。この関数では、<code>removeItem()</code> を用いてウェブストレージから <code>name</code> という項目を削除し、その後、表示を更新するために <code>nameDisplayCheck()</code> を再び実行します。これを末尾に付け加えてください。</p> + + <pre class="brush: js notranslate">// 'Forget' ボタンがクリックされたら関数を実行します。 +forgetBtn.addEventListener('click', function() { + // 保存してある名前をウェブストレージから削除します。 + localStorage.removeItem('name'); + // 再び一般的な挨拶を表示するとともにフォーム表示を更新する + // 措置をとるべく、nameDisplayCheck() を実行します。 + nameDisplayCheck(); +});</pre> + </li> + <li> + <p>さて今や <code>nameDisplayCheck()</code> という関数そのものを定義すべきときです。ここでは、<code>localStorage.getItem('name')</code> を条件テストとして用いることにより、<code>name</code> という項目がウェブストレージに保存済みかどうかを調べます。もし保存済みなら、この呼び出しは <code>true</code> と評価されるでしょう。もし保存済みでなければ、<code>false</code> になるでしょう。もし <code>true</code> なら、個人に合わせた挨拶を表示し、フォームの "forget" の部分を表示し、フォームの "Say hello" の部分を隠します。もし <code>false</code> なら、一般的な挨拶を表示し、逆のことをします (フォームの "forget" の部分を隠し、フォームの "Say hello" の部分を表示します)。またもや末尾に以下のコードを追加してください。</p> + + <pre class="brush: js notranslate">// nameDisplayCheck() という関数を定義します。 +function nameDisplayCheck() { + // 'name' というデータ項目がウェブストレージに保存されているかどうかを調べます。 + if(localStorage.getItem('name')) { + // もし保存されていたら、個人に合わせた挨拶を表示します。 + let 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'; + } +}</pre> + </li> + <li> + <p>最後に、ページがロードされるたびに <code>nameDisplayCheck()</code> という関数を実行せねばなりません。もしそうしなければ、個人に合わせた挨拶は、ページのリロードをまたがってまでは持続しなくなってしまうでしょう。以下のものをコードの末尾に追加してください。</p> + + <pre class="brush: js notranslate">document.body.onload = nameDisplayCheck;</pre> + </li> +</ol> + +<p>例が完成しました。よくできましたね! 現時点で残っているのは、コードを保存して HTML ページをブラウザーでテストすることだけです。<a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/web-storage/personal-greeting.html">ライブ実行される完成版をここで</a> 見られます。</p> + +<div class="note"> +<p><strong>注</strong>: <a href="/ja/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API">ウェブストレージ API の使用</a> のところには、探究するにはほんの少しだけ更に複雑な別の例もあります。</p> +</div> + +<div class="note"> +<p><strong>注</strong>: 完成版のソースのうち <code><script src="index.js" defer></script></code> という行では、<code>defer</code> 属性により、ページをロードし終わるまでは {{htmlelement("script")}} 要素の中身を実行しないように指定しています。</p> +</div> + +<h2 id="Storing_complex_data_—_IndexedDB" name="Storing_complex_data_—_IndexedDB">複雑なデータを保存する—— IndexedDB</h2> + +<p><a href="/ja/docs/Web/API/IndexedDB_API">IndexedDB API</a> (ときには IDB と省略します) は、ブラウザーで利用可能であり、複雑で関係性のあるデータを保存できる、完全なデータベース・システムです。そしてそのデータの型は、文字列または数値のような単純な値に限定されません。動画や静止画像、そして、その他のものもほとんどすべて、IndexedDB インスタンスに保存できます。</p> + +<p>しかし、これは高くつきます。IndexedDB の使用は、ウェブストレージ API の使用よりも遥かに複雑なのです。本節では、IndexedDB ができることのうち本当に表面的なところに触れるだけですが、始めるのに十分なだけのことは、お伝えしましょう。</p> + +<h3 id="Working_through_a_note_storage_example" name="Working_through_a_note_storage_example">メモ書きの保存の事例を通して作業します</h3> + +<p>ここでは、メモ書きをブラウザーに保存して好きなときにそれを見たり消したりできるようにする事例を、見ていただきましょう。その際、その例は御自分で組み立てていただきますが、進行に合わせて、IDB の最も根本的な部分について御説明します。</p> + +<p>当該アプリは、以下のような見かけをしています。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/15744/idb-demo.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<p>メモ書きの各々には題名と何らかの本文があり、題名と本文のそれぞれは別々に編集できます。以下で見てゆく JavaScript コードには、何が起きているのかを理解する手助けとなる詳しいコメントがあります。</p> + +<h3 id="Getting_started" name="Getting_started">始めますよ</h3> + +<ol> + <li>まず、<code><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/index.html">index.html</a></code> と <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/style.css">style.css</a></code> と <code><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/index-start.js">index-start.js</a></code> というファイルのローカルコピーを、ローカルマシンの新規ディレクトリ内に作成してください。</li> + <li>ファイルを見てください。HTML がかなり簡潔なのがお分かりでしょう。これは、ヘッダーとフッターのあるウェブサイトです。また、メモ書きを表示する場所と、データベースに新たなメモ書きを入力するためのフォームとを含む、本文コンテンツ領域もあります。 CSS は、何が起きているのかをより明瞭にするための、ある種の簡素なスタイルづけを提供しています。JavaScript ファイルは、宣言された五つの定数を含んでいます。つまり、 内部にメモ書きを表示することになる {{htmlelement("ul")}} 要素への参照と、題名および本文の {{htmlelement("input")}} 要素への参照と、{{htmlelement("form")}} 自体への参照と、{{htmlelement("button")}} への参照とを含んでいます。</li> + <li>JavaScript ファイルの名前を <code>index.js</code> に変更してください。コードをそこに追加し始める準備がこれで整いました。</li> +</ol> + +<h3 id="Database_initial_set_up" name="Database_initial_set_up">データベースの初期設定</h3> + +<p>では、実際にデータベースを設定するために最初にすべきことを見てみましょう。</p> + +<ol> + <li> + <p>定数の宣言の下に、以下の行を追加してください。</p> + + <pre class="brush: js notranslate">// 開いたデータベースを記憶しておくためのデータベース・オブジェクトのインスタンスを作成します。 +let db;</pre> + + <p>ここでは、<code>db</code> と呼ばれる変数を宣言しています。これは後に、データベースを表すオブジェクトを記憶するのに使われます。この変数を何箇所かで使うつもりなので、物事を容易にするために、ここでこの変数を大域的に宣言しておきました。</p> + </li> + <li> + <p>次に、以下のものをコードの末尾に加えてください。</p> + + <pre class="brush: js notranslate">window.onload = function() { + +};</pre> + + <p>続きのコードはすべて、この <code>window.onload</code> イベント・ハンドラー関数——ウィンドウの {{event("load")}} イベントが発火したときに呼ばれます——の中に書いてゆきます。アプリが完全にロード動作を終えるよりも前には IndexedDB 機能を使おうとはしないよう保証するために、そうしています (もしそう保証しなかったら、失敗する可能性があります)。</p> + </li> + <li> + <p><code>window.onload</code> ハンドラーの中に、以下のものを追加してください。</p> + + <pre class="brush: js notranslate">// データベースを開きます。データベースは、まだ存在していない場合には +// 新規作成されます (後述の onupgradeneeded を参照)。 +let request = window.indexedDB.open('notes', 1);</pre> + + <p>この行では、<code>notes</code> と呼ばれるデータベースのバージョン <code>1</code> を開く <code>request</code> (要求) を作成します。もしそのデータベースがまだ存在しなければ、後述のコードによって新規作成されます。IndexedDB の全体を通じて、この要求パターンが非常に高頻度で使われることが、いずれお分かりになるでしょう。データベース操作には時間がかかります。その結果を待つ間、ブラウザーをハングさせることはお望みでないでしょうから、データベース操作は {{Glossary("asynchronous")}} (非同期) となっています。このことが意味するのは、結果は直ちに生じるのではなく、将来のいずれかの時点で生じるだろうということ、および、結果が出たときには通知されるということです。</p> + + <p>こういったことを IndexedDB で扱うために、要求オブジェクト (何とでも好きなように呼んで構いませんが、何を目的としたものなのかが明白になるので、<code>request</code> (要求) と呼んでおきました) を作成します。それから、要求が完了する、失敗する、などの際にコードを実行するために、いくつかのイベント・ハンドラーを使います。この点については、使用されているところを後で見ることになります。</p> + + <div class="note"> + <p><strong>注</strong>: バージョン番号は重要です。(たとえばテーブル構造を変更することによって) データベースをアップグレードしたい場合には、上げたバージョン番号や、<code>onupgradeneeded</code> ハンドラー (下記参照) の内部で指定される別のスキーマなどを使って、コードを再度実行せねばなりません。この簡単なチュートリアルでは、データベースのアップグレードは扱いません。</p> + </div> + </li> + <li> + <p>さて今度は、前に追加した分のすぐ下に、以下のイベント・ハンドラーを追加してください。今度もまた、<code>window.onload</code> ハンドラーの中への追加です。</p> + + <pre class="brush: js notranslate">// onerror ハンドラーは、データベースがうまく開けなかったことを意味します。 +request.onerror = function() { + console.log('Database failed to open'); +}; + +// onsuccess ハンドラーは、データベースがうまく開けたことを意味します。 +request.onsuccess = function() { + console.log('Database opened successfully'); + + // 開いたデータベース・オブジェクトを、db という変数に記憶します。この変数は、以下でたくさん使われます。 + db = request.result; + + // IDB 内の既存のメモ書きを表示するために、displayData() 関数を実行します。 + displayData(); +};</pre> + + <p>要求は失敗した、と伝えつつシステムが戻ってくる場合には、{{domxref("IDBRequest.onerror", "request.onerror")}} というハンドラーが実行されます。これによって、(要求が失敗したという) この問題に対処できるようになります。この簡単な例では、単に JavaScript コンソールにメッセージを印字します。</p> + + <p>他方、{{domxref("IDBRequest.onsuccess", "request.onsuccess")}} ハンドラーは、要求が成功裡に戻ってくる場合、つまりデータベースをうまく開けた場合に、実行されます。この場合、開いたデータベースを表すオブジェクトが、{{domxref("IDBRequest.result", "request.result")}} というプロパティで利用可能となります。それにより、データベースを操作できるようになります。後で使うために、と事前に作っておいた <code>db</code> という変数に、このオブジェクトを保存します。また、<code>displayData()</code> と呼ばれるカスタム関数も実行します。この関数は、データベース内のデータを {{HTMLElement("ul")}} 内部に表示します。すでにデータベース内にあるメモ書きが、ページがロードされ次第すぐに表示されるように、ここでこの関数を実行しています。この関数を定義する様子は、後で見ることにしましょう。</p> + </li> + <li> + <p>本節の最後では、データベースを設定するためには多分もっとも重要なイベント・ハンドラーを追加しましょう。つまり、{{domxref("IDBOpenDBRequest.onupgradeneeded", "request.onupgradeneeded")}} です。このハンドラーは、データベースがまだ設定されていなかった場合、あるいは、保存済みの既存のデータベースよりも上のバージョン番号でデータベースが開かれた場合 (アップグレードを行う場合) に、実行されます。前のハンドラーの下に、以下のコードを追加してください。</p> + + <pre class="brush: js notranslate">// これがまだ実行されていない場合に、データベースのテーブルを設定します。 +request.onupgradeneeded = function(e) { + // 開いたデータベースに対する参照を求めます。 + let db = e.target.result; + + // 自動的にインクリメントするキーを含んでおり、メモ書きを中に保存するための + // (基本的に一つのテーブルに類似した) objectStore を、作成します。 + let objectStore = db.createObjectStore('notes', { keyPath: 'id', autoIncrement:true }); + + // objectStore が含むことになるデータ項目を定義します。 + objectStore.createIndex('title', 'title', { unique: false }); + objectStore.createIndex('body', 'body', { unique: false }); + + console.log('Database setup complete'); +};</pre> + + <p>ここは、データベースのスキーマ (構造) ——すなわち、データベースが含む列 (ないしフィールド) の集合——を定義している箇所です。ここではまず、<code>e.target.result</code> (イベント・ターゲットの <code>result</code> というプロパティ) から、既存のデータベースへの参照を求めていますが、これ (<code>e.target</code> というイベント・ターゲット) は、<code>request</code> というオブジェクトです。この行は、<code>onsuccess</code> ハンドラーの中の <code>db = request.result;</code> という行と等価です。しかし、それとは別に、ここでこのようにする必要があります。なぜなら、<code>onupgradeneeded</code> ハンドラーは、(もし必要な場合には) <code>onsuccess</code> ハンドラーよりも前に実行されることになる——つまり、もしここでこのようにしておかなければ、<code>db</code> の値を利用できない——からです。</p> + + <p>それから、{{domxref("IDBDatabase.createObjectStore()")}} を用いて、開いたデータベースの内部に新たなオブジェクト・ストアを作成します。これは、従来のデータベース・システムにおける一つのテーブルと等価です。このオブジェクト・ストアには <code>notes</code> という名前をつけました。また、<code>id</code> と呼ばれる <code>autoIncrement</code> キーのフィールドも指定しました。新規レコードの各々において、このフィールドには、インクリメントされた値が自動的に与えられ、開発者は、このフィールドを明示的に設定する必要がありません。キーであるがゆえに、<code>id</code> フィールドは、たとえばレコードを削除または表示する際に、レコードを一意に識別するのに使われることでしょう。</p> + + <p>{{domxref("IDBObjectStore.createIndex()")}} メソッドを用いて、別の二つのインデックス (フィールド) も作成します。すなわち、<code>title</code> (それぞれのメモ書きの題名を含むことになります) と、<code>body</code> (そのメモ書きの本文を含むことになります) を作成します。</p> + </li> +</ol> + +<p>以上のようにこの簡素なデータベース・スキーマを設定したので、データベースにレコードを追加し始めれば、それぞれのレコードは、以下の行のようなオブジェクトとして表現されることでしょう。</p> + +<pre class="brush: js notranslate">{ + title: "Buy milk", + body: "Need both cows milk and soy.", + id: 8 +}</pre> + +<h3 id="Adding_data_to_the_database" name="Adding_data_to_the_database">データをデータベースに追加します</h3> + +<p>それでは、どのようにしたらデータベースにレコードを追加できるか、その方法を見てみましょう。これは、ページ上のフォームを使って行われます。</p> + +<p>前のイベント・ハンドラーの下に (ただし、やはり <code>window.onload</code> ハンドラーの内部に)、 以下の行を追加してください。以下の行では、フォームが送信された際に (送信 {{htmlelement("button")}} が押され、成功したフォーム送信、という結果に至ったときに)、<code>addData()</code> と呼ばれる関数を実行する、<code>onsubmit</code> ハンドラーを設定しています。</p> + +<pre class="brush: js notranslate">// フォームが送信されたときに addData() 関数が実行されるように、onsubmit ハンドラーを作成します。 +form.onsubmit = addData;</pre> + +<p>では、<code>addData()</code> 関数を定義しましょう。上記の行の下に、以下のものを追加してください。</p> + +<pre class="brush: js notranslate">// addData() 関数を定義します。 +function addData(e) { + // デフォルト動作を防止します。従来通りの方法でフォームを送信したくはないからです。 + e.preventDefault(); + + // フォーム・フィールドに入力された値を求めます。そして、それらの値を、データベースへ挿入すべく準備してあるオブジェクトに保存します。 + let newItem = { title: titleInput.value, body: bodyInput.value }; + + // 読み書きのデータベース・トランザクションを開いて、データの追加に備えます。 + let transaction = db.transaction(['notes'], 'readwrite'); + + // データベースに追加済みのオブジェクト・ストアを呼び出します。 + let objectStore = transaction.objectStore('notes'); + + // newItem というオブジェクトをオブジェクト・ストアに追加するための要求を作ります。 + let request = objectStore.add(newItem); + request.onsuccess = function() { + // フォームをクリアして、次のエントリーの追加に備えます。 + titleInput.value = ''; + bodyInput.value = ''; + }; + + // すべてが済んだら、完了するトランザクションの成功を報告します。 + transaction.oncomplete = function() { + console.log('Transaction completed: database modification finished.'); + + // displayData() を再度実行することによって、データの表示を更新して、新たに追加した項目を表示します。 + displayData(); + }; + + transaction.onerror = function() { + console.log('Transaction not opened due to error'); + }; +}</pre> + +<p>これは割と複雑ですね。噛み砕くと、以下の通りです。</p> + +<ul> + <li>フォームが実際に従来通りの方法で送信してしまうこと (これはページ・リフレッシュを引き起こし、体験をそこなうでしょう) を防ぐために、イベント・オブジェクトに対して {{domxref("Event.preventDefault()")}} を実行します。</li> + <li>データベースに入力すべきレコードを表すオブジェクトを作成します。その際、そのオブジェクトには、フォーム入力からの値を埋め込みます。<code>id</code> の値を明示的に含める必要がないことに注意してください。以前説明したとおり、これは自動的に埋め込まれます。</li> + <li>{{domxref("IDBDatabase.transaction()")}} メソッドを用いて、<code>notes</code> というオブジェクト・ストアに対する<code>readwrite</code> (読み書き) トランザクションを開きます。このトランザクション・オブジェクトのおかげでオブジェクト・ストアにアクセスできるようになり、オブジェクト・ストアに対して何か——たとえば新規レコードの追加など——を行えるようになります。</li> + <li>{{domxref("IDBTransaction.objectStore()")}}メソッドを用いてオブジェクト・ストアにアクセスし、その結果を <code>objectStore</code> という変数に保存します。</li> + <li> {{domxref("IDBObjectStore.add()")}} を用いて、データベースに新規レコードを追加します。これは、以前見たのと同様の方法で、要求オブジェクトを作り出します。</li> + <li>ライフサイクル内での重大な時点 (クリティカル・ポイント) においてコードを実行するために、<code>request</code> (要求) と <code>transaction</code> (トランザクション) に対する一群のイベント・ハンドラーを追加します。要求が成功したら、次のメモ書きの入力に備えてフォーム入力をクリアします。トランザクションが完了したら、ページ上のメモ書きの表示を更新するために、<code>displayData()</code> 関数を再び実行します。</li> +</ul> + +<h3 id="Displaying_the_data" name="Displaying_the_data">データを表示します</h3> + +<p>すでにコード内で <code>displayData()</code> を二度も参照したからには、多分これを定義すべきでしょうね。以下のものをコードに (今までの関数定義の下に) 追加してください。</p> + +<pre class="brush: js notranslate">// displayData() 関数を定義します。 +function displayData() { + // ここでは、表示を更新するたびにリスト要素の中身を空にします。 + // もしこのようにしなかったら、新たなメモ書きを追加するたびに複製を列挙する羽目になるでしょう。 + while (list.firstChild) { + list.removeChild(list.firstChild); + } + + // オブジェクト・ストアを開き、それから、カーソルを取得します。 + // カーソルは、ストア内の異なるデータ項目のすべてにわたって反復処理を行うものです。 + let objectStore = db.transaction('notes').objectStore('notes'); + objectStore.openCursor().onsuccess = function(e) { + // カーソルへの参照を求めます。 + let 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 がどの項目に対応しているのかがわかります。これは、後で項目を削除したくなったときに有用です。 + listItem.setAttribute('data-note-id', cursor.value.id); + + // ボタンを作成し、それを各 listItem の内部に設置します。 + const deleteBtn = document.createElement('button'); + listItem.appendChild(deleteBtn); + deleteBtn.textContent = 'Delete'; + + // ボタンがクリックされたら deleteItem() 関数が実行されるように、 + // イベント・ハンドラーを設定します。 + deleteBtn.onclick = deleteItem; + + // カーソルにおける次の項目へと反復処理を進めます。 + cursor.continue(); + } else { + // またもや、リスト項目が空であれば、'No notes stored' (メモ書きは何も保存されていません) というメッセージを表示します。 + if(!list.firstChild) { + const listItem = document.createElement('li'); + listItem.textContent = 'No notes stored.'; + list.appendChild(listItem); + } + // 反復処理をすべきカーソル項目がこれ以上ない場合、そのように示します。 + console.log('Notes all displayed'); + } + }; +}</pre> + +<p>再びになりますが、これを噛み砕いてみましょう。</p> + +<ul> + <li>まず、更新した中身を埋め込む前に、{{htmlelement("ul")}} 要素の中身を空っぽにします。これを行わないと、遂には、更新のたびに追加された複製された中身からなる巨大なリストができあがってしまいます。</li> + <li>次に、{{domxref("IDBDatabase.transaction()")}} と {{domxref("IDBTransaction.objectStore()")}} を用いて、<code>addData()</code> で行ったのと同様にして (ただしここではこれらを繋いで 1 行にまとめている点が異なりますが)、<code>notes</code> というオブジェクト・ストアへの参照を求めます。</li> + <li>次の段階は、{{domxref("IDBObjectStore.openCursor()")}} メソッドを使って、カーソルに対する要求を開くことです。カーソルとは、オブジェクト・ストア内の全レコードにわたって反復処理を行うのに使える構造体です。コードをより簡潔にするために、この行の最後に <code>onsuccess</code> ハンドラーを繋げています。カーソルが成功裡に返されると、このハンドラーが実行されます。</li> + <li><code>let cursor = e.target.result</code> を用いて、カーソル自体 ({{domxref("IDBCursor")}} オブジェクト) に対する参照を求めています。</li> + <li>次に、カーソルがデータストアのレコードを含むか否かを調べます (<code>if(cursor){ ... }</code>)。もし含むなら、DOM 断片を作成し、その断片にレコードのデータを埋め込み、ページ内に (<code><ul></code> 要素の内部に) その断片を挿入します。また、クリックされたら <code>deleteItem()</code> 関数を実行することによって当該メモ書きを削除するような削除ボタンも含めておきます。この関数は、次の節で見ることにします。</li> + <li><code>if</code> ブロックの最後では、{{domxref("IDBCursor.continue()")}} メソッドを用いてカーソルをデータストア内の次のレコードへと進め、<code>if</code> ブロックの中身を再び実行します。反復処理をすべき別のレコードがある場合には、こうすることにより、その別のレコードがページに挿入されることになり、その後また <code>continue()</code> が実行され、以下同様に続きます。</li> + <li>反復処理をすべき対象のレコードがもうない場合、<code>cursor</code> は <code>undefined</code> を返すことになります。よって、<code>if</code> ブロックの代わりに <code>else</code> ブロックが実行されることになります。このブロックでは、<code><ul></code> に何らかのメモ書きが挿入されたかどうかを調べます。もし何も挿入されていなければ、何もメモ書きが保存されていなかった旨を述べるメッセージを挿入します。</li> +</ul> + +<h3 id="Deleting_a_note" name="Deleting_a_note">メモ書きを削除します</h3> + +<p>上述のとおり、メモ書きの削除ボタンが押されると、そのメモ書きは削除されます。これは、<code>deleteItem()</code> 関数により達成されます。この関数は以下のようなものです。</p> + +<pre class="brush: js notranslate">// deleteItem() 関数を定義します。 +function deleteItem(e) { + // 削除したいタスクの名前 (訳注: ID の間違い?) を取り出します。 + // それを IDB で使おうとする前に、数値に変換する必要があります。 + // IDB のキーの値には、型による区別があるのです。 + let noteId = Number(e.target.parentNode.getAttribute('data-note-id')); + + // データベース・トランザクションを開き、当該タスクを削除します。その際、上記で取得した ID を用いて、当該タスクを見つけます。 + let transaction = db.transaction(['notes'], 'readwrite'); + let objectStore = transaction.objectStore('notes'); + let request = objectStore.delete(noteId); + + // データ項目を削除したことを報告します。 + transaction.oncomplete = function() { + // ボタンの親——リスト項目——を削除します。 + // すると、それはもはや表示されなくなります。 + e.target.parentNode.parentNode.removeChild(e.target.parentNode); + console.log('Note ' + noteId + ' deleted.'); + + // 再びになりますが、リスト項目が空の場合は、'No notes stored' (メモ書きは何も保存されていません) というメッセージを表示します。 + if(!list.firstChild) { + let listItem = document.createElement('li'); + listItem.textContent = 'No notes stored.'; + list.appendChild(listItem); + } + }; +}</pre> + +<ul> + <li>これの最初の部分は説明を要します。 <code>Number(e.target.parentNode.getAttribute('data-note-id'))</code> を用いて、削除すべきレコードの ID を取り出します。レコードの ID は、最初にその <code><li></code> が表示された際にその <code><li></code> の <code>data-note-id</code> という属性に保存されている、ということを思い出してください。しかし、その属性は、 <a href="/ja/docs/Web/JavaScript/Reference/Global_Objects/Number">Number()</a> というグローバル・ビルトイン・オブジェクトを通じて渡す必要があります。なぜなら、属性は今のところ文字列であり、こうしなければデータベースに認識されないからです。</li> + <li>それから、以前に見たのと同じパターンを使って、オブジェクト・ストアへの参照を求めます。そして、{{domxref("IDBObjectStore.delete()")}} メソッドを用いて、データベースから当該レコードを削除します。その際、データベースには ID を渡します。</li> + <li>データベース・トランザクションが完了したら、当該メモ書きの <code><li></code> を DOM から削除します。そして再び、<code><ul></code> が現時点で空かどうかを調べ、適宜注記を挿入します。</li> +</ul> + +<p>さあ、これで全部終わりです! あなたの例は今やちゃんと動くはずですよ。</p> + +<p>もし問題があれば、気軽に <a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/notes/">ライブ例と突き合わせてみてください</a> (<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/notes/index.js">ソースコード</a> も参照してください)。</p> + +<h3 id="Storing_complex_data_via_IndexedDB" name="Storing_complex_data_via_IndexedDB">IndexedDB を通じて複雑なデータを保存します</h3> + +<p>上述のとおり、IndexedDB は、単純なテキスト文字列以上のものを保存するのに使えます。望むものはほとんど何でも——動画や静止画像のブロブ (blob) のような、複雑なオブジェクトまで含めて——保存できるのです。しかも、他のどの型のデータと比べても、達成するのがずっと困難だという訳でもないのです。</p> + +<p>やり方を実演するために、<a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/client-side-storage/indexeddb/video-store">IndexedDB 動画ストア</a> と呼ばれる別の例を書きました (<a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/indexeddb/video-store/">ここでライブで動いているところも</a> 参照してください)。この例を最初に実行すると、すべての動画をネットワークからダウンロードして IndexedDB データベースに保存し、それから、{{htmlelement("video")}} 要素内部の UI の中に動画を表示します。二度目に実行すると、動画を表示する前に、データベース内の動画を見つけ出し、(ネットワークからダウンロードする) 代わりにそこから動画を取ってきます。こうすることにより、後続のロードは高速化され、帯域幅をあまり食わなくなります。</p> + +<p>この例のもっとも興味深い部分を見て回りましょう。すべては見ないことにします。というのも、多くの部分は前の例に類似しており、コードにはちゃんとコメントがつけてありますから。</p> + +<ol> + <li> + <p>この単純な例のために、取得すべき動画の名前をオブジェクトの配列の形で保存しておきました。</p> + + <pre class="brush: js notranslate">const videos = [ + { 'name' : 'crystal' }, + { 'name' : 'elf' }, + { 'name' : 'frog' }, + { 'name' : 'monster' }, + { 'name' : 'pig' }, + { 'name' : 'rabbit' } +];</pre> + </li> + <li> + <p>まずはじめに、データベースを成功裡に開くことができたら、<code>init()</code> 関数を実行します。これは、異なる動画の名前をループしてゆきますが、その際、それぞれの名前で識別されるレコードを <code>videos</code> というデータベースからロードしようと試みます。</p> + + <p>各々の動画がデータベース内で見つかったら (これは、<code>request.result</code> が <code>true</code> と評価されるかどうかを調べることにより、容易に確認できます。もしレコードが存在しなければ、<code>undefined</code> となります)、その動画ファイル (ブロブとして保存されています) および動画の名前が、UI に配置するために、すぐに <code>displayVideo()</code> 関数へと渡されます。もし動画がデータベース内で見つからなければ、動画の名前が <code>fetchVideoFromNetwork()</code> 関数に渡されます。それが何のためか、見当がついていることでしょうが……そう、その動画をネットワークから取ってくるためです。</p> + + <pre class="brush: js notranslate">function init() { + // 動画の名前を一つずつループしてゆきます。 + for(let i = 0; i < videos.length; i++) { + // トランザクションを開き、オブジェクト・ストアを取得し、名前によって各動画を get() します。 + let objectStore = db.transaction('videos').objectStore('videos'); + let request = objectStore.get(videos[i].name); + request.onsuccess = function() { + // もし結果がデータベース内に存在したら (存在しなければ undefined)、 + if(request.result) { + // displayVideo() を用いて、動画を IDB から取り出して表示します。 + console.log('taking videos from IDB'); + displayVideo(request.result.mp4, request.result.webm, request.result.name); + } else { + // 動画をネットワークから取ってきます。 + fetchVideoFromNetwork(videos[i]); + } + }; + } +}</pre> + </li> + <li> + <p>以下のスニペットは、<code>fetchVideoFromNetwork()</code> の内部から取ったものです。ここでは、二つの別々の {{domxref("fetch()", "WindowOrWorkerGlobalScope.fetch()")}} 要求を用いて、MP4 版の動画と WebM 版の動画を取ってきます。それから、{{domxref("blob()", "Body.blob()")}} メソッドを用いて、それぞれの応答の本体をブロブとして抽出します。このブロブは、保存して後で表示することの可能な、動画のオブジェクト表現を与えてくれます。</p> + + <p>しかし、ここで問題があります。これらの二つの要求はどちらも非同期的なのですが、双方のプロミスが成立 (fulfill) した場合にだけ動画を表示もしくは保存しようと試みたいのです。幸い、そうした問題を扱うビルトイン・メソッドがあります。すなわち {{jsxref("Promise.all()")}} です。これは一つの引数——成立したかどうかを調べたい個々のプロミスすべてに対する参照を配列に入れたもの——をとり、これ自体がプロミスに基づいています。</p> + + <p>それらのプロミスすべてが成立したら、成立した個々の値すべてを含む配列をともなって、<code>all()</code> プロミスも成立します。<code>all()</code> のブロック内部では、以前 UI に動画を表示するために行ったのと同様にして <code>displayVideo()</code> 関数を呼び出していること、そして、それらの動画をデータベース内に保存するために <code>storeVideo()</code> 関数も呼び出していることが、お分かりでしょう。</p> + + <pre class="brush: js notranslate">let mp4Blob = fetch('videos/' + video.name + '.mp4').then(response => + response.blob() +); +let webmBlob = fetch('videos/' + video.name + '.webm').then(response => + response.blob() +); + +// 双方のプロミスが成立したときのみ、次のコードを実行します。 +Promise.all([mp4Blob, webmBlob]).then(function(values) { + // ネットワークから取ってきた動画を、displayVideo() により表示します。 + displayVideo(values[0], values[1], video.name); + // storeVideo() を用いて、その動画を IDB に保存します。 + storeVideo(values[0], values[1], video.name); +});</pre> + </li> + <li> + <p>まず <code>storeVideo()</code> を見ましょう。これは、データベースにデータを追加するための上記の例で見たパターンに、とてもよく似ています。つまり、<code>readwrite</code> (読み書き) トランザクションを開き、<code>videos</code> に対するオブジェクト・ストア参照を求め、データベースに追加すべきレコードを表すオブジェクトを作成し、それから、{{domxref("IDBObjectStore.add()")}} を用いてそのオブジェクトを単純に追加しています。</p> + + <pre class="brush: js notranslate">function storeVideo(mp4Blob, webmBlob, name) { + // トランザクションを開き、オブジェクト・ストアを求めます。IDB に書き込めるようにするために、これは読み書きトランザクションにしておきます。 + let objectStore = db.transaction(['videos'], 'readwrite').objectStore('videos'); + // IDB に追加するレコードを作成します。 + let record = { + mp4 : mp4Blob, + webm : webmBlob, + name : name + } + + // add() を使ってレコードを IDB に追加します。 + let request = objectStore.add(record); + + ... + +};</pre> + </li> + <li> + <p>最後に、<code>displayVideo()</code> があります。これは、UI に動画を挿入するのに必要な DOM 要素を作成してから、それらの DOM 要素をページに追加します。これの一番面白い部分は、以下に示した箇所です。<code><video></code> 要素内に動画ブロブを実際に表示するには、{{domxref("URL.createObjectURL()")}} メソッドを使って、オブジェクト URL (メモリに記憶されている動画ブロブを指し示す内部 URL) を作成せねばならないのです。それが済んだら、オブジェクト URL を {{htmlelement("source")}} 要素の <code>src</code> 属性の値として設定できて、物事がうまく機能します。</p> + + <pre class="brush: js notranslate">function displayVideo(mp4Blob, webmBlob, title) { + // ブロブからオブジェクト URL を作成します。 + let mp4URL = URL.createObjectURL(mp4Blob); + let webmURL = URL.createObjectURL(webmBlob); + + ... + + 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'; + + ... +}</pre> + </li> +</ol> + +<h2 id="Offline_asset_storage" name="Offline_asset_storage">オフラインでの資産の保存</h2> + +<p>上記の例は、IndexedDB データベース内に大規模な資産を保存するアプリの作り方を既に示しており、こうすることで、それらの大規模な資産を二度以上ダウンロードする必要性をなくしています。これは既にユーザー体験にとっての多大なる進歩ではありますが、まだ一つ欠けていることがあります。すなわち、依然として、主たる HTML と CSS と JavaScript のファイルを、サイトにアクセスするたびにダウンロードせねばならないのです。これが意味することは、ネットワーク接続がない場合にはサイトが動作しないということです。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/15759/ff-offline.png" style="border-style: solid; border-width: 1px; display: block; height: 307px; margin: 0px auto; width: 765px;"></p> + +<p>ここは、 <a href="/ja/docs/Web/API/Service_Worker_API">サービスワーカー</a> およびそれと緊密に関連した <a href="/ja/docs/Web/API/Cache">キャッシュ API</a> の出番です。</p> + +<p>サービスワーカーとは、ただ単に置いてあって、特定のオリジン (ウェブサイト、または、あるドメインにあるウェブサイトの一部) に対して、そこにブラウザでアクセスした際に登録される、JavaScript ファイルのことです。登録されれば、サービスワーカーは、当該オリジンで利用可能なページを制御できます。サービスワーカーは、ロードされたページとネットワークとの間に位置して、当該オリジン宛のネットワーク要求を横取りすることにより、こうした制御を行います。</p> + +<p>サービスワーカーが要求を横取りすると、その要求に対して望むことは何でも行えますが (<a href="/ja/docs/Web/API/Service_Worker_API#Other_use_case_ideas">使用例の案</a> を参照)、典型例では、ネットワーク応答をオフラインに保存しており、その後、要求に応じて、ネットワークからの応答の代わりに、保存してあるそれらの応答を提供しています。これによって事実上、ウェブサイトを完全にオフラインで機能させることが可能になります。</p> + +<p>キャッシュ API は、クライアント側での保存のもう一つの仕組みですが、これにはちょっとした相違点があります。キャッシュ API は HTTP 応答を保存するように設計されているのです。そのため、サービスワーカーと一緒に使うと、とてもうまく機能します。</p> + +<div class="note"> +<p><strong>注</strong>: サービスワーカーとキャッシュは、現在、ほとんどのモダン・ブラウザーでサポートされています。執筆時点では、Safari はまだ実装するのに忙しかったのですが、もうすぐサポートされるはずです。</p> +</div> + +<h3 id="A_service_worker_example" name="A_service_worker_example">サービスワーカーの例</h3> + +<p>これがどのような感じなのかについて少しばかりお教えするために、例を見ましょう。前節で見た動画ストアの例の、別のバージョンを作っておきました。このバージョンは、サービスワーカーを介してキャッシュ API で HTML と CSS と JavaScript も保存する点を除いて、同等に機能しますが、この点のおかげで、この例がオフラインで実行できるようになるのです!</p> + +<p><a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/">サービスワーカーを用いた IndexedDB 動画ストアがライブで実行中のところ</a> をご覧ください。また、<a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/client-side-storage/cache-sw/video-store-offline">ソースコードも参照してください</a>。</p> + +<h4 id="Registering_the_service_worker" name="Registering_the_service_worker">サービスワーカーを登録します</h4> + +<p>注意すべき第一の点は、主たる JavaScript ファイル中に追加のコードが少々ある点です (<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/cache-sw/video-store-offline/index.js">index.js</a> を参照)。まず、{{domxref("Navigator")}} オブジェクトにおいて <code>serviceWorker</code> メンバーが利用可能かどうかを調べる機能検出検査を行います。もしこれが true を返したら、サービスワーカーの少なくとも基本部分がサポートされていることが分かります。ここの内部では、{{domxref("ServiceWorkerContainer.register()")}} メソッドを用いて、<code>sw.js</code> ファイルに含まれるサービスワーカーを、このファイルのあるオリジンに対して登録します。すると、同一ディレクトリまたは下位ディレクトリにあるページを制御できるようになります。このメソッドのプロミスが成立すると、サービスワーカーは登録されたものと見なされます。</p> + +<pre class="brush: js notranslate"> // サイトがオフラインで動くようにする処理を制御するために、サービスワーカーを登録します。 + + if('serviceWorker' in navigator) { + navigator.serviceWorker + .register('/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js') + .then(function() { console.log('Service Worker Registered'); }); + }</pre> + +<div class="note"> +<p><strong>注</strong>: <code>sw.js</code> ファイルに至るまでの、与えられたパスは、サイト・オリジンに対して相対的なのであり、上記コードを含む JavaScript ファイルに対して相対的なのではありません。サービスワーカーは <code>https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code> にあります。オリジンは <code>https://mdn.github.io</code> です。よって、与えられるパスは、<code>/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js</code> でなくてはなりません。もしこの例を御自分のサーバーにホストしたいとお思いでしたら、それに合わせて、ここを変更せねばなりません。これはやや混乱を招くところですが、セキュリティ上の理由から、この方法で動作する必要があるのです。</p> +</div> + +<h4 id="Installing_the_service_worker" name="Installing_the_service_worker">サービスワーカーをインストールします</h4> + +<p>サービスワーカーの制御下にあるいずれかのページが次にアクセスされた際には (たとえば、この例がリロードされたときには)、サービスワーカーがそのページに対してインストールされます。それが意味することは、サービスワーカーがそのページを制御し始めるだろう、ということです。これが起きると、サービスワーカーに対して <code>install</code> イベントを発火させます。サービスワーカー自体の内部には、当該インストールに応じるコードを書くことができます。</p> + +<p><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/cache-sw/video-store-offline/sw.js">sw.js</a> ファイル (サービスワーカー) 内の例を見てみましょう。<code>self</code> に対して <code>install</code> リスナーが登録されるのがお分かりでしょう。この <code>self</code> というキーワードは、サービスワーカー・ファイルの内部からサービスワーカーのグローバル・スコープを参照する手段です。</p> + +<p><code>install</code> ハンドラーの内部では、{{domxref("ExtendableEvent.waitUntil()")}}メソッド——イベント・オブジェクト上で使えます——を用いて、当メソッドの内部のプロミスが成功して成立するまではブラウザーはサービスワーカーのインストールを完了させるべきではない、と知らせます。</p> + +<p>ここは、キャッシュ API が動作しているのが見られる箇所です。応答を保存できる新規キャッシュ・オブジェクト (IndexedDB オブジェクト・ストアと似ています) を開くために、{{domxref("CacheStorage.open()")}} メソッドを使います。このプロミスは、<code>video-store</code> というキャッシュを表現する {{domxref("Cache")}} オブジェクトをともなって成立します。その後、一連の資源を取ってきて、その応答をキャッシュに追加するために、{{domxref("Cache.addAll()")}} メソッドを使います。</p> + +<pre class="brush: js notranslate">self.addEventListener('install', function(e) { + e.waitUntil( + caches.open('video-store').then(function(cache) { + return 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' + ]); + }) + ); +});</pre> + +<p>さて、これで終わりです。インストールが済みました。</p> + +<h4 id="Responding_to_further_requests" name="Responding_to_further_requests">さらなる要求に応答します</h4> + +<p>HTML ページに対してサービスワーカーが登録されてインストールされ、関連する資産がすべてキャッシュに追加されれば、ほぼ開始準備が整っています。すべきことは、あと一つだけです。つまり、さらなるネットワーク要求に応答するための何らかのコードを書くことです。</p> + +<p><code>sw.js</code> における第二のちょっとしたコードがしていることは、こうです。すなわち、サービスワーカーのグローバル・スコープにもう一つのリスナーを追加し、これにより、<code>fetch</code> イベントが生じたときにハンドラー関数を実行します。このイベントは、サービスワーカーの登録先のディレクトリ内の資産に対してブラウザーが要求を出す際には、いつでも生じます。</p> + +<p>ハンドラーの内部では、要求された資産の URL をまず記録します。それから、{{domxref("FetchEvent.respondWith()")}} メソッドを使って、その要求に対するカスタム応答を提供します。</p> + +<p>このブロックの内部では、{{domxref("CacheStorage.match()")}} を用いて、マッチング要求 (URL にマッチします) がいずれかのキャッシュの中に見つかるかどうかを調べます。このプロミスは、マッチが見つからなければ (訳注: 正しくは「見つかれば」?) そのマッチする応答をともなって成立し、マッチが見つからなければ <code>undefined</code> となります。</p> + +<p>もしマッチが見つかれば、単純にそれをカスタム応答として返します。もしマッチが見つからなければ、代わりに、ネットワークから応答を <a href="/ja/docs/Web/API/WindowOrWorkerGlobalScope/fetch">fetch()</a> して、それを返します。</p> + +<pre class="brush: js notranslate">self.addEventListener('fetch', function(e) { + console.log(e.request.url); + e.respondWith( + caches.match(e.request).then(function(response) { + return response || fetch(e.request); + }) + ); +});</pre> + +<p>これで私たちの単純なサービスワーカーは終わりです。サービスワーカーを使ってできる、もっと多くのことがありますが、より詳しくは、<a href="https://serviceworke.rs/">service worker cookbook</a> を参照してください。また、<a href="https://developers.google.com/web/fundamentals/codelabs/offline/">ウェブアプリへの Service Worker とオフラインの追加</a> という記事について、著者の Paul Kinlan さんに感謝します。あの記事のおかげで、この単純な例の着想を得られました。</p> + +<h4 id="Testing_the_example_offline" name="Testing_the_example_offline">この例をオフラインで試します</h4> + +<p><a href="https://mdn.github.io/learning-area/javascript/apis/client-side-storage/cache-sw/video-store-offline/">サービスワーカー の例</a> を試すには、それが確実にインストールされるように、二、三度ロードする必要があるでしょう。それが済んだら、以下のことができます。</p> + +<ul> + <li>ネットワーク接続ケーブルを抜いてみましょう。あるいは、Wi-Fi を切ってみましょう。</li> + <li>Firefox を使っているなら、[ファイル] > [オフライン作業] を選択してください。</li> + <li>Chrome を使っているなら、[デベロッパーツール] へ進み、 [<em>Application] > [Service Workers]</em> を選び、それから、[<em>Offline] </em>のチェックボックスをチェックしてください。</li> +</ul> + +<p>この例のページをもう一度リフレッシュすれば、当該ページが依然として、まさに申し分なくロードされているところを見ることになるはずです。あらゆるものがオフラインに保存されています。すなわち、ページ資産はキャッシュに保存されており、動画は IndexedDB データベースに保存されています。</p> + +<h2 id="Summary" name="Summary">まとめ</h2> + +<p>これで終わりです。クライアント側での保存の技術についてのこの概要を、皆さんが有用だと思ってくださったのであれば良いな、と望んでいます。</p> + +<h2 id="See_also" name="See_also">あわせて参照</h2> + +<ul> + <li><a href="/ja/docs/Web/API/Web_Storage_API">Web storage API</a></li> + <li><a href="/ja/docs/Web/API/IndexedDB_API">IndexedDB API</a></li> + <li><a href="/ja/docs/Web/HTTP/Cookies">Cookies</a></li> + <li><a href="/ja/docs/Web/API/Service_Worker_API">Service worker API</a></li> +</ul> + +<p>{{PreviousMenu("Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</p> + +<h2 id="このモジュール">このモジュール</h2> + +<div> +<ul> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Web API の紹介</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">ドキュメントの操作</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">サーバからのデータ取得</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">サードパーティ API</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">グラフィックの描画</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">動画と音声の API</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">クライアント側ストレージ</a></li> +</ul> +</div> diff --git a/files/ja/learn/javascript/client-side_web_apis/drawing_graphics/index.html b/files/ja/learn/javascript/client-side_web_apis/drawing_graphics/index.html new file mode 100644 index 0000000000..86c19aa6bb --- /dev/null +++ b/files/ja/learn/javascript/client-side_web_apis/drawing_graphics/index.html @@ -0,0 +1,879 @@ +--- +title: グラフィックの描画 +slug: Learn/JavaScript/Client-side_web_APIs/Drawing_graphics +tags: + - API + - Article + - Beginner + - Canvas + - CodingScripting + - Graphics + - JavaScript + - Learn + - WebGL +translation_of: Learn/JavaScript/Client-side_web_APIs/Drawing_graphics +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Third_party_APIs", "Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</div> + +<p class="summary">ブラウザーには Scalable Vector Graphics (<a href="/ja/docs/Web/SVG">SVG</a>) 言語から、HTML {{htmlelement("canvas")}} 要素へ描画するための API (<a href="/ja/docs/Web/API/Canvas_API">The Canvas API</a> と <a href="/ja/docs/Web/API/WebGL_API">WebGL</a> を参照) まで、非常に強力なグラフィックプログラミングツールが含まれています。<br> + この記事では、canvas の概要とさらに詳細を学ぶためのリソースについて説明します。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>JavaScript basics (see <a href="/ja/docs/Learn/JavaScript/First_steps">first steps</a>, <a href="/ja/docs/Learn/JavaScript/Building_blocks">building blocks</a>, <a href="/ja/docs/Learn/JavaScript/Objects">JavaScript objects</a>), the <a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">basics of Client-side APIs</a></td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>JavaScript を使用して <code><canvas></code> 要素に描画するための基本を学ぶ。</td> + </tr> + </tbody> +</table> + +<h2 id="Graphics_on_the_Web" name="Graphics_on_the_Web">Webでのグラフィック</h2> + +<p>As we talked about in our HTML <a href="/ja/docs/Learn/HTML/Multimedia_and_embedding">Multimedia and embedding</a> module, the Web was originally just text, which was very boring, so images were introduced — first via the {{htmlelement("img")}} element and later via CSS properties such as {{cssxref("background-image")}}, and <a href="/ja/docs/Web/SVG">SVG</a>.</p> + +<p>This however was still not enough. While you could use <a href="/ja/docs/Learn/CSS">CSS</a> and <a href="/ja/docs/Learn/JavaScript">JavaScript</a> to animate (and otherwise manipulate) SVG vector images — as they are represented by markup — there was still no way to do the same for bitmap images, and the tools available were rather limited. The Web still had no way to effectively create animations, games, 3D scenes, and other requirements commonly handled by lower level languages such as C++ or Java.</p> + +<p>The situation started to improve when browsers began to support the {{htmlelement("canvas")}} element and associated <a href="/ja/docs/Web/API/Canvas_API">Canvas API</a> — Apple invented it in around 2004, and other browsers followed by implementing it in the years that followed. As you'll see below, canvas provides many useful tools for creating 2D animations, games, data visualizations, and other types of app, especially when combined with some of the other APIs the web platform provides.</p> + +<p>The below example shows a simple 2D canvas-based bouncing balls animation that we originally met in our <a href="/ja/docs/Learn/JavaScript/Objects/Object_building_practice">Introducing JavaScript objects</a> module:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/oojs/bouncing-balls/index-finished.html", '100%', 500)}}</p> + +<p>Around 2006–2007, Mozilla started work on an experimental 3D canvas implementation. This became <a href="/ja/docs/Web/API/WebGL_API">WebGL</a>, which gained traction among browser vendors, and was standardized around 2009–2010. WebGL allows you to create real 3D graphics inside your web browser; the below example shows a simple rotating WebGL cube:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/threejs-cube/index.html", '100%', 500)}}</p> + +<p>This article will focus mainly on 2D canvas, as raw WebGL code is very complex. We will however show how to use a WebGL library to create a 3D scene more easily, and you can find a tutorial covering raw WebGL elsewhere — see <a href="/ja/docs/Web/API/WebGL_API/Tutorial/Getting_started_with_WebGL">Getting started with WebGL</a>.</p> + +<div class="note"> +<p><strong>注</strong>: Basic canvas functionality is supported well across browsers, with the exception of IE 8 and below for 2D canvas, and IE 11 and below for WebGL.</p> +</div> + +<h2 id="Active_learning_Getting_started_with_a_<canvas>" name="Active_learning_Getting_started_with_a_<canvas>">アクティブラーニング: <canvas>を始めよう</h2> + +<p>If you want to create a 2D <em>or</em> 3D scene on a web page, you need to start with an HTML {{htmlelement("canvas")}} element. This element is used to define the area on the page into which the image will be drawn. This is as simple as including the element on the page:</p> + +<pre class="brush: html notranslate"><canvas width="320" height="240"></canvas></pre> + +<p>This will create a canvas on the page with a size of 320 by 240 pixels.</p> + +<p>Inside the canvas tags, you can put some fallback content, which is shown if the user's browser doesn't support canvas.</p> + +<pre class="brush: html notranslate"><canvas width="320" height="240"> + <p>Your browser doesn't support canvas. Boo hoo!</p> +</canvas></pre> + +<p>Of course, the above message is really unhelpful! In a real example you'd want to relate the fallback content to the canvas content. 例えば、if you were rendering a constantly updating graph of stock prices, the fallback content could be a static image of the latest stock graph, with alt text saying what the prices are in text.</p> + +<h3 id="Creating_and_sizing_our_canvas" name="Creating_and_sizing_our_canvas">canvasの作成とサイズ変更</h3> + +<p>Let's start by creating our own canvas that we draw future experiments on to.</p> + +<ol> + <li> + <p>First make a local copy of our <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/0_canvas_start.html">0_canvas_start.html</a> file, and open it in your text editor.</p> + </li> + <li> + <p>Add the following code into it, just below the opening {{htmlelement("body")}} tag:</p> + + <pre class="brush: html notranslate"><canvas class="myCanvas"> + <p>Add suitable fallback here.</p> +</canvas></pre> + + <p>We have added a <code>class</code> to the <code><canvas></code> element so it will be easier to select if we have multiple canvases on the page, but we have removed the <code>width</code> and <code>height</code> attributes for now (you could add them back in if you wanted, but we will set them using JavaScript in a below section). Canvases with no explicit width and height default to 300 pixels wide by 150 pixels high.</p> + </li> + <li> + <p>Now add the following lines of JavaScript inside the {{htmlelement("script")}} element:</p> + + <pre class="brush: js notranslate">const canvas = document.querySelector('.myCanvas'); +const width = canvas.width = window.innerWidth; +const height = canvas.height = window.innerHeight;</pre> + + <p>Here we have stored a reference to the canvas in the <code>canvas</code> constant. In the second line we set both a new constant <code>width</code> and the canvas' <code>width</code> property equal to {{domxref("Window.innerWidth")}} (which gives us the viewport width). In the third line we set both a new constant <code>height</code> and the canvas' <code>height</code> property equal to {{domxref("Window.innerHeight")}} (which gives us the viewport height). So now we have a canvas that fills the entire width and height of the browser window!</p> + + <p>You'll also see that we are chaining assignments together with multiple equals signs — this is allowed in JavaScript, and it is a good technique if you want to make multiple variables all equal to the same value. We wanted to make the canvas width and height easily accessible in the width/height variables, as they are useful values to have available for later (例えば、if you want to draw something exactly halfway across the width of the canvas).</p> + </li> + <li> + <p>If you save and load your example in a browser now, you'll see nothing, which is fine, but you'll also see scrollbars — this is a problem for us, which happens because the {{htmlelement("body")}} element has a {{cssxref("margin")}} that, added to our full-window-size canvas, results in a document that's wider than the window. To get rid of the scrollbars, we need to remove the {{cssxref("margin")}} and also set {{cssxref("overflow")}} to <code>hidden</code>. Add the following into the {{htmlelement("head")}} of your document:</p> + + <pre class="brush: html notranslate"><style> + body { + margin: 0; + overflow: hidden; + } +</style></pre> + + <p>The scrollbars should now be gone.</p> + </li> +</ol> + +<div class="note"> +<p><strong>注</strong>: You should generally set the size of the image using HTML attributes or DOM properties, as explained above. You could use CSS, but the trouble then is that the sizing is done after the canvas has rendered, and just like any other image (the rendered canvas is just an image), the image could become pixellated/distorted.</p> +</div> + +<h3 id="Getting_the_canvas_context_and_final_setup" name="Getting_the_canvas_context_and_final_setup">canvasコンテキストと最終セットアップを取得する</h3> + +<p>We need to do one final thing before we can consider our canvas template finished. To draw onto the canvas we need to get a special reference to the drawing area called a context. This is done using the {{domxref("HTMLCanvasElement.getContext()")}} method, which for basic usage takes a single string as a parameter representing the type of context you want to retrieve.</p> + +<p>In this case we want a 2d canvas, so add the following JavaScript line below the others inside the <code><script></code> element:</p> + +<pre class="brush: js notranslate">const ctx = canvas.getContext('2d');</pre> + +<div class="note"> +<p><strong>注</strong>: other context values you could choose include <code>webgl</code> for WebGL, <code>webgl2</code> for WebGL 2, etc., but we won't need those in this article.</p> +</div> + +<p>So that's it — our canvas is now primed and ready for drawing on! The <code>ctx</code> variable now contains a {{domxref("CanvasRenderingContext2D")}} object, and all drawing operations on the canvas will involve manipulating this object.</p> + +<p>Let's do one last thing before we move on. We'll color the canvas background black to give you a first taste of the canvas API. Add the following lines at the bottom of your JavaScript:</p> + +<pre class="brush: js notranslate">ctx.fillStyle = 'rgb(0, 0, 0)'; +ctx.fillRect(0, 0, width, height);</pre> + +<p>Here we are setting a fill color using the canvas' {{domxref("CanvasRenderingContext2D.fillStyle", "fillStyle")}} property (this takes <a href="/ja/docs/Learn/CSS/Introduction_to_CSS/Values_and_units#Colors">color values</a> just like CSS properties do), then drawing a rectangle that covers the entire area of the canvas with the{{domxref("CanvasRenderingContext2D.fillRect", "fillRect")}} method (the first two parameters are the coordinates of the rectangle's top left hand corner; the last two are the width and height you want the rectangle drawn at — we told you those <code>width</code> and <code>height</code> variables would be useful)!</p> + +<p>OK, our template is done and it's time to move on.</p> + +<h2 id="2D_canvas_basics" name="2D_canvas_basics">2D canvas の基本</h2> + +<p>As we said above, all drawing operations are done by manipulating a {{domxref("CanvasRenderingContext2D")}} object (in our case, <code>ctx</code>). Many operations need to be given coordinates to pinpoint exactly where to draw something — the top left of the canvas is point (0, 0), the horizontal (x) axis runs from left to right, and the vertical (y) axis runs from top to bottom.</p> + +<p><img alt="" class="internal" src="https://mdn.mozillademos.org/files/224/Canvas_default_grid.png" style="display: block; height: 220px; margin: 0px auto; width: 220px;"></p> + +<p>Drawing shapes tends to be done using the rectangle shape primitive, or by tracing a line along a certain path and then filling in the shape. Below we'll show how to do both.</p> + +<h3 id="Simple_rectangles" name="Simple_rectangles">簡単な矩形</h3> + +<p>Let's start with some simple rectangles.</p> + +<ol> + <li> + <p>First of all, take a copy of your newly coded canvas template (or make a local copy of <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.html</a> if you didn't follow the above steps).</p> + </li> + <li> + <p>Next, add the following lines to the bottom of your JavaScript:</p> + + <pre class="brush: js notranslate">ctx.fillStyle = 'rgb(255, 0, 0)'; +ctx.fillRect(50, 50, 100, 150);</pre> + + <p>If you save and refresh, you should see a red rectangle has appeared on your canvas. Its top left corner is 50 pixels away from the top and left of the canvas edge (as defined by the first two parameters), and it is 100 pixels wide and 150 pixels tall (as defined by the third and fourth parameters).</p> + </li> + <li> + <p>Let's add another rectangle into the mix — a green one this time. Add the following at the bottom of your JavaScript:</p> + + <pre class="brush: js notranslate">ctx.fillStyle = 'rgb(0, 255, 0)'; +ctx.fillRect(75, 75, 100, 100);</pre> + + <p>Save and refresh, and you'll see your new rectangle. This raises an important point: graphics operations like drawing rectangles, lines, and so forth are performed in the order in which they occur. Think of it like painting a wall, where each coat of paint overlaps and may even hide what's underneath. You can't do anything to change this, so you have to think carefully about the order in which you draw the graphics.</p> + </li> + <li> + <p>Note that you can draw semi-transparent graphics by specifying a semi-transparent color, 例えば、by using <code>rgba()</code>. The <code>a</code> value defines what's called the "alpha channel, " or the amount of transparency the color has. The higher its value, the more it will obscure whatever's behind it. Add the following to your code:</p> + + <pre class="brush: js notranslate">ctx.fillStyle = 'rgba(255, 0, 255, 0.75)'; +ctx.fillRect(25, 100, 175, 50);</pre> + </li> + <li> + <p>Now try drawing some more rectangles of your own; have fun!</p> + </li> +</ol> + +<h3 id="Strokes_and_line_widths" name="Strokes_and_line_widths">ストロークと線の幅</h3> + +<p>So far we've looked at drawing filled rectangles, but you can also draw rectangles that are just outlines (called <strong>strokes</strong> in graphic design). To set the color you want for your stroke, you use the {{domxref("CanvasRenderingContext2D.strokeStyle", "strokeStyle")}} property; drawing a stroke rectangle is done using {{domxref("CanvasRenderingContext2D.strokeRect", "strokeRect")}}.</p> + +<ol> + <li> + <p>Add the following to the previous example, again below the previous JavaScript lines:</p> + + <pre class="brush: js notranslate">ctx.strokeStyle = 'rgb(255, 255, 255)'; +ctx.strokeRect(25, 25, 175, 200);</pre> + </li> + <li> + <p>The default width of strokes is 1 pixel; you can adjust the {{domxref("CanvasRenderingContext2D.lineWidth", "lineWidth")}} property value to change this (it takes a number representing the number of pixels wide the stroke is). Add the following line in between the previous two lines:</p> + + <pre class="brush: js notranslate">ctx.lineWidth = 5;</pre> + </li> +</ol> + +<p>Now you should see that your white outline has become much thicker! That's it for now. At this point your example should look like this:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/getting-started/2_canvas_rectangles.html", '100%', 250)}}</p> + +<div class="note"> +<p><strong>注</strong>: The finished code is available on GitHub as <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/2_canvas_rectangles.html">2_canvas_rectangles.html</a>.</p> +</div> + +<h3 id="Drawing_paths" name="Drawing_paths">パスの描画</h3> + +<p>If you want to draw anything more complex than a rectangle, you need to draw a path. Basically, this involves writing code to specify exactly what path the pen should move along on your canvas to trace the shape you want to draw. Canvas includes functions for drawing straight lines, circles, Bézier curves, and more.</p> + +<p>Let's start the section off by making a fresh copy of our canvas template (<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.html</a>), in which to draw the new example.</p> + +<p>We'll be using some common methods and properties across all of the below sections:</p> + +<ul> + <li>{{domxref("CanvasRenderingContext2D.beginPath", "beginPath()")}} — start drawing a path at the point where the pen currently is on the canvas. On a new canvas, the pen starts out at (0, 0).</li> + <li>{{domxref("CanvasRenderingContext2D.moveTo", "moveTo()")}} — move the pen to a different point on the canvas, without recording or tracing the line; the pen simply "jumps" to the new position.</li> + <li>{{domxref("CanvasRenderingContext2D.fill", "fill()")}} — draw a filled shape by filling in the path you've traced so far.</li> + <li>{{domxref("CanvasRenderingContext2D.stroke", "stroke()")}} — draw an outline shape by drawing a stroke along the path you've drawn so far.</li> + <li>You can also use features like <code>lineWidth</code> and <code>fillStyle</code>/<code>strokeStyle</code> with paths as well as rectangles.</li> +</ul> + +<p>A typical, simple path-drawing operation would look something like so:</p> + +<pre class="brush: js notranslate">ctx.fillStyle = 'rgb(255, 0, 0)'; +ctx.beginPath(); +ctx.moveTo(50, 50); +// draw your path +ctx.fill();</pre> + +<h4 id="Drawing_lines" name="Drawing_lines">線を描く</h4> + +<p>Let's draw an equilateral triangle on the canvas.</p> + +<ol> + <li> + <p>First of all, add the following helper function to the bottom of your code. This converts degree values to radians, which is useful because whenever you need to provide an angle value in JavaScript, it will nearly always be in radians, but humans usually think in degrees.</p> + + <pre class="brush: js notranslate">function degToRad(degrees) { + return degrees * Math.PI / 180; +};</pre> + </li> + <li> + <p>Next, start off your path by adding the following below your previous addition; here we set a color for our triangle, start drawing a path, and then move the pen to (50, 50) without drawing anything. That's where we'll start drawing our triangle.</p> + + <pre class="brush: js notranslate">ctx.fillStyle = 'rgb(255, 0, 0)'; +ctx.beginPath(); +ctx.moveTo(50, 50);</pre> + </li> + <li> + <p>Now add the following lines at the bottom of your script:</p> + + <pre class="brush: js notranslate">ctx.lineTo(150, 50); +let triHeight = 50 * Math.tan(degToRad(60)); +ctx.lineTo(100, 50+triHeight); +ctx.lineTo(50, 50); +ctx.fill();</pre> + + <p>Let's run through this in order:</p> + + <p>First we draw a line across to (150, 50) — our path now goes 100 pixels to the right along the x axis.</p> + + <p>Second, we work out the height of our equalateral triangle, using a bit of simple trigonometry. Basically, we are drawing the triangle pointing downwards. The angles in an equalateral triangle are always 60 degrees; to work out the height we can split it down the middle into two right-angled triangles, which will each have angles of 90 degrees, 60 degrees, and 30 degrees. In terms of the sides:</p> + + <ul> + <li>The longest side is called the <strong>hypotenuse</strong></li> + <li>The side next to the 60 degree angle is called the <strong>adjacent</strong> — which we know is 50 pixels, as it is half of the line we just drew.</li> + <li>The side opposite the 60 degree angle is called the <strong>opposite</strong>, which is the height of the triangle we want to calculate.</li> + </ul> + + <p><img alt="" src="https://mdn.mozillademos.org/files/14829/trigonometry.png" style="display: block; margin: 0 auto;"></p> + + <p>One of the basic trigonometric formulae states that the length of the adjacent multiplied by the tangent of the angle is equal to the opposite, hence we come up with <code>50 * Math.tan(degToRad(60))</code>. We use our <code>degToRad()</code> function to convert 60 degrees to radians, as {{jsxref("Math.tan()")}} expects an input value in radians.</p> + </li> + <li> + <p>With the height calculated, we draw another line to <code>(100, 50 + triHeight)</code>. The X coordinate is simple; it must be halfway between the previous two X values we set. The Y value on the other hand must be 50 plus the triangle height, as we know the top of the triangle is 50 pixels from the top of the canvas.</p> + </li> + <li> + <p>The next line draws a line back to the starting point of the triangle.</p> + </li> + <li> + <p>Last of all, we run <code>ctx.fill()</code> to end the path and fill in the shape.</p> + </li> +</ol> + +<h4 id="Drawing_circles" name="Drawing_circles">円を描く</h4> + +<p>Now let's look at how to draw a circle in canvas. This is accomplished using the {{domxref("CanvasRenderingContext2D.arc", "arc()")}} method, which draws all or part of a circle at a specified point.</p> + +<ol> + <li> + <p>Let's add an arc to our canvas — add the following to the bottom of your code:</p> + + <pre class="brush: js notranslate">ctx.fillStyle = 'rgb(0, 0, 255)'; +ctx.beginPath(); +ctx.arc(150, 106, 50, degToRad(0), degToRad(360), false); +ctx.fill();</pre> + + <p><code>arc()</code> takes six parameters. The first two specify the position of the arc's center (X and Y, respectively). The third is the circle's radius, the fourth and fifth are the start and end angles at which to draw the circle (so specifying 0 and 360 degrees gives us a full circle), and the sixth parameter defines whether the circle should be drawn counterclockwise (anticlockwise) or clockwise (<code>false</code> is clockwise).</p> + + <div class="note"> + <p><strong>注</strong>: 0 degrees is horizontally to the right.</p> + </div> + </li> + <li> + <p>Let's try adding another arc:</p> + + <pre class="brush: js notranslate">ctx.fillStyle = 'yellow'; +ctx.beginPath(); +ctx.arc(200, 106, 50, degToRad(-45), degToRad(45), true); +ctx.lineTo(200, 106); +ctx.fill();</pre> + + <p>The pattern here is very similar, but with two differences:</p> + + <ul> + <li>We have set the last parameter of <code>arc()</code> to <code>true</code>, meaning that the arc is drawn counterclockwise, which means that even though the arc is specified as starting at -45 degrees and ending at 45 degrees, we draw the arc around the 270 degrees not inside this portion. If you were to change <code>true</code> to <code>false</code> and then re-run the code, only the 90 degree slice of the circle would be drawn.</li> + <li>Before calling <code>fill()</code>, we draw a line to the center of the circle. This means that we get the rather nice Pac-Man-style cutout rendered. If you removed this line (try it!) then re-ran the code, you'd get just an edge of the circle chopped off between the start and end point of the arc. This illustrates another important point of the canvas — if you try to fill an incomplete path (i.e. one that is not closed), the browser fills in a straight line between the start and end point and then fills it in.</li> + </ul> + </li> +</ol> + +<p>That's it for now; your final example should look like this:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/getting-started/3_canvas_paths.html", '100%', 200)}}</p> + +<div class="note"> +<p><strong>注</strong>: The finished code is available on GitHub as <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/3_canvas_paths.html">3_canvas_paths.html</a>.</p> +</div> + +<div class="note"> +<p><strong>注</strong>: To find out more about advanced path drawing features such as Bézier curves, check out our <a href="/ja/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes">Drawing shapes with canvas</a> tutorial.</p> +</div> + +<h3 id="Text" name="Text">テキスト</h3> + +<p>Canvas also has features for drawing text. Let's explore these briefly. Start by making another fresh copy of our canvas template (<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.html</a>) in which to draw the new example.</p> + +<p>Text is drawn using two methods:</p> + +<ul> + <li>{{domxref("CanvasRenderingContext2D.fillText", "fillText()")}} — draws filled text.</li> + <li>{{domxref("CanvasRenderingContext2D.strokeText", "strokeText()")}} — draws outline (stroke) text.</li> +</ul> + +<p>Both of these take three properties in their basic usage: the text string to draw and the X and Y coordinates of the point to start drawing the text at. This works out as the <strong>bottom left</strong> corner of the <strong>text box</strong> (literally, the box surrounding the text you draw), which might confuse you as other drawing operations tend to start from the top left corner — bear this in mind.</p> + +<p>There are also a number of properties to help control text rendering such as {{domxref("CanvasRenderingContext2D.font", "font")}}, which lets you specify font family, size, etc. It takes as its value the same syntax as the CSS {{cssxref("font")}} property.</p> + +<p>Try adding the following block to the bottom of your JavaScript:</p> + +<pre class="brush: js notranslate">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);</pre> + +<p>Here we draw two lines of text, one outline and the other stroke. The final example should look like so:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/getting-started/4_canvas_text.html", '100%', 180)}}</p> + +<div class="note"> +<p><strong>注</strong>: The finished code is available on GitHub as <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/4_canvas_text.html">4_canvas_text.html</a>.</p> +</div> + +<p>Have a play and see what you can come up with! You can find more information on the options available for canvas text at <a href="/ja/docs/Web/API/Canvas_API/Tutorial/Drawing_text">Drawing text</a>.</p> + +<h3 id="Drawing_images_onto_canvas" name="Drawing_images_onto_canvas">canvasに画像を描画する</h3> + +<p>It is possible to render external images onto your canvas. These can be simple images, frames from videos, or the content of other canvases. For the moment we'll just look at the case of using some simple images on our canvas.</p> + +<ol> + <li> + <p>As before, make another fresh copy of our canvas template (<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.html</a>) in which to draw the new example. In this case you'll also need to save a copy of our sample image — <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/firefox.png">firefox.png</a> — in the same directory.</p> + + <p>Images are drawn onto canvas using the {{domxref("CanvasRenderingContext2D.drawImage", "drawImage()")}} method. The simplest version takes three parameters — a reference to the image you want to render, and the X and Y coordinates of the image's top left corner.</p> + </li> + <li> + <p>Let's start by getting an image source to embed in our canvas. Add the following lines to the bottom of your JavaScript:</p> + + <pre class="brush: js notranslate">let image = new Image(); +image.src = 'firefox.png';</pre> + + <p>Here we create a new {{domxref("HTMLImageElement")}} object using the {{domxref("HTMLImageElement.Image()", "Image()")}} constructor. The returned object is the same type as that which is returned when you grab a reference to an existing {{htmlelement("img")}} element). We then set its {{htmlattrxref("src", "img")}} attribute to equal our Firefox logo image. At this point, the browser starts loading the image.</p> + </li> + <li> + <p>We could now try to embed the image using <code>drawImage()</code>, but we need to make sure the image file has been loaded first, otherwise the code will fail. We can achieve this using the <code>onload</code> event handler, which will only be invoked when the image has finished loading. Add the following block below the previous one:</p> + + <pre class="brush: js notranslate">image.onload = function() { + ctx.drawImage(image, 50, 50); +}</pre> + + <p>If you load your example in the browser now, you should see the image embeded in the canvas.</p> + </li> + <li> + <p>But there's more! What if we want to display only a part of the image, or to resize it? We can do both with the more complex version of <code>drawImage()</code>. Update your <code>ctx.drawImage()</code> line like so:</p> + + <pre class="brush: js notranslate">ctx.drawImage(image, 20, 20, 185, 175, 50, 50, 185, 175);</pre> + + <ul> + <li>The first parameter is the image reference, as before.</li> + <li>Parameters 2 and 3 define the coordinates of the top left corner of the area you want to cut out of the loaded image, relative to the top-left corner of the image itself. Nothing to the left of the first parameter or above the second will be drawn.</li> + <li>Parameters 4 and 5 define the width and height of the area we want to cut out from the original image we loaded.</li> + <li>Parameters 6 and 7 define the coordinates at which you want to draw the top-left corner of the cut-out portion of the image, relative to the top-left corner of the canvas.</li> + <li>Parameters 8 and 9 define the width and height to draw the cut-out area of the image. In this case, we have specified the same dimensions as the original slice, but you could resize it by specifying different values.</li> + </ul> + </li> +</ol> + +<p>The final example should look like so:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/getting-started/5_canvas_images.html", '100%', 260)}}</p> + +<div class="note"> +<p><strong>注</strong>: The finished code is available on GitHub as <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/5_canvas_images.html">5_canvas_images.html</a>.</p> +</div> + +<h2 id="Loops_and_animations" name="Loops_and_animations">ループとアニメーション</h2> + +<p>We have so far covered some very basic uses of 2D canvas, but really you won't experience the full power of canvas unless you update or animate it in some way. After all, canvas does provide scriptable images! If you aren't going to change anything, then you might as well just use static images and save yourself all the work.</p> + +<h3 id="Creating_a_loop" name="Creating_a_loop">ループの作成</h3> + +<p>Playing with loops in canvas is rather fun — you can run canvas commands inside a <code><a href="/ja/docs/Web/JavaScript/Reference/Statements/for">for</a></code> (or other type of) loop just like any other JavaScript code.</p> + +<p>Let's build a simple example.</p> + +<ol> + <li> + <p>Make another fresh copy of our canvas template (<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.html</a>) and open it in your code editor.</p> + </li> + <li> + <p>Add the following line to the bottom of your JavaScript. This contains a new method, {{domxref("CanvasRenderingContext2D.translate", "translate()")}}, which moves the origin point of the canvas:</p> + + <pre class="brush: js notranslate">ctx.translate(width/2, height/2);</pre> + + <p>This causes the coordinate origin (0, 0) to be moved to the center of the canvas, rather than being at the top left corner. This is very useful in many situations, like this one, where we want our design to be drawn relative to the center of the canvas.</p> + </li> + <li> + <p>Now add the following code to the bottom of the JavaScript:</p> + + <pre class="brush: js notranslate">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(var i = 0; i < length; i++) { + +}</pre> + + <p>Here we are implementing the same <code>degToRad()</code> function we saw in the triangle example above, a <code>rand()</code> function that returns a random number between given lower and upper bounds, <code>length</code> and <code>moveOffset</code> variables (which we'll find out more about later), and an empty <code>for</code> loop.</p> + </li> + <li> + <p>The idea here is that we'll draw something on the canvas inside the <code>for</code> loop, and iterate on it each time so we can create something interesting. Add the following code inside your <code>for</code> loop:</p> + + <pre class="brush: js notranslate">ctx.fillStyle = 'rgba(' + (255-length) + ', 0, ' + (255-length) + ', 0.9)'; +ctx.beginPath(); +ctx.moveTo(moveOffset, moveOffset); +ctx.lineTo(moveOffset+length, moveOffset); +let 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));</pre> + + <p>So on each iteration, we:</p> + + <ul> + <li>Set the <code>fillStyle</code> to be a shade of slightly transparent purple, which changes each time based on the value of <code>length</code>. As you'll see later the length gets smaller each time the loop runs, so the effect here is that the color gets brighter with each successive triangle drawn.</li> + <li>Begin the path.</li> + <li>Move the pen to a coordinate of <code>(moveOffset, moveOffset)</code>; This variable defines how far we want to move each time we draw a new triangle.</li> + <li>Draw a line to a coordinate of <code>(moveOffset+length, moveOffset)</code>. This draws a line of length <code>length</code> parallel to the X axis.</li> + <li>Calculate the triangle's height, as before.</li> + <li>Draw a line to the downward-pointing corner of the triangle, then draw a line back to the start of the triangle.</li> + <li>Call <code>fill()</code> to fill in the triangle.</li> + <li>Update the variables that describe the sequence of triangles, so we can be ready to draw the next one. We decrease the <code>length</code> value by 1, so the triangles get smaller each time; increase <code>moveOffset</code> by a small amount so each successive triangle is slightly further away, and use another new function, {{domxref("CanvasRenderingContext2D.rotate", "rotate()")}}, which allows us to rotate the entire canvas! We rotate it by 5 degrees before drawing the next triangle.</li> + </ul> + </li> +</ol> + +<p>That's it! The final example should look like so:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/6_canvas_for_loop.html", '100%', 550)}}</p> + +<p>At this point, we'd like to encourage you to play with the example and make it your own! 例えば、:</p> + +<ul> + <li>Draw rectangles or arcs instead of triangles, or even embed images.</li> + <li>Play with the <code>length</code> and <code>moveOffset</code> values.</li> + <li>Introduce some random numbers using that <code>rand()</code> function we included above but didn't use.</li> +</ul> + +<div class="note"> +<p><strong>注</strong>: The finished code is available on GitHub as <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/loops_animation/6_canvas_for_loop.html">6_canvas_for_loop.html</a>.</p> +</div> + +<h3 id="Animations" name="Animations">アニメーション</h3> + +<p>The loop example we built above was fun, but really you need a constant loop that keeps going and going for any serious canvas applications (such as games and real time visualizations). If you think of your canvas as being like a movie, you really want the display to update on each frame to show the updated view, with an ideal refresh rate of 60 frames per second so that movement appears nice and smooth to the human eye.</p> + +<p>There are a few JavaScript functions that will allow you to run functions repeatedly, several times a second, the best one for our purposes here being {{domxref("window.requestAnimationFrame()")}}. It takes one parameter — the name of the function you want to run for each frame. The next time the browser is ready to update the screen, your function will get called. If that function draws the new update to your animation, then calls <code>requestAnimationFrame()</code> again just before the end of the function, the animation loop will continue to run. The loop ends when you stop calling <code>requestAnimationFrame()</code> or if you call {{domxref("window.cancelAnimationFrame()")}} after calling <code>requestAnimationFrame()</code> but before the frame is called.</p> + +<div class="note"> +<p><strong>注:</strong> It's good practice to call <code>cancelAnimationFrame()</code> from your main code when you're done using the animation, to ensure that no updates are still waiting to be run.</p> +</div> + +<p>The browser works out complex details such as making the animation run at a consistent speed, and not wasting resources animating things that can't be seen.</p> + +<p>To see how it works, let's quickly look again at our Bouncing Balls example (<a href="https://mdn.github.io/learning-area/javascript/oojs/bouncing-balls/index-finished.html">see it live</a>, and also see <a href="https://github.com/mdn/learning-area/tree/master/javascript/oojs/bouncing-balls">the source code</a>). The code for the loop that keeps everything moving looks like this:</p> + +<pre class="brush: js notranslate">function loop() { + ctx.fillStyle = 'rgba(0, 0, 0, 0.25)'; + ctx.fillRect(0, 0, width, height); + + for(let i = 0; i < balls.length; i++) { + balls[i].draw(); + balls[i].update(); + balls[i].collisionDetect(); + } + + requestAnimationFrame(loop); +} + +loop();</pre> + +<p>We run the <code>loop()</code> function once at the bottom of the code to start the cycle, drawing the first animation frame; the <code>loop()</code> function then takes charge of calling <code>requestAnimationFrame(loop)</code> to run the next frame of the animation, again and again.</p> + +<p>Note that on each frame we are completely clearing the canvas and redrawing everything. For every ball present we draw it, update its position, and check to see if it is colliding with any other balls. Once you've drawn a graphic to a canvas, there's no way to manipulate that graphic individually like you can with DOM elements. You can't move each ball around on the canvas, because once it's drawn, it's part of the canvas, and is not an individual accessible element or object. Instead, you have to erase and redraw, either by erasing the entire frame and redrawing everything, or by having code that knows exactly what parts need to be erased and only erases and redraws the minimum area of the canvas necessary.</p> + +<p>Optimizing animation of graphics is an entire specialty of programming, with lots of clever techniques available. Those are beyond what we need for our example, though!</p> + +<p>In general, the process of doing a canvas animation involves the following steps:</p> + +<ol> + <li>Clear the canvas contents (e.g. with {{domxref("CanvasRenderingContext2D.fillRect", "fillRect()")}} or {{domxref("CanvasRenderingContext2D.clearRect", "clearRect()")}}).</li> + <li>Save state (if necessary) using {{domxref("CanvasRenderingContext2D.save", "save()")}} — this is needed when you want to save settings you've updated on the canvas before continuing, which is useful for more advanced applications.</li> + <li>Draw the graphics you are animating.</li> + <li>Restore the settings you saved in step 2, using {{domxref("CanvasRenderingContext2D.restore", "restore()")}}</li> + <li>Call <code>requestAnimationFrame()</code> to schedule drawing of the next frame of the animation.</li> +</ol> + +<div class="note"> +<p><strong>注</strong>: We won't cover <code>save()</code> and <code>restore()</code> here, but they are explained nicely in our <a href="/ja/docs/Web/API/Canvas_API/Tutorial/Transformations">Transformations</a> tutorial (and the ones that follow it).</p> +</div> + +<h3 id="A_simple_character_animation" name="A_simple_character_animation">簡単なキャラクターのアニメーション</h3> + +<p>Now let's create our own simple animation — we'll get a character from a certain rather awesome retro computer game to walk across the screen.</p> + +<ol> + <li> + <p>Make another fresh copy of our canvas template (<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/getting-started/1_canvas_template.html">1_canvas_template.html</a>) and open it in your code editor. Make a copy of <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/loops_animation/walk-right.png">walk-right.png</a> in the same directory.</p> + </li> + <li> + <p>At the bottom of the JavaScript, add the following line to once again make the coordinate origin sit in the middle of the canvas:</p> + + <pre class="brush: js notranslate">ctx.translate(width/2, height/2);</pre> + </li> + <li> + <p>Now let's create a new {{domxref("HTMLImageElement")}} object, set its {{htmlattrxref("src", "img")}} to the image we want to load, and add an <code>onload</code> event handler that will cause the <code>draw()</code> function to fire when the image is loaded:</p> + + <pre class="brush: js notranslate">let image = new Image(); +image.src = 'walk-right.png'; +image.onload = draw;</pre> + </li> + <li> + <p>Now we'll add some variables to keep track of the position the sprite is to be drawn on the screen, and the sprite number we want to display.</p> + + <pre class="brush: js notranslate">let sprite = 0; +let posX = 0;</pre> + + <p>Let's explain the spritesheet image (which we have respectfully borrowed from Mike Thomas' <a href="http://atomicrobotdesign.com/blog/htmlcss/create-a-sprite-sheet-walk-cycle-using-using-css-animation/" rel="bookmark" title="Permanent Link to Create a sprite sheet walk cycle using using CSS animation">Create a sprite sheet walk cycle using using CSS animation</a>). The image looks like this:</p> + + <p><img alt="" src="https://mdn.mozillademos.org/files/14847/walk-right.png" style="display: block; height: 148px; margin: 0px auto; width: 612px;"></p> + + <p>It contains six sprites that make up the whole walking sequence — each one is 102 pixels wide and 148 pixels high. To display each sprite cleanly we will have to use <code>drawImage()</code> to chop out a single sprite image from the spritesheet and display only that part, like we did above with the Firefox logo. The X coordinate of the slice will have to be a multiple of 102, and the Y coordinate will always be 0. The slice size will always be 102 by 148 pixels.</p> + </li> + <li> + <p>Now let's insert an empty <code>draw()</code> function at the bottom of the code, ready for filling up with some code:</p> + + <pre class="brush: js notranslate">function draw() { + +};</pre> + </li> + <li> + <p>the rest of the code in this section goes inside <code>draw()</code>. First, add the following line, which clears the canvas to prepare for drawing each frame. Notice that we have to specify the top-left corner of the rectangle as <code>-(width/2), -(height/2)</code> because we specified the origin position as <code>width/2, height/2</code> earlier on.</p> + + <pre class="brush: js notranslate">ctx.fillRect(-(width/2), -(height/2), width, height);</pre> + </li> + <li> + <p>Next, we'll draw our image using drawImage — the 9-parameter version. Add the following:</p> + + <pre class="brush: js notranslate">ctx.drawImage(image, (sprite*102), 0, 102, 148, 0+posX, -74, 102, 148);</pre> + + <p>As you can see:</p> + + <ul> + <li>We specify <code>image</code> as the image to embed.</li> + <li>Parameters 2 and 3 specify the top-left corner of the slice to cut out of the source image, with the X value as <code>sprite</code> multiplied by 102 (where <code>sprite</code> is the sprite number between 0 and 5) and the Y value always 0.</li> + <li>Parameters 4 and 5 specify the size of the slice to cut out — 102 pixels by 148 pixels.</li> + <li>Parameters 6 and 7 specify the top-left corner of the box into which to draw the slice on the canvas — the X position is 0 + <code>posX</code>, meaning that we can alter the drawing position by altering the <code>posX</code> value.</li> + <li>Parameters 8 and 9 specify the size of the image on the canvas. We just want to keep its original size, so we specify 102 and 148 as the width and height.</li> + </ul> + </li> + <li> + <p>Now we'll alter the <code>sprite</code> value after each draw — well, after some of them anyway. Add the following block to the bottom of the <code>draw()</code> function:</p> + + <pre class="brush: js notranslate"> if (posX % 13 === 0) { + if (sprite === 5) { + sprite = 0; + } else { + sprite++; + } + }</pre> + + <p>We are wrapping the whole block in <code> if (posX % 13 === 0) { ... }</code>. We use the modulo (<code>%</code>) operator (also known as the <a href="/ja/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Remainder_()">remainder operator</a>) to check whether the <code>posX</code> value can be exactly divided by 13 with no remainder. If so, we move on to the next sprite by incrementing <code>sprite</code> (wrapping to 0 after we're done with sprite #5). This effectively means that we are only updating the sprite on every 13th frame, or roughly about 5 frames a second (<code>requestAnimationFrame()</code> calls us at up to 60 frames per second if possible). We are deliberately slowing down the frame rate because we only have six sprites to work with, and if we display one every 60th of a second, our character will move way too fast!</p> + + <p>Inside the outer block we use an <code><a href="/ja/docs/Web/JavaScript/Reference/Statements/if...else">if ... else</a></code> statement to check whether the <code>sprite</code> value is at 5 (the last sprite, given that the sprite numbers run from 0 to 5). If we are showing the last sprite already, we reset <code>sprite</code> back to 0; if not we just increment it by 1.</p> + </li> + <li> + <p>Next we need to work out how to change the <code>posX</code> value on each frame — add the following code block just below your last one. </p> + + <pre class="brush: js notranslate"> if(posX > width/2) { + newStartPos = -((width/2) + 102); + posX = Math.ceil(newStartPos); + console.log(posX); + } else { + posX += 2; + }</pre> + + <p>We are using another <code>if ... else</code> statement to see if the value of <code>posX</code> has become greater than <code>width/2</code>, which means our character has walked off the right edge of the screen. If so, we calculate a position that would put the character just to the left of the left side of the screen.</p> + + <p>If our character hasn't yet walked off the edge of the screen, we simply increment <code>posX</code> by 2. This will make him move a little bit to the right the next time we draw him.</p> + </li> + <li> + <p>Finally, we need to make the animation loop by calling {{domxref("window.requestAnimationFrame", "requestAnimationFrame()")}} at the bottom of the <code>draw()</code> function:</p> + + <pre class="brush: js notranslate">window.requestAnimationFrame(draw);</pre> + </li> +</ol> + +<p>That's it! The final example should look like so:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html", '100%', 260)}}</p> + +<div class="note"> +<p><strong>注</strong>: The finished code is available on GitHub as <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/loops_animation/7_canvas_walking_animation.html">7_canvas_walking_animation.html</a>.</p> +</div> + +<h3 id="A_simple_drawing_application" name="A_simple_drawing_application">簡単なドローアプリ</h3> + +<p>As a final animation example, we'd like to show you a very simple drawing application, to illustrate how the animation loop can be combined with user input (like mouse movement, in this case). We won't get you to walk through and build this one; we'll just explore the most interesting parts of the code.</p> + +<p>The example can be found on GitHub as <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/loops_animation/8_canvas_drawing_app.html">8_canvas_drawing_app.html</a>, and you can play with it live below:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/loops_animation/8_canvas_drawing_app.html", '100%', 600)}}</p> + +<p>Let's look at the most interesting parts. First of all, we keep track of the mouse's X and Y coordinates and whether it is being clicked or not with three variables: <code>curX</code>, <code>curY</code>, and <code>pressed</code>. When the mouse moves, we fire a function set as the <code>onmousemove</code> event handler, which captures the current X and Y values. We also use <code>onmousedown</code> and <code>onmouseup</code> event handlers to change the value of <code>pressed</code> to <code>true</code> when the mouse button is pressed, and back to <code>false</code> again when it is released.</p> + +<pre class="brush: js notranslate">let curX; +let curY; +let pressed = false; + +document.onmousemove = function(e) { + curX = (window.Event) ? e.pageX : e.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft); + curY = (window.Event) ? e.pageY : e.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop); +} + +canvas.onmousedown = function() { + pressed = true; +}; + +canvas.onmouseup = function() { + pressed = false; +}</pre> + +<p>When the "Clear canvas" button is pressed, we run a simple function that clears the whole canvas back to black, the same way we've seen before:</p> + +<pre class="brush: js notranslate">clearBtn.onclick = function() { + ctx.fillStyle = 'rgb(0, 0, 0)'; + ctx.fillRect(0, 0, width, height); +}</pre> + +<p>The drawing loop is pretty simple this time around — if pressed is <code>true</code>, we draw a circle with a fill style equal to the value in the color picker, and a radius equal to the value set in the range input. We have to draw the circle 85 pixels above where we measured it from, because the vertical measurement is taken from the top of the viewport, but we are drawing the circle relative to the top of the canvas, which starts below the 85 pixel-high toolbar. If we drew it with just <code>curY</code> as the y coordinate, it would appear 85 pixels lower than the mouse position.</p> + +<pre class="brush: js notranslate">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();</pre> + +<div class="note"> +<p><strong>注</strong>: The {{htmlelement("input")}} <code>range</code> and <code>color</code> types are supported fairly well across browsers, with the exception of Internet Explorer versions less than 10; also Safari doesn't yet support <code>color</code>. If your browser doesn't support these inputs, they will fall back to simple text fields and you'll just have to enter valid color/number values yourself.</p> +</div> + +<h2 id="WebGL" name="WebGL">WebGL</h2> + +<p>It's now time to leave 2D behind, and take a quick look at 3D canvas. 3D canvas content is specified using the <a href="/ja/docs/Web/API/WebGL_API">WebGL API</a>, which is a completely separate API from the 2D canvas API, even though they both render onto {{htmlelement("canvas")}} elements.</p> + +<p>WebGL is based on <a href="/ja/docs/Glossary/OpenGL">OpenGL</a> (Open Graphics Library), and allows you to communicate directly with the computer's <a href="/ja/docs/Glossary/GPU">GPU</a>. As such, writing raw WebGL is closer to low level languages such as C++ than regular JavaScript; it is quite complex but incredibly powerful.</p> + +<h3 id="Using_a_library" name="Using_a_library">ライブラリーの使用</h3> + +<p>Because of its complexity, most people write 3D graphics code using a third party JavaScript library such as <a href="/ja/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">Three.js</a>, <a href="/ja/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_PlayCanvas">PlayCanvas</a>, or <a href="/ja/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Babylon.js">Babylon.js</a>. Most of these work in a similar way, providing functionality to create primitive and custom shapes, position viewing cameras and lighting, covering surfaces with textures, and more. They handle the WebGL for you, letting you work on a higher level.</p> + +<p>Yes, using one of these means learning another new API (a third party one, in this case), but they are a lot simpler than coding raw WebGL.</p> + +<h3 id="Recreating_our_cube" name="Recreating_our_cube">立方体を作成する</h3> + +<p>Let's look at a simple example of how to create something with a WebGL library. We'll choose <a href="/ja/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">Three.js</a>, as it is one of the most popular ones. In this tutorial we'll create the 3D spinning cube we saw earlier.</p> + +<ol> + <li> + <p>To start with, make a local copy of <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/threejs-cube/index.html">index.html</a> in a new folder, then save a copy of <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/drawing-graphics/threejs-cube/metal003.png">metal003.png</a> in the same folder. This is the image we'll use as a surface texture for the cube later on.</p> + </li> + <li> + <p>Next, create a new file called <code>main.js</code>, again in the same folder as before.</p> + </li> + <li> + <p>If you open <code>index.html</code> in your code editor, you'll see that it has two {{htmlelement("script")}} elements — the first one attaching <code>three.min.js</code> to the page, and the second one attaching our <code>main.js</code> file to the page. You need to <a href="https://raw.githubusercontent.com/mrdoob/three.js/dev/build/three.min.js">download the three.min.js library</a> and save it in the same directory as before.</p> + </li> + <li> + <p>Now we've got <code>three.js</code> attached to our page, we can start to write JavaScript that makes use of it into <code>main.js</code>. Let's start by creating a new scene — add the following into your main.js file:</p> + + <pre class="brush: js notranslate">const scene = new THREE.Scene();</pre> + + <p>The <code><a href="https://threejs.org/docs/index.html#Reference/Scenes/Scene">Scene()</a></code> constructor creates a new scene, which represents the whole 3D world we are trying to display.</p> + </li> + <li> + <p>Next, we need a <strong>camera</strong> so we can see the scene. In 3D imagery terms, the camera represents a viewer's position in the world. To create a camera, add the following lines next:</p> + + <pre class="brush: js notranslate">const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); +camera.position.z = 5; +</pre> + + <p>The <code><a href="https://threejs.org/docs/index.html#Reference/Cameras/PerspectiveCamera">PerspectiveCamera()</a></code> constructor takes four arguments:</p> + + <ul> + <li>The field of view: How wide the area in front of the camera is that should be visible onscreen, in degrees.</li> + <li>The aspect ratio: Usually, this is the ratio of the scene's width divided by the scene's height. Using another value will distort the scene (which might be what you want, but usually isn't).</li> + <li>The near plane: How close to the camera objects can be before we stop rendering them to the screen. Think about how when you move your fingertip closer and closer to the space between your eyes, eventually you can't see it anymore.</li> + <li>The far plane: How far away things are from the camera before they are no longer rendered.</li> + </ul> + + <p>We also set the camera's position to be 5 distance units out of the Z axis, which, like in CSS, is out of the screen towards you, the viewer.</p> + </li> + <li> + <p>The third vital ingredient is a renderer. This is an object that renders a given scene, as viewed through a given camera. We'll create one for now using the <code><a href="https://threejs.org/docs/index.html#Reference/Renderers/WebGLRenderer">WebGLRenderer()</a></code> constructor, but we'll not use it till later. Add the following lines next:</p> + + <pre class="brush: js notranslate">const renderer = new THREE.WebGLRenderer(); +renderer.setSize(window.innerWidth, window.innerHeight); +document.body.appendChild(renderer.domElement);</pre> + + <p>The first line creates a new renderer, the second line sets the size at which the renderer will draw the camera's view, and the third line appends the {{htmlelement("canvas")}} element created by the renderer to the document's {{htmlelement("body")}}. Now anything the renderer draws will be displayed in our window.</p> + </li> + <li> + <p>Next, we want to create the cube we'll display on the canvas. Add the following chunk of code at the bottom of your JavaScript:</p> + + <pre class="brush: js notranslate">let cube; + +let loader = new THREE.TextureLoader(); + +loader.load( 'metal003.png', function (texture) { + texture.wrapS = THREE.RepeatWrapping; + texture.wrapT = THREE.RepeatWrapping; + texture.repeat.set(2, 2); + + let geometry = new THREE.BoxGeometry(2.4, 2.4, 2.4); + let material = new THREE.MeshLambertMaterial( { map: texture, shading: THREE.FlatShading } ); + cube = new THREE.Mesh(geometry, material); + scene.add(cube); + + draw(); +});</pre> + + <p>There's a bit more to take in here, so let's go through it in stages:</p> + + <ul> + <li>We first create a <code>cube</code> global variable so we can access our cube from anywhere in the code.</li> + <li>Next, we create a new <code><a href="https://threejs.org/docs/index.html#Reference/Loaders/TextureLoader">TextureLoader</a></code> object, then call <code>load()</code> on it. <code>load()</code> takes two parameters in this case (although it can take more): the texture we want to load (our PNG), and a function that will run when the texture has loaded.</li> + <li>Inside this function we use properties of the <code><a href="https://threejs.org/docs/index.html#Reference/Textures/Texture">texture</a></code> object to specify that we want a 2 x 2 repeat of the image wrapped around all sides of the cube. Next, we create a new <code><a href="https://threejs.org/docs/index.html#Reference/Geometries/BoxGeometry">BoxGeometry</a></code> object and a new <code><a href="https://threejs.org/docs/index.html#Reference/Materials/MeshLambertMaterial">MeshLambertMaterial</a></code> object, and bring them together in a <code><a href="https://threejs.org/docs/index.html#Reference/Objects/Mesh">Mesh</a></code> to create our cube. An object typically requires a geometry (what shape it is) and a material (what its surface looks like).</li> + <li>Last of all, we add our cube to the scene, then call our <code>draw()</code> function to start off the animation.</li> + </ul> + </li> + <li> + <p>Before we get to defining <code>draw()</code>, we'll add a couple of lights to the scene, to liven things up a bit; add the following blocks next:</p> + + <pre class="brush: js notranslate">let light = new THREE.AmbientLight('rgb(255, 255, 255)'); // soft white light +scene.add(light); + +let spotLight = new THREE.SpotLight('rgb(255, 255, 255)'); +spotLight.position.set( 100, 1000, 1000 ); +spotLight.castShadow = true; +scene.add(spotLight);</pre> + + <p>An <code><a href="https://threejs.org/docs/index.html#Reference/Lights/AmbientLight">AmbientLight</a></code> object is a kind of soft light that lightens the whole scene a bit, like the sun when you are outside. The <code><a href="https://threejs.org/docs/index.html#Reference/Lights/SpotLight">SpotLight</a></code> object, on the other hand, is a directional beam of light, more like a flashlight/torch (or a spotlight, in fact).</p> + </li> + <li> + <p>Last of all, let's add our <code>draw()</code> function to the bottom of the code:</p> + + <pre class="brush: js notranslate">function draw() { + cube.rotation.x += 0.01; + cube.rotation.y += 0.01; + renderer.render(scene, camera); + + requestAnimationFrame(draw); +}</pre> + + <p>This is fairly intuitive; on each frame, we rotate our cube slightly on its X and Y axes, then render the scene as viewed by our camera, then finally call <code>requestAnimationFrame()</code> to schedule drawing our next frame.</p> + </li> +</ol> + +<p>Let's have another quick look at what the finished product should look like:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/drawing-graphics/threejs-cube/index.html", '100%', 500)}}</p> + +<p>You can <a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/drawing-graphics/threejs-cube">find the finished code on GitHub</a>.</p> + +<div class="note"> +<p><strong>注</strong>: In our GitHub repo you can also find another interesting 3D cube example — <a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/drawing-graphics/threejs-video-cube">Three.js Video Cube</a> (<a href="https://mdn.github.io/learning-area/javascript/apis/drawing-graphics/threejs-video-cube/">see it live also</a>). This uses {{domxref("MediaDevices.getUserMedia", "getUserMedia()")}} to take a video stream from a computer web cam and project it onto the side of the cube as a texture!</p> +</div> + +<h2 id="Summary" name="Summary">まとめ</h2> + +<p>At this point, you should have a useful idea of the basics of graphics programming using Canvas and WebGL and what you can do with these APIs, as well as a good idea of where to go for further information. Have fun!</p> + +<h2 id="See_also" name="See_also">関連情報</h2> + +<p>Here we have covered only the real basics of canvas — there is so much more to learn! The below articles will take you further.</p> + +<ul> + <li><a href="/ja/docs/Web/API/Canvas_API/Tutorial">Canvas tutorial</a> — A very detailed tutorial series explaining what you should know about 2D canvas in much more detail than was covered here. Essential reading.</li> + <li><a href="/ja/docs/Web/API/WebGL_API/Tutorial">WebGL tutorial</a> — A series that teaches the basics of raw WebGL programming.</li> + <li><a href="/ja/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">Building up a basic demo with Three.js</a> — basic Three.js tutorial. We also have equivalent guides for <a href="/ja/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_PlayCanvas">PlayCanvas</a> or <a href="/ja/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Babylon.js">Babylon.js</a>.</li> + <li><a href="/ja/docs/Games">Game development</a> — the landing page for web games development on MDN. There are some really useful tutorials and techniques available here related to 2D and 3D canvas — see the Techniques and Tutorials menu options.</li> +</ul> + +<h2 id="Examples" name="Examples">例</h2> + +<ul> + <li><a href="https://github.com/mdn/violent-theremin">Violent theramin</a> — Uses the Web Audio API to generate sound, and canvas to generate a pretty visualization to go along with it.</li> + <li><a href="https://github.com/mdn/voice-change-o-matic">Voice change-o-matic</a> — Uses a canvas to visualize real-time audio data from the Web Audio API.</li> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Third_party_APIs", "Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</p> + +<h2 id="In_this_module" name="In_this_module">このモジュール内</h2> + +<ul> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Introduction to web APIs</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">Manipulating documents</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">Fetching data from the server</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">Third party APIs</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">Drawing graphics</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">Video and audio APIs</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">Client-side storage</a></li> +</ul> diff --git a/files/ja/learn/javascript/client-side_web_apis/fetching_data/index.html b/files/ja/learn/javascript/client-side_web_apis/fetching_data/index.html new file mode 100644 index 0000000000..44f7c8b035 --- /dev/null +++ b/files/ja/learn/javascript/client-side_web_apis/fetching_data/index.html @@ -0,0 +1,389 @@ +--- +title: サーバからのデータ取得 +slug: Learn/JavaScript/Client-side_web_APIs/Fetching_data +tags: + - API + - Article + - Beginner + - CodingScripting + - Fetch + - JSON + - JavaScript + - Learn + - Promises + - Server + - XHR + - XML + - XMLHttpRequest + - data + - request +translation_of: Learn/JavaScript/Client-side_web_APIs/Fetching_data +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Manipulating_documents", "Learn/JavaScript/Client-side_web_APIs/Third_party_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</div> + +<p class="summary">モダンな Web サイトやアプリケーションでしょっちゅう必要になる仕事は、サーバから個々のデータを取ってきて、新しいページ全体を読んでくることなしに、ページの一部を書き換える事です。この一見ちょっとした事が、サイトのパフォーマンスや振舞いに巨大なインパクトを与えました。この記事ではそのコンセプトを解説し、これを可能にした技術 XMLHttpRequest や Fetch API について見ていきます。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>JavaScript の基本 (<a href="/ja/docs/Learn/JavaScript/First_steps">最初のステップ</a>、<a href="/ja/docs/Learn/JavaScript/Building_blocks">ビルディングブロック</a>、<a href="/ja/docs/Learn/JavaScript/Objects">JavaScript オブジェクト</a>を参照)、<a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">クライアントサイド API の基本</a></td> + </tr> + <tr> + <th scope="row">目標:</th> + <td>サーバからデータを取得し、それを使用して Web ページのコンテンツを更新する方法を習得する。</td> + </tr> + </tbody> +</table> + +<h2 id="これの問題は何か">これの問題は何か?</h2> + +<p>もともと Web のページ読み込みは単純でした — Web サイトのデータをサーバにリクエストすると、何も問題がなければ、ページを構成するいろいろなものがダウンロードされてあなたのコンピュータに表示されていました。</p> + +<p><img alt="A basic representation of a web site architecture" src="https://mdn.mozillademos.org/files/6475/web-site-architechture@2x.png" style="display: block; height: 134px; margin: 0px auto; width: 484px;"></p> + +<p>このモデルの問題は、どこかページの一部を書き換えたい場合、例えば新しい商品の一群を表示したり新しいページを読み込ませたりをする毎に、ページ全体を読み直さなければならない事です。これはとても無駄が多くてユーザ体験が悪化します、とりわけページが大きくて複雑になってくるにつれて。</p> + +<h3 id="Ajax_の登場">Ajax の登場</h3> + +<p>上述の問題を解決すべく、Web ページから細かいデータ (<a href="/docs/Web/HTML">HTML</a>、{{glossary("XML")}}、<a href="/docs/Learn/JavaScript/Objects/JSON">JSON</a> やプレーンテキストのような) をリクエストし、それを必要な時だけ表示するという技術の誕生へと繋がりました。</p> + +<p>これは {{domxref("XMLHttpRequest")}} や、最近では <a href="/docs/Web/API/Fetch_API">Fetch API</a> の利用によって実現されます。これらの技術は、Web ページがサーバにある特定のリソースを直接 <a href="/docs/Web/HTTP">HTTP</a> リクエストし、必要があれば結果のデータを表示する前に整形する事を可能にしました。</p> + +<div class="note"> +<p><strong>注記</strong>: これらのテクニック一般はかつて Ajax (Asynchronous JavaScript and XML)と呼ばれていましたが、これは {{domxref("XMLHttpRequest")}} を使って XML データを要求するものが多かったためです。今日ではそういうものばかりではありませんが (<code>XMLHttpRequest</code> や Fetch を使って JSON を要求する場合の方が多いでしょう)、結果としては同じであり、"Ajax" という用語はしばしば今でもこのテクニックを説明するのに使われます。</p> +</div> + +<p><img alt="A simple modern architecture for web sites" src="https://mdn.mozillademos.org/files/6477/moderne-web-site-architechture@2x.png" style="display: block; height: 235px; margin: 0px auto; width: 559px;"></p> + +<p>Ajax モデルには、ブラウザにページ全体をリロードされるのではなく、もっと賢くデータをリクエストするために Web API をプロキシとして使うという事も含まれます。これの重要性を考えてみて下さい:</p> + +<ol> + <li>お気に入りの情報に富んだサイト、アマゾンとか YouTube とか CNN とかに行って読み込みます。</li> + <li>さて新しい商品だか何だかを検索します。メインのコンテンツは変わるでしょうが、周りに表示されている情報、ヘッダーやフッター、ナビゲーションメニューなど、大半はそのままでしょう。</li> +</ol> + +<p>これはとても良いことで、それは:</p> + +<ul> + <li>ページの更新がずっと素早く、切り替わるのを待つ必要もないので、サイトがずっと早くて反応の良いものに感じられます。</li> + <li>更新毎にダウンロードされるデータが少ないので、帯域の無駄が少なくなります。ブロードバンドに接続されたデスクトップではさして問題ではないかもしれませんが、モバイルデバイスからや、どこでも高速インターネット接続が使えるわけではない開発途上国ではとても重要な問題です。</li> +</ul> + +<p>さらなる高速化のために、サイトの中には必要なものやデータを最初にリクエストされた時にユーザのコンピュータに保存してしまい、以降の訪問では保存ずみのものを、サーバから最新版のダウンロードさせる事なく使用するものもあります。コンテンツはそれが更新された時だけサーバから再読み込みされます。</p> + +<p><img alt="A basic web app data flow architecture" src="https://mdn.mozillademos.org/files/6479/web-app-architecture@2x.png" style="display: block; height: 383px; margin: 0px auto; width: 562px;"></p> + +<h2 id="基本的な_Ajax_リクエスト">基本的な Ajax リクエスト</h2> + +<p>{{domxref("XMLHttpRequest")}} と <a href="/docs/Web/API/Fetch_API">Fetch</a> それぞれを使って、そのようなリクエストをどうやるのか見ていきましょう。それらの例では、いくつかの異なるテキストファイルから取り出したデータをリクエストし、コンテンツ領域に埋め込みます。</p> + +<p>この一連のファイルは疑似データベースとして働きます。実際のアプリケーションでは、PHP や Python、Node のようなサーバサイド言語を使ってデータベースから取り出したデータをリクエストする場合が多いでしょう。ですがここでは簡単にしておき、クライアント側のパートに集中します。</p> + +<h3 id="XMLHttpRequest">XMLHttpRequest</h3> + +<p><code>XMLHttpRequest</code> (よく XHR と略記されます) は今となってはかなり古い技術です — Microsoft によって1990年代に発明され、非常に長い間ブラウザを超えて標準化されてきました。</p> + +<ol> + <li> + <p>この例題を始めるにあたり、<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/ajax-start.html">ajax-start.html</a> と4つのテキストファイル — <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse1.txt">verse1.txt</a>、<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse2.txt">verse2.txt</a>、<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse3.txt">verse3.txt</a> と <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse4.txt">verse4.txt</a> — のローカルコピーを、あなたのコンピュータの新しいディレクトリに作って下さい。この例題では、ドロップダウンメニューから選択されたら、詩 (ご存知の詩かも) のこれら異なる節を XHR を使って読み込みます。</p> + </li> + <li> + <p>{{htmlelement("script")}} 要素のすぐ内側に、下のコードを書き足して下さい。これは {{htmlelement("select")}} と {{htmlelement("pre")}} 要素への参照を定数に保存し、{{domxref("GlobalEventHandlers.onchange","onchange")}} イベントハンドラ関数を定義していて、これは select の値が変わったら、その値が呼び出される関数 <code>updateDisplay()</code> の引数となるようにします。</p> + + <pre class="brush: js notranslate">const verseChoose = document.querySelector('select'); +const poemDisplay = document.querySelector('pre'); + +verseChoose.onchange = function() { + const verse = verseChoose.value; + updateDisplay(verse); +};</pre> + </li> + <li> + <p><code>updateDisplay()</code> 関数を定義しましょう。まずはさっきのコードブロックの下に以下を書き足します — これは関数のからっぽのガワです。 注: ステップ 4 から 9 はすべて、この関数<em>内で</em>実施します。</p> + + <pre class="brush: js notranslate">function updateDisplay(verse) { + +}</pre> + </li> + <li> + <p>関数を、後から必要になる読み込みたいテキストファイルを指す相対 URL を作るところからはじめます。{{htmlelement("select")}} 要素の値は常に、選択されている {{htmlelement("option")}} の内側テキスト、例えば"Verse 1"とか、に一致します (value 属性で異なる値を設定していなければ)。これに相当するテキストファイルは "verse1.txt" で HTML と同じディレクトリにあるので、ファイル名だけで十分です。</p> + + <p>ただ、Web サーバはたいてい大文字小文字を区別しますし、今回のファイル名にスペースは含まれていません。"Verse 1" を "verse1.txt" に変換するためには、V を小文字にして、スペースを取り除き、.txt を末尾に追加しなければなりません。これは{{jsxref("String.replace", "replace()")}} に {{jsxref("String.toLowerCase", "toLowerCase()")}}、あと単なる <a href="/ja/docs/Learn/JavaScript/First_steps/Strings#Concatenating_strings">文字列の結合</a> で実現できます。以下のコードをあなたの <code>updateDisplay()</code> 関数の内側に追加して下さい:</p> + + <pre class="brush: js notranslate">verse = verse.replace(" ", ""); +verse = verse.toLowerCase(); +let url = verse + '.txt';</pre> + </li> + <li> + <p>XHR リクエストを作り始めるため、リクエストオブジェクトを {{domxref("XMLHttpRequest.XMLHttpRequest", "XMLHttpRequest()")}} コンストラクタを使って作成しなければなりません。このオブジェクトには好きな名前を付けられますが、単純にするため <code>request</code> を使います。<code>updateDisplay()</code> 関数の内側で、先の行の下に以下を追加します:</p> + + <pre class="brush: js notranslate">let request = new XMLHttpRequest();</pre> + </li> + <li> + <p>次に {{domxref("XMLHttpRequest.open","open()")}} メソッドを使ってどの <a href="/ja/docs/Web/HTTP/Methods">HTTP リクエストメソッド</a> を使ってリソースをネットワークから取得するか、URL はどこかを指定しなければなりません。ここでは単に <code><a href="/ja/docs/Web/HTTP/Methods/GET">GET</a></code> メソッドを使い、URL には <code>url</code> 変数の値をセットします。先の行の下に以下を追加します:</p> + + <pre class="brush: js notranslate">request.open('GET', url);</pre> + </li> + <li> + <p>次はレスポンスにどのような形式にしたいか指定 — これはリクエストの {{domxref("XMLHttpRequest.responseType", "responseType")}} プロパティで指定します — <code>text</code> にします。厳密に言えばこの場合は必須の指定ではありません — XHR はデフォルトで text を返します — が、いつの日か他のデータ形式を指定したくなる場合にそなえて、この設定をする習慣をつけておくと良いと思います。次を追加して下さい:</p> + + <pre class="brush: js notranslate">request.responseType = 'text';</pre> + </li> + <li> + <p>ネットワークからリソースを取得する処理は非同期{{glossary("asynchronous")}} 処理なので、戻りを使って何かをする前に、あなたは処理が完了(リソースがネットワークから返ってくる)するのを待たなければならず、さもないとエラーが投げられます。XHR では {{domxref("XMLHttpRequest.onload", "onload")}} イベントハンドラを使ってこの問題をさばけます — これは {{event("load")}} イベントが発火(レスポンスが返ってきた)した時に実行されます。このイベントが起きた後は、レスポンスデータは XHR リクエストオブジェクトの <code>response</code> プロパティとして取得できます。</p> + + <p>さっき追加した行の後に以下を追加して下さい。<code>onload</code> イベントハンドラの中で、<code>poemDisplay</code> ({{htmlelement("pre")}}要素) の <code><a href="/ja/docs/Web/API/Node/textContent">textContent</a></code> プロパティに {{domxref("XMLHttpRequest.response", "request.response")}} プロパティの値を設定しているのがお判りでしょう。</p> + + <pre class="brush: js notranslate">request.onload = function() { + poemDisplay.textContent = request.response; +};</pre> + </li> + <li> + <p>以上は全部、XHR リクエストの設定です — 実は私たちがやれと指示するまで動作はしません。やれと指示するには、{{domxref("XMLHttpRequest.send","send()")}} メソッドを使います。さっき追加した行の後に以下を追加して、関数を完成させます。この行は、<code>updateDisplay()</code> 関数の閉じ中括弧のすぐ上に置く必要があります。</p> + + <pre class="brush: js notranslate">request.send();</pre> + </li> + <li> + <p>今の時点でのこの例題にある問題の一つは、最初に読み込まれた時点ではなにも詩が表示されないことです。これを直すには、あなたのコードの一番下 (<code></script></code> 閉じタグのすぐ上) に以下の二行を追加し、デフォルトで1番の詩を読み込みませ、{{htmlelement("select")}} 要素に適切な値を指させます:</p> + + <pre class="brush: js notranslate">updateDisplay('Verse 1'); +verseChoose.value = 'Verse 1';</pre> + </li> +</ol> + +<h3 id="サーバからあなたの例題を送らせる">サーバからあなたの例題を送らせる</h3> + +<p>今時のブラウザ (Chrome も含まれます) は、ローカルファイルとして例題を実行しても XHR リクエストを行ないません。これはセキュリティの制限によるものです (Web のセキュリティにより詳しくは <a href="/docs/Learn/Server-side/First_steps/Website_security">Webサイトのセキュリティ</a>を読んで下さい)。</p> + +<p>これをどうにかするため、例題をローカルの Web サーバを使って実行しなければなりません。どうやるのかは、 <a href="/docs/Learn/Common_questions/set_up_a_local_testing_server">テスト用のローカルサーバを設定するにはどうすればいい?</a> を読んで下さい。</p> + +<h3 id="Fetch">Fetch</h3> + +<p>Fetch API は、基本的には XHR の今風の代替品です — 最近になってブラウザに組込まれたもので、非同期 HTTP リクエストを JavaScript で、開発者や他の Fetch の上に組まれた API から簡単に行なえるようにするためのものです。</p> + +<p>先の例を Fetch を使うように書き換えてみましょう!</p> + +<ol> + <li> + <p>さっき完成させた例題のディレクトリのコピーを作ります(前の例題を完成させていないなら、新しいディレクトリを作成して、そこに <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/xhr-basic.html">xhr-basic.html</a> と4つのテキストファイル — (<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse1.txt">verse1.txt</a>、<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse2.txt">verse2.txt</a>、<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse3.txt">verse3.txt</a> と <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/verse4.txt">verse4.txt</a>) のコピーを作って下さい。</p> + </li> + <li> + <p><code>updateDisplay()</code> 関数の中から、XHR のコードを探し出します:</p> + + <pre class="brush: js notranslate">let request = new XMLHttpRequest(); +request.open('GET', url); +request.responseType = 'text'; + +request.onload = function() { + poemDisplay.textContent = request.response; +}; + +request.send();</pre> + </li> + <li> + <p>XHR のコードを次のように置き換えます:</p> + + <pre class="brush: js notranslate">fetch(url).then(function(response) { + response.text().then(function(text) { + poemDisplay.textContent = text; + }); +});</pre> + </li> + <li> + <p>例題をブラウザに読み込むと(Web サーバから読んで下さい)、XHR 版と同様に動作するするはずです。今時のブラウザを使っていれば。</p> + </li> +</ol> + +<h4 id="Fetch_のコードでは何が起きている">Fetch のコードでは何が起きている?</h4> + +<p>まず最初に、{{domxref("WorkerOrWindowGlobalScope.fetch()","fetch()")}} メソッドが呼ばれ、取得したいリソースの URL が渡されています。これは XHR の {{domxref("XMLHttpRequest.open","request.open()")}} の今時な同等品で、さらに言えば <code>.send()</code> に相当するものは必要ありません。</p> + +<p>その後に、{{jsxref("Promise.then",".then()")}} メソッドが <code>fetch()</code> の後に連鎖されているのがわかるでしょう — このメソッドは {{jsxref("Promise","Promises")}} の一部で、非同期処理を行なうための今風な JavaScript に備わる機能です。<code>fetch()</code> はプロミスを返し、これはサーバから送られたレスポンスによって解決されます — <code>.then()</code> を使ってプロミスが解決された後にある種後始末のコードを走らせるようにし、そのコードとは内側で定義した関数にあたります。これは XHR 版の <code>onload</code> イベントハンドラに相当します。</p> + +<p>この関数には、<code>fetch()</code> のプロミスが解決された際に、自動的にサーバからのレスポンスが引数として渡されます。関数の中で、レスポンスをつかまえてその {{domxref("Body.text","text()")}} メソッド、これは基本的にレスポンスを生のテキストで返すもの、を走らせます。これは XHR 版の <code>request.responseType = 'text'</code> 部分と等価です。</p> + +<p><code>text()</code> もプロミスを返しているのがおわかりでしょう、ですのでそれに別の <code>.then()</code> を連鎖させ、その中で <code>text()</code> のプロミスが解決する生テキストを受けとるよう、関数を定義します。</p> + +<p>内側のプロミスの関数の中で、XHR 版でやったのとほとんど同じ事をやっています — {{htmlelement("pre")}} 要素のテキストコンテントにテキスト値を設定しています。</p> + +<h3 id="Aside_on_promises">Aside on promises</h3> + +<p>プロミスは初めて見るとちょっと混乱させられますが、今はひとまずそんなに心配しなくて大丈夫です。ちょっとすれば慣れます、とくに今風の JavaScript APIを学んでいけば — 新しい部分の大半がこのプロミスに強く依存しています。</p> + +<p>上の例のプロミスの構造を見直してみましょう、もうちょっと意味が通じてくるかもしれません:</p> + +<pre class="brush: js notranslate">fetch(url).then(function(response) { + response.text().then(function(text) { + poemDisplay.textContent = text; + }); +});</pre> + +<p>最初の行で言っているのは、「urlにあるリソースを取ってこい(fetch)」(<code>fetch(url)</code>)で、「それから(then)プロミスが解決したら指定した関数を実行しろ」(<code>.then(function() { ... })</code>)です。「解決」とは、「この先どこかの時点で、指定された処理の実行を終える」事を意味します。この場合だと指定された処理とは、指定のURLからリソースを取ってきて(HTTPリクエストを使って)、そのレスポンスを私たちがどうにかできるように返せ、です。</p> + +<p>実際のところ、<code>then()</code>に渡される関数は、すぐには実行されないコードの塊です — すぐにではなく、未来のどこかの時点でレスポンスが返って来た時に実行されます。頭に入れておいて下さい、プロミスは変数に保存する事もできて、変数に {{jsxref("Promise.then",".then()")}} を連鎖する事ができます。次のコードがやっているのも同じ事です:</p> + +<pre class="brush: js notranslate">let myFetch = fetch(url); + +myFetch.then(function(response) { + response.text().then(function(text) { + poemDisplay.textContent = text; + }); +});</pre> + +<p><code>fetch()</code> メソッドは HTTP レスポンスによって解決されるプロミスを返し、その後ろに連鎖された <code>.then()</code> の中にどのような関数を定義しても、それには引数としてレスポンスが自動で渡されます。引数にどんな名前を付けるのもご自由です — 下の例もちゃんと動きます:</p> + +<pre class="brush: js notranslate">fetch(url).then(function(dogBiscuits) { + dogBiscuits.text().then(function(text) { + poemDisplay.textContent = text; + }); +});</pre> + +<p>ですがパラメータにはその中身がわかる名前を付けた方がいいですよね!</p> + +<p>今度は関数だけに着目しましょう:</p> + +<pre class="brush: js notranslate">function(response) { + response.text().then(function(text) { + poemDisplay.textContent = text; + }); +}</pre> + +<p>レスポンスオブジェクトには {{domxref("Body.text","text()")}} メソッドがあって、これはレスポンスボディにある生データを受けて、プレインテキスト(これが私たちの必要とする形式です)、に変換します。このメソッドもプロミス(これは結果となるテキスト文字列で解決します)を返すので、ここでまた別の {{jsxref("Promise.then",".then()")}} を使い、この内部で、テキスト文字列を使って私たちがやりたい事を行うための別の関数を定義します。私たちがやるのは、ただ詩用の {{htmlelement("pre")}} 要素の <code><a href="/docs/Web/API/Node/textContent">textContent</a></code> プロパティをテキスト文字列と同じに設定だけなので、これはとても単純です。</p> + +<p>これも覚えておく価値があります、それぞれのブロックの結果を次のブロックに渡していくように、直接複数のプロミスブロック(<code>.then()</code>ブロック以外の種類もあります)を次から次へと連鎖する事ができます、あたかも鎖を下にたどっていくように。このおかげで、プロミスはとても強力なのです。</p> + +<p>次のブロックはもとの例題と同じ事をしますが、違うやり方で書かれています:</p> + +<pre class="brush: js notranslate">fetch(url).then(function(response) { + return response.text() +}).then(function(text) { + poemDisplay.textContent = text; +});</pre> + +<p>多くの開発者はこの書き方の方が好きです、なぜなら平らで、間違いなく長大なプロミス連鎖も読みやすいからです — それぞれのプロミスが、前のやつの内側に来る(これは扱いづらくなる場合があります)のではなく、前のやつから順々に続いています。違うのは <code><a href="/docs/Learn/JavaScript/Building_blocks/Return_values">return</a></code> 文を response.text() の前に書いて、それが出した結果を次の鎖に渡すようにしなければならないところだけです。</p> + +<h3 id="どっちの機構を使うべき">どっちの機構を使うべき?</h3> + +<p>これは本当に、あなたがどんなプロジェクトを進めているかによります。XHR は長いこと存在しているので、様々なブラウザで非常によくサポートされています。一方 Fetch とプロミスは Web プラットフォームに最近追加されたものなので、ブラウザ界では結構サポートされているんですが、IE はサポートしていません。</p> + +<p>古いブラウザをサポートする必要があるのならば、XHR の方が良いでしょう。ですがあなたがもっと先進的なプロジェクトで働いて、古いブラウザの事でさして悩まないなら、Fetch が良い選択になるでしょう。</p> + +<p>本当はどっちも学ぶべきです — Fetch は IE が消えていくにつれ(IE は、Microsoft の新しい Edge ブラウザのおかげで開発が終了しています)どんどん一般的になっていくでしょうが、もうしばらくは XHR が必要でしょう。</p> + +<h2 id="もっとややこしい例題">もっとややこしい例題</h2> + +<p>この記事のまとめとして、Fetch のより興味深い使い方を示す、ちょっとばかり難しい例題を見ていきましょう。例題用に缶詰屋というサイトを作成しました — これは缶詰だけを売る仮想のお店です。これの <a href="https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store/">GitHubでのライブ実行</a> と <a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/fetching-data/can-store">ソースコード</a> が見られます。</p> + +<p><img alt="A fake ecommerce site showing search options in the left hand column, and product search results in the right hand column." src="https://mdn.mozillademos.org/files/14779/can-store.png" style="display: block; margin: 0 auto;"></p> + +<p>デフォルトではサイトには全ての商品が表示されますが、左側のカラムにあるフォームコントロールからカテゴリから、検索語から、あるいはその両方によってフィルタリングをかけられます。</p> + +<p>商品をカテゴリや検索語によってフィルタリングする処理をし、UIでデータが正しく表示されるように文字列を操作するためなどに、けっこうな量の複雑なコードがあります。この記事のなかでそれら全てについて解説しませんが、ソースコードのコメントに詳しいことがたくさん書いてあります(<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/can-store/can-script.js">can-script.js</a>を見て下さい)。</p> + +<p>ですが、Fetch のコードについては説明していきます。</p> + +<p>Fetch を使うブロックの最初は、JavaScript の初めの方にあります:</p> + +<pre class="brush: js notranslate">fetch('products.json').then(function(response) { + return response.json(); +}).then(function(json) { + let products = json; + initialize(products); +}).catch(function(err) { + console.log('Fetch problem: ' + err.message); +});</pre> + +<p><code>fetch()</code> 関数はプロミスを返します。これが成功裏に完了すると、一つ目の <code>.then()</code> ブロックの中にある関数は、ネットワークから返された <code>response</code> を受け取ります。</p> + +<p>この関数の中で、{{domxref("Body.text","text()")}} ではなくて {{domxref("Body.json","json()")}} を実行しています。プレインテキストではなく、構造化された JSON データとしてレスポンスを返してほしいからです。</p> + +<p>次に、別の <code>.then()</code> を最初の <code>.then()</code> の後に連鎖させています。これに、<code>response.json()</code> プロミスから返された <code>json</code> を含む成功時の関数を渡しています。この <code>json</code> を <code>products</code> 変数の値として代入してから、<code>initialize(products)</code> を実行します。すべての商品をユーザーインターフェイスに表示する処理が開始されます。</p> + +<p>エラーを処理するために、連鎖の最後に <code>.catch()</code> ブロックを連鎖させています。これは、何らかの理由でプロミスが失敗した場合に実行されます。その中には、引数として渡される関数、<code>error</code> オブジェクトが含まれています。この <code>error</code> オブジェクトを使用して、発生したエラーがどういうものかを伝えられます。ここでは単純な <code>console.log()</code> を使用して伝えています。</p> + +<p>ただし、完全な Web サイトでは、ユーザの画面にメッセージを表示し、状況を改善する選択肢を提供することで、このエラーをより適切に処理するでしょう。とは言え、ここでは単純な <code>console.log()</code> 意外は必要ありません。</p> + +<p>あなたは自分でも失敗した場合のテストができます:</p> + +<ol> + <li>例題のファイルのローカルコピーを作成して下さい(<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/can-store/can-store.zip?raw=true">缶詰屋の ZIPファイル</a>をダウンロードして展開して下さい)。</li> + <li>コードを Web サーバから読んで走らせるようにします(方法は前に {{anch("Serving your example from a server")}}で解説しました)。</li> + <li>fetch するファイルのパスを、'produc.json' のようなものに変更します(誤ったファイル名にして下さい)。</li> + <li>ここでインデックスファイルをブラウザに読み込んで( <code>localhost:8000</code> から)、あなたのブラウザの開発者コンソールを見ます。次の行のようなメッセージが表示されるはずです「Network request for produc.json failed with response 404: File not found」。</li> +</ol> + +<p>二つ目の Fetch ブロックは <code>fetchBlob()</code> 関数の中にあります:</p> + +<pre class="brush: js notranslate">fetch(url).then(function(response) { + return response.blob(); +}).then(function(blob) { + // Convert the blob to an object URL — this is basically a temporary internal URL + // that points to an object stored inside the browser + let objectURL = URL.createObjectURL(blob); + // invoke showProduct + showProduct(objectURL, product); +});</pre> + +<p>これも前のとおおよそ同じように動作しますが、{{domxref("Body.json","json()")}} ではなくて {{domxref("Body.blob","blob()")}} を使っているところが違います — 今回の場合は画像ファイルを返したいので、これ用に使うデータ形式は <a href="/ja/docs/Web/API/Blob">Blob</a> — これは "<u>B</u>inary <u>L</u>arge <u>Ob</u>ject" の略で、たいていは巨大なファイルのようなオブジェクト、画像や動画のようなものを示すのに使われます。</p> + +<p>blob を成功裏に受信したら、{{domxref("URL.createObjectURL()", "createObjectURL()")}}を使ってそこからオブジェクトURLを取り出します。これはそのブラウザの中でのみ有効なオブジェクトを示す一時的な URL を返します。あまり読み易いものではありませんが、缶詰屋アプリを開いて画像を Ctrlクリックもしくは右クリックして、メニューから「画像を表示」を選択する(これはあなたが使っているブラウザによって異なる場合があります)と見ることができます。オブジェクトURLはブラウザのアドレスバーに表示され、こんな感じになるでしょう:</p> + +<pre class="notranslate">blob:http://localhost:7800/9b75250e-5279-e249-884f-d03eb1fd84f4</pre> + +<h3 id="課題_XHR_版の缶詰屋">課題: XHR 版の缶詰屋</h3> + +<p>ちょっとした練習として、アプリの Fetch 版を XHR を使うように書き換えて下さい。<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/can-store/can-store.zip?raw=true">ZIPファイル </a>のコピーを作って、上手く JavaScript を書き換えてみて下さい。</p> + +<p>ちょっとしたヒントです:</p> + +<ul> + <li>{{domxref("XMLHttpRequest")}} のリファレンス記事が役に立つでしょう。</li> + <li>基本的には、初めの方の <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/xhr-basic.html">XHR-basic.html</a> の例で見たのと同じようなパターンを使う必要があります。</li> + <li>ただし、Fetch 版の缶詰屋でお見せしたのと同様なエラー処理を追加する必要があります: + <ul> + <li><code>load</code> イベントが発火した後は、プロミスの <code>then()</code> の中ではなく、<code>request.response</code> の中にレスポンスはあります。</li> + <li>XHR において、Fetch の <code>response.ok</code> に相当する一番良いやり方は、{{domxref("XMLHttpRequest.status","request.status")}} が 200 であるか、{{domxref("XMLHttpRequest.readyState","request.readyState")}} が 4 である事をチェックする事です。</li> + <li>ステータスとステータスメッセージを取得するためのプロパティは一緒ですが、これは <code>response</code> オブジェクトの中ではなく <code>request</code>(XHR)オブジェクトの中にあります。</li> + </ul> + </li> +</ul> + +<div class="note"> +<p><strong>注記</strong>: 上手くいかないときは、我々のGitHubにある完成版のコード (<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/fetching-data/can-store-xhr/can-script.js">ソースコードはこちらから</a>、<a href="https://mdn.github.io/learning-area/javascript/apis/fetching-data/can-store-xhr/">ライブ実行版</a>もどうぞ) と比べてみて下さい。</p> +</div> + +<h2 id="まとめ">まとめ</h2> + +<p>私たちのサーバからのデータ取得に関する記事は以上です。ここまでくれば、どう XHR と Fetch を使って進めていけばいいのか理解できたことでしょう。</p> + +<h2 id="あわせて参照">あわせて参照</h2> + +<p>この記事には様々なほんのさわりしか説明していない事項がたくさんあります。これらの事項についてもっと詳しくは、以下の記事を見て下さい:</p> + +<ul> + <li><a href="/ja/docs/Web/Guide/AJAX/Getting_Started">Ajax — 始めましょう</a></li> + <li><a href="/ja/docs/Web/API/Fetch_API/Using_Fetch">Fetch を使う</a></li> + <li><a href="/ja/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promises</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Objects/JSON">JSON データの操作</a></li> + <li><a href="/ja/docs/Web/HTTP/Overview">HTTP の概要</a></li> + <li><a href="/ja/docs/Learn/Server-side">サーバサイド Web サイトプログラミング</a></li> +</ul> + +<div>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Manipulating_documents", "Learn/JavaScript/Client-side_web_APIs/Third_party_APIs", "Learn/JavaScript/Client-side_web_APIs")}}</div> + +<h2 id="このモジュール">このモジュール</h2> + +<div> +<ul> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Web API の紹介</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">ドキュメントの操作</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">サーバからのデータ取得</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">サードパーティ API</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">グラフィックの描画</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">動画と音声の API</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">クライアント側ストレージ</a></li> +</ul> +</div> diff --git a/files/ja/learn/javascript/client-side_web_apis/index.html b/files/ja/learn/javascript/client-side_web_apis/index.html new file mode 100644 index 0000000000..0675ea4da0 --- /dev/null +++ b/files/ja/learn/javascript/client-side_web_apis/index.html @@ -0,0 +1,51 @@ +--- +title: クライアントサイド Web API +slug: Learn/JavaScript/Client-side_web_APIs +tags: + - API + - CodingScripting + - DOM + - JavaScript + - Landing + - WebAPI + - グラフィック + - データ + - メディア + - モジュール + - 初心者向け + - 学習 + - 記事 +translation_of: Learn/JavaScript/Client-side_web_APIs +--- +<div>{{LearnSidebar}}</div> + +<p class="summary">Web サイトやアプリケーション用にクライアント側のJavaScriptを書いていると、すぐに<strong>アプリケーションプログラミングインターフェース </strong>(<u>A</u>pprication <u>P</u>rogramming <u>I</u>nterfaces、<strong>API</strong>) にでくわします。API とはブラウザやサイトが動作している OS の様々な面を操作したり、他の Web サイト、サービスから取得したデータを操作するためのプログラムされた機能です。このモジュールでは API とは何か、開発作業の中でよく見かける最もよく利用される API のいくつかについて、どのように使うかを説明していきます。</p> + +<h2 id="前提条件">前提条件</h2> + +<p>このモジュールをよく理解するためには、ここまでの一連のJavaScriptに関するモジュール (<a href="/en-US/docs/Learn/JavaScript/First_steps">First steps</a>, <a href="/en-US/docs/Learn/JavaScript/Building_blocks">Building blocks</a> と <a href="/en-US/docs/Learn/JavaScript/Objects">JavaScript objects</a>) の学習をすませているべきです。これらのモジュールでは大抵簡単な API を使っていますが、その助けなしにクライアント側の JavaScript を書き上げるのは難しいからです。このチュートリアルの中では、JavaScript 言語のコア部分については十分理解しているものとして、よく使われる Web API についてもう少し詳しく探っていきます。</p> + +<p><a href="/en-US/docs/Learn/HTML">HTML</a> と <a href="/en-US/docs/Learn/CSS">CSS</a> に関する基礎知識も役に立つでしょう。</p> + +<div class="note"> +<p><strong>注記</strong>: もし自分のファイルを作成できないようなデバイス上で作業しているなら、大半のコード例を <a href="http://jsbin.com/">JSBin</a> や <a href="https://thimble.mozilla.org/">Thimble</a> のようなオンラインプログラム作成・実行環境で試してみることもできます。</p> +</div> + +<h2 id="ガイド">ガイド</h2> + +<dl> + <dt><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Web API の紹介</a></dt> + <dd>まずはAPIを高い視点から見ていきます — これは何なのか、どう働くのか、あなたのコードでどう使うのか、どういう風に作られているのか? また様々なクラスのAPIが何なのか、どんな使い方があるのかも見ていきます。</dd> + <dt><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">文章の操作</a></dt> + <dd>Webページやアプリを書く場合に、最も多く必要になるのはWeb文書をどうかして操作する事でしょう。これは普通ドキュメントオブジェクトモデル(<u>D</u>ocument <u>O</u>bject <u>M</u>odel、DOM)、これはHTMLとスタイルに関する情報を{{domxref("Document")}}オブジェクトを使いまくって操作するための一連のAPIです、を用いて行ないます。この記事では、DOMの使い方を詳しく見ながら、面白い方法であなたの環境を変える事ができる興味深い他のAPIもいくつか見ていきます。</dd> + <dt><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">サーバからのデータ取得</a></dt> + <dd>また別に、モダンなWebサイトやアプリケーションでしょっちゅう必要になるのは、サーバから個々のデータを取ってきて、新しいページ全体を読んでくることなしに、ページの一部を書き換える事です。この一見ちょっとした事が、サイトのパフォーマンスや振舞いに巨大なインパクトを与えました。この記事ではそのコンセプトを解説し、これを可能にした技術{{domxref("XMLHttpRequest")}}と<a href="/ja/docs/Web/API/Fetch_API">Fetch API</a>について見ていきます。</dd> + <dt><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">サードパーティ API</a></dt> + <dd>これまでに説明したAPIはブラウザに組込まれていますが、全てのAPIが組込まれているのではありません。グーグルマップやTwitter、Facebook、ペイパルなど、多くの巨大なWebサイトやサービスが、開発者に対して彼らのデータを利用したり(例:あなたのブログにtwitterのタイムラインを表示させる)、サービスを利用したり(例:あなたのサイトに独自のグーグルマップを表示したり、あなたのサービス利用者にFacebookでログインできたり)するためのAPIを提供しています。この記事ではブラウザAPIとサードパーティAPIの違いを見ていき、典型的な後者の使い方をお見せします。</dd> + <dt><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">絵を描く</a></dt> + <dd>ブラウザにはグラフィックを描くためのとても強力なツールがいくつか組込まれています。<a href="/docs/Web/SVG">SVG</a>(Scalable Vector Graphics)言語から、HTMLの{{htmlelement("canvas")}}キャンバス要素に描画するためのAPIまで (<a href="/docs/Web/API/Canvas_API">キャンバスAPI</a> や <a href="/docs/Web/API/WebGL_API">WebGL</a>を参照)。 この記事ではキャンバスAPIへの導入を説明し、もっと深く学習していくためのリソースをご紹介します。</dd> + <dt><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">動画と音声の API</a></dt> + <dd>HTML5には文書にリッチなメディアを埋め込むための要素が備わっています — {{htmlelement("video")}} と {{htmlelement("audio")}} — それぞれに再生やシークなどの操作するための独自APIを備えています。この記事では独自の再生操作パネルを作成するような、よくある仕事をどうやればいいのかお見せします。</dd> + <dt><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">クライアント側でのデータ保存</a></dt> + <dd>モダンなブラウザには、Webサイトに関するデータを保存し必要なときに取り出すための様々に異なる技術が実装されており、これを使ってデータを長期間保存したり、サイトをオフラインに保存したりなどなどができます。この記事ではこれらがいかに動作するのか、その基本の基本について説明します。</dd> +</dl> diff --git a/files/ja/learn/javascript/client-side_web_apis/introduction/index.html b/files/ja/learn/javascript/client-side_web_apis/introduction/index.html new file mode 100644 index 0000000000..521cd6d234 --- /dev/null +++ b/files/ja/learn/javascript/client-side_web_apis/introduction/index.html @@ -0,0 +1,317 @@ +--- +title: Web API の紹介 +slug: Learn/JavaScript/Client-side_web_APIs/Introduction +tags: + - 3rd party + - API + - Article + - Beginner + - Browser + - CodingScripting + - Learn + - Object + - WebAPI + - client-side +translation_of: Learn/JavaScript/Client-side_web_APIs/Introduction +--- +<div>{{LearnSidebar}}</div> + +<div>{{NextMenu("Learn/JavaScript/Client-side_web_APIs/Manipulating_documents", "Learn/JavaScript/Client-side_web_APIs")}}</div> + +<p class="summary">まずはAPIを高い視点から見ていきます — これは何なのか、どう働くのか、あなたのコードでどう使うのか、どういう風に作られているのか? また様々なクラスのAPIは何なのか、どのような使い方があるのかも見ていきます。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>基本的なコンピュータの知識および利用能力、<a href="/en-US/docs/Learn/HTML">HTML</a> と <a href="/en-US/docs/Learn/CSS">CSS</a> の基本的な理解、JavaScript の基本 (<a href="/docs/Learn/JavaScript/First_steps">第一歩</a>、<a href="/docs/Learn/JavaScript/Building_blocks">構成要素</a>, <a href="/docs/Learn/JavaScript/Objects">JavaScriptオブジェクト</a>).</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>API に何ができて、あなたのコードでどう使えばいいのか知ること。</td> + </tr> + </tbody> +</table> + +<h2 id="API_って何">API って何?</h2> + +<p>Application Programming Interfaces (APIs) は、開発者が複雑な機能をより簡単に作成できるよう、プログラミング言語から提供される構造です。複雑なコードを抽象化し、それにかわる簡潔な構文を提供します。</p> + +<p>実世界の例として、あなたの家、アパートや他の住処にある電気のコンセントについて考えて下さい。あなたの家で機器を使いたい時には、電源コードのプラグをコンセントに差し込めば事足ります。電源に直接結線したりしないでしょう — そんなのは非効率ですし、あなたが電気工事士でなければ、やってみるには難しいし危険です。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14317/plug-socket.png" style="display: block; height: 472px; margin: 0px auto; width: 700px;"></p> + +<p><em>画像提供: <a href="https://www.flickr.com/photos/easy-pics/9518184890/in/photostream/lightbox/">超タコ足コンセント</a> by <a href="https://www.flickr.com/photos/easy-pics/">The Clear Communication People</a>, Flickr より</em></p> + +<p>それと同じことで、そうですね、例えば3次元グラフィックのプログラムを JavaScript や Python のような高レベル言語で書かれた API を使ってやる方が、C や C++ のような低レベル言語から直接コンピュータの GPU やグラフィック機能を叩いてやるよりも、ずっと簡単です。</p> + +<div class="note"> +<p><strong>注記</strong>: API という語についてもっと詳しいことは <a href="/ja/docs/Glossary/API">APIの用語解説</a> を参照して下さい。</p> +</div> + +<h3 id="クライアントサイド_JavaScript_での_API">クライアントサイド JavaScript での API</h3> + +<p>クライアントサイド API では、実際非常にたくさんのAPIが使えます — それらは JavaScript 言語本体の一部ではなく、あなたにスーパーパワーを与えるべく JavaScript 言語のコアの上に築かれた代物です。それらはおおよそ二つのカテゴリに分けられます:</p> + +<ul> + <li><strong>ブラウザ API</strong> は Web ブラウザに組込まれていて、ブラウザやコンピュータの環境の情報を取得し、これを使って役に立つややこしい事を行えるようにするものです。 例えば <a href="/en-US/docs/Web/API/Geolocation/Using_geolocation">Geolocation API</a> は位置情報を取得するための簡単な JavaScript 構造を提供するので、例えばグーグルマップにあなたの居場所を表示するような事ができます。裏で実際にはブラウザは低レベル (例えば C++) の複雑なコードをいくつか使ってデバイスの GPS 機器 (あるいは位置情報を得られる他のなんだか) と通信し、位置情報を取得し、コードから利用できるようにブラウザ環境に情報を戻しています。ですがここでもこの複雑な事柄は API で抽象化され隠蔽されます。</li> + <li><strong>サードパーティ API</strong> はデフォルトではブラウザに組込まれておらず、普通はコードと情報を Web のどこから読み込まねばなりません。例えば <a href="https://dev.twitter.com/overview/documentation">Twitter API</a> を使えばあなたの Web サイトにあなたの最新のツイートを表示するような事が可能になります。Twitter API は、Twitter サービスに特定の情報を要求したりするのに使える特別な構造のかたまりを提供します。</li> +</ul> + + + + + +<p><img alt="" src="https://mdn.mozillademos.org/files/13508/browser.png" style="display: block; height: 511px; margin: 0px auto; width: 815px;"></p> + + + +<h3 id="JavaScript_と_API_とその他_JavaScript_ツールの関係">JavaScript と API とその他 JavaScript ツールの関係</h3> + +<p>ここまででクライアントサイド API とは何か、JavaScript 言語とどう関係しているのかお話しました。もっとはっきりさせるために一度おさらいして、ついでに他の JavaScript ツールがどう関係してくるのかもお話しましょう:</p> + +<ul> + <li>JavaScript — ブラウザに組込まれた高レベルスクリプト言語で、Web ページやアプリに機能を実装するのに使えます。<a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Introduction">Node</a> のようなブラウザ以外の他のプログラミング環境でも使えるのは覚えておいて下さい。</li> + <li>ブラウザ API — ブラウザに組込みの JavaScript 言語の上にある構造で、何かの機能をもっと簡単に実装できるようにします。</li> + <li>サードパーティ API — サードパーティのプラットフォーム (Twitter や Facebook) 上に作られた構造で、それらのプラットフォームの機能を Web ページで利用できるようにします (例えばあなたの最新のツイートをあなたの Web ページに表示する)。</li> + <li>JavaScript ライブラリ — 多くは、<a href="/en-US/docs/Learn/JavaScript/Building_blocks/Functions#Custom_functions">独自の関数</a> を含んだ一つか複数の JavaScript ファイルで、Web ページにくっつけることでスピードアップしたり共通の機能を書いたりできるものです。例えば、jQuery、Mootools や React がなどが含まれます。</li> + <li>JavaScript フレームワーク — ライブラリの一階層上にあたり、JavaScript フレームワーク (例えば Angular や Ember) は HTML や CSS に JavaScript、インストールして一から Web アプリケーションを作成するのに使えるその他もろもろの技術がパッケージ化されている場合が多いです。ライブラリとフレームワークの大きな相違点は、「制御の逆転 (Inversion of Control)」にあります。ライブラリのメソッドを呼ぶ時には、開発者がコントロールしています。フレームワークでは、コントロールが逆転します: フレームワークから開発者のコードが呼ばれるのです。</li> +</ul> + +<h2 id="API_で何ができる">API で何ができる?</h2> + +<p>モダンなブラウザではすごい数の API を利用できるので、コードからとてもいろいろな事ができます。 <a href="/ja/docs/Web/API">MDN API 索引</a>を見てみればわかると思います。</p> + +<h3 id="一般的なブラウザ_API">一般的なブラウザ API</h3> + +<p>特に、あなたが使うであろう最も一般的なブラウザ API のカテゴリ (このモジュールでとても詳しい所まで網羅していきます) は:</p> + +<ul> + <li>ブラウザで読み込んだ<strong>文書を操作するための API</strong>。一番目にする例は <a href="/ja/docs/Web/API/Document_Object_Model">DOM (Document Object Model) API</a> で、 HTML と CSS を操作できます — HTML を作成したり削除したり書き換えたり、動的に新しいスタイルをページに適用したり、などなど。例えばページにポップアップウィンドウが表われたり、何か新しい中身が表示されたりする時、DOM が使われています。この種の API については<a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents#The_document_object_model">ドキュメントの操作</a>でもっといろいろ見られます。</li> + <li><strong>サーバからデータ取得をする API</strong> で Web ページの一部を書き換える事はとてもよく行なわれます。この一見ちょっとした事が、サイトのパフォーマンスや振舞いに巨大なインパクトを与えました — 在庫一覧や新しいお話一覧を書き換えたい時に、サーバからページ全体をリロードする事なしにさくっとできたら、サイトやアプリはずっと反応よく素早く感じられます。これを可能にした API には <a href="https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest" title="XMLHttpRequest is an API that provides client functionality for transferring data between a client and a server. It provides an easy way to retrieve data from a URL without having to do a full page refresh. This enables a Web page to update just a part of the page without disrupting what the user is doing."><code>XMLHttpRequest</code></a> と <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a> が含まれています。<strong>Ajax</strong> という言葉を聞いた事があるかもしれませんが、これがこのテクニックの呼び名です。これらの API について <a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">サーバからのデータ取得</a>でもっといろいろ見られます。</li> + <li><strong>グラフィックスを描画したり操作する API</strong> は多くのブラウザがサポートしています — 最も知られているものには<a href="/docs/Web/API/Canvas_API"> Canvas</a> と <a href="/docs/Web/API/WebGL_API">WebGL</a> があり、HTML の{{htmlelement("canvas")}} 要素上にあるピクセルデータを書き換えて2次元や3次元のシーンを作成するのに使えます。例えばキャンバスAPIを使って長方形や円のような形を描いたり、キャンバスに画像を読み込んだり、セピアやグレイスケールといったフィルターを適用したり、あるいは WebGL を使ってライティングやテクスチャを使った3Dシーンを作成したりできます。これらの API はよくアニメーションループを作成するAPI({{domxref("window.requestAnimationFrame()")}} など)や他のものと組み合わせて使われ、アニメやゲームのようなものの表示を定期的に書き換えるようにします。</li> + <li><strong><a href="https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery">動画と音声の API</a></strong> {{domxref("HTMLMediaElement")}}や <a href="/docs/Web/API/Web_Audio_API">Web Audio API</a> や <a href="/docs/Web/API/WebRTC_API">WebRTC</a> のような API を使うと、 マルチメディアを使ってとても面白い事ができます。音声や動画再生のための独自のコントロールUIの作成、字幕やサブタイトルのような音声トラックをビデオと一緒に表示したり、Web カメラの画像を取り込んで操作し、上述のキャンバスに表示したり Web カンファレンスに参加している他の誰かのコンピュータ上に表示したり、音声トラックにイフェクト(ゲイン、ディストーション、音場効果など)をかけたりできます。</li> + <li><strong>デバイス API</strong> は基本的に Web アプリで使えるような形で、今時のハードウェアデバイスのデータを操作したり取得する API です。デバイスの位置データにアクセスして地図上にあなたの居場所を書くような位置情報 API についてはすでにお話しました。他の例にはシステム通知を使って Web アプリに役に立つアップデートがあるのを知らせたり(<a href="/docs/Web/API/Notifications_API">Notifications API</a> を参照)、ハードウェアを振動させたり(<a href="/docs/Web/API/Vibration_API">Vibration API</a> を参照)などがあります。</li> + <li><strong>クライアント側でのデータ保持 API </strong>は今多くのブラウザに普及しつつあります。— クライアント側にデータを保存できると、ページを移動しても状態を保存したり、たとえデバイスがオフラインでも動作するようなアプリを作成したいような場合、とても役に立ちます。いくつもの選択肢があり、例えば <a href="/docs/Web/API/Web_Storage_API">Web Storage API</a> を使ったキーバリューストアや、 <a href="/docs/Web/API/IndexedDB_API">IndexedDB API</a> を使ったもっと複雑なテーブル型データ保存などです。</li> +</ul> + +<h3 id="一般的なサードパーティAPI">一般的なサードパーティAPI</h3> + +<p>サードパーティ API はバラエティーに富んでいます。あなたが遅かれ早かれ使うようになりそうな、世間でよく使われているものには以下のようなものがあります:</p> + +<ul> + <li><a href="https://dev.twitter.com/overview/documentation">Twitter API</a>、あなたの最新のツイートをあなたの Web サイトに表示したりするような事に使えます。</li> + <li><a href="https://developer.mapquest.com/">Mapquest</a> や <a href="https://developers.google.com/maps/">Google Maps API</a> のような地図の API は、あなたのWebページ上に地図を使ったあらゆる事を可能にします。</li> + <li><a href="https://developers.facebook.com/docs/">Facebook APIスイート</a>によって Facebook エコシステムの様々な部品を使ってあなたのアプリを強化できます。例えばアプリへのログインを Facebook のログインで行なったり、アプリ内での支払い、ターゲット広告を出したりなどです。</li> + <li><a href="https://core.telegram.org/api">Telegram APIs</a> を使用すると、ボットのサポートに加えて、Telegram チャネルのコンテンツを Web サイトに埋め込むことができます。</li> + <li><a href="https://developers.google.com/youtube/">YouTube API</a>を使ってあなたのサイトに YouTube のビデオを埋め込んだり、YouTube を検索したり、プレイリストを作成したりなどなどできます。</li> + <li><a href="https://developers.pinterest.com/">Pinterest API</a> は、Pinterest のボードとピンを管理して Web サイトに含めるためのツールを提供します。</li> + <li><a href="https://www.twilio.com/">Twilio API</a>はあなたのアプリで音声・ビデオ電話の機能を作成したり、SMS/MMSを送信したりなどするためのフレームワークを提供します。</li> + <li><a href="https://docs.joinmastodon.org/api/">Mastodon API</a> を使用すると、Mastodon ソーシャルネットワークの機能をプログラムで操作できます。</li> +</ul> + +<div class="note"> +<p><strong>注記</strong>: サードパーティAPIについては <a href="http://www.programmableweb.com/category/all/apis">Programmable Web API directory</a>でもっと多くの情報を見られます。</p> +</div> + +<h2 id="APIはどのように動作する">APIはどのように動作する?</h2> + +<p>異なるJavaScript APIはそれぞれに違う方法で動作しますが、普通は、共通した機能とどのように動くべきかの類似したテーマを持ちます。</p> + +<h3 id="オブジェクトに基づいています">オブジェクトに基づいています</h3> + +<p>あなたのコードは一つ以上の <a href="/docs/Learn/JavaScript/Objects">JavaScript オブジェクト</a>を通じて API とやりとりし、オブジェクトは API が使用するデータ (オブジェクトのプロパティとして持つ) や API が提供する機能(オブジェクトメソッドとして持つ) の容れ物として使われます。</p> + +<div class="note"> +<p><strong>注記</strong>: もしまだオブジェクトがどのように動作するかについて理解があやふやなら、先に進む前に <a href="/docs/Learn/JavaScript/Objects">JavaScript オブジェクト</a> モジュールを読みなおし、練習するのをおすすめします。</p> +</div> + +<p>Let's return to the example of the Web Audio API — this is a fairly complex API, which consists of a number of objects. The most obvious ones are:</p> + +<ul> + <li>{{domxref("AudioContext")}}, which represents an <a href="/en-US/docs/Web/API/Web_Audio_API/Basic_concepts_behind_Web_Audio_API#Audio_graphs">audio graph</a> that can be used to manipulate audio playing inside the browser, and has a number of methods and properties available to manipulate that audio.</li> + <li>{{domxref("MediaElementAudioSourceNode")}}, which represents an {{htmlelement("audio")}} element containing sound you want to play and manipulate inside the audio context.</li> + <li>{{domxref("AudioDestinationNode")}}, which represents the destination of the audio, i.e. the device on your computer that will actually output it — usually your speakers or headphones.</li> +</ul> + +<p>So how do these objects interact? If you look at our <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/introduction/web-audio/index.html">simple web audio example</a> (<a href="https://mdn.github.io/learning-area/javascript/apis/introduction/web-audio/">see it live also</a>), you'll first see the following HTML:</p> + +<pre class="brush: html notranslate"><audio src="outfoxing.mp3"></audio> + +<button class="paused">Play</button> +<br> +<input type="range" min="0" max="1" step="0.01" value="1" class="volume"></pre> + +<p>We, first of all, include an <code><audio></code> element with which we embed an MP3 into the page. We don't include any default browser controls. Next, we include a {{htmlelement("button")}} that we'll use to play and stop the music, and an {{htmlelement("input")}} element of type range, which we'll use to adjust the volume of the track while it's playing.</p> + +<p>Next, let's look at the JavaScript for this example.</p> + +<p>We start by creating an <code>AudioContext</code> instance inside which to manipulate our track:</p> + +<pre class="brush: js notranslate">const AudioContext = window.AudioContext || window.webkitAudioContext; +const audioCtx = new AudioContext();</pre> + +<p>Next, we create constants that store references to our <code><audio></code>, <code><button></code>, and <code><input></code> elements, and use the {{domxref("AudioContext.createMediaElementSource()")}} method to create a <code>MediaElementAudioSourceNode</code> representing the source of our audio — the <code><audio></code> element will be played from:</p> + +<pre class="brush: js notranslate">const audioElement = document.querySelector('audio'); +const playBtn = document.querySelector('button'); +const volumeSlider = document.querySelector('.volume'); + +const audioSource = audioCtx.createMediaElementSource(audioElement);</pre> + +<p>Next up we include a couple of event handlers that serve to toggle between play and pause when the button is pressed and reset the display back to the beginning when the song has finished playing:</p> + +<pre class="brush: js notranslate">// play/pause audio +playBtn.addEventListener('click', function() { + // check if context is in suspended state (autoplay policy) + if (audioCtx.state === 'suspended') { + audioCtx.resume(); + } + + // if track is stopped, play it + if (this.getAttribute('class') === 'paused') { + audioElement.play(); + this.setAttribute('class', 'playing'); + this.textContent = 'Pause' + // if track is playing, stop it +} else if (this.getAttribute('class') === 'playing') { + audioElement.pause(); + this.setAttribute('class', 'paused'); + this.textContent = 'Play'; + } +}); + +// if track ends +audioElement.addEventListener('ended', function() { + playBtn.setAttribute('class', 'paused'); + playBtn.textContent = 'Play'; +});</pre> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: Some of you may notice that the <code>play()</code> and <code>pause()</code> methods being used to play and pause the track are not part of the Web Audio API; they are part of the {{domxref("HTMLMediaElement")}} API, which is different but closely-related.</p> +</div> + +<p>Next, we create a {{domxref("GainNode")}} object using the {{domxref("AudioContext.createGain()")}} method, which can be used to adjust the volume of audio fed through it, and create another event handler that changes the value of the audio graph's gain (volume) whenever the slider value is changed:</p> + +<pre class="brush: js notranslate">const gainNode = audioCtx.createGain(); + +volumeSlider.addEventListener('input', function() { + gainNode.gain.value = this.value; +});</pre> + +<p>The final thing to do to get this to work is to connect the different nodes in the audio graph up, which is done using the {{domxref("AudioNode.connect()")}} method available on every node type:</p> + +<pre class="brush: js notranslate">audioSource.connect(gainNode).connect(audioCtx.destination);</pre> + +<p>The audio starts in the source, which is then connected to the gain node so the audio's volume can be adjusted. The gain node is then connected to the destination node so the sound can be played on your computer (the {{domxref("AudioContext.destination")}} property represents whatever is the default {{domxref("AudioDestinationNode")}} available on your computer's hardware, e.g. your speakers).</p> + +<h3 id="認識できる入口があります">認識できる入口があります</h3> + +<p>APIを使うときは、その API の入口がどこなのかしっかり確認するべきです。Web Audio APIではとても単純でした — それは {{domxref("AudioContext")}} オブジェクトであり、あらゆる音声操作を行うために使用する必要があります。</p> + +<p>Document Object Model (DOM) API でも単純な入口があります — これの機能は{{domxref("Document")}} もしくは何らかの方法で影響を与えたい いHTML 要素のインスタンスにぶらさがっている場合が多く、例えば:</p> + +<pre class="brush: js notranslate">const em = document.createElement('em'); // create a new em element +const para = document.querySelector('p'); // reference an existing p element +em.textContent = 'Hello there!'; // give em some text content +para.appendChild(em); // embed em inside para</pre> + +<p><a href="/docs/Web/API/Canvas_API">Canvas API</a> は、諸々を操作するために使用するコンテキストオブジェクトの取得にも依存していますが、この場合は、音声コンテキストではなく描画コンテキストです。そのコンテキストオブジェクトは、描画をしたい {{htmlelement("canvas")}} 要素への参照を取得して、 これの{{domxref("HTMLCanvasElement.getContext()")}} メソッドを呼ぶと作成されます:</p> + +<pre class="brush: js notranslate">const canvas = document.querySelector('canvas'); +const ctx = canvas.getContext('2d');</pre> + +<p>キャンバスを使って何かやろうとする場合は何でも、コンテキストオブジェクト (これは{{domxref("CanvasRenderingContext2D")}} のインスタンスです) のプロパティやメソッドを呼んで行ないます。例えば:</p> + +<pre class="brush: js notranslate">Ball.prototype.draw = function() { + ctx.beginPath(); + ctx.fillStyle = this.color; + ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI); + ctx.fill(); +};</pre> + +<div class="note"> +<p><strong>注記</strong>: この実例を<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/introduction/bouncing-balls.html">弾むボールのデモ</a> (<a href="http://mdn.github.io/learning-area/javascript/apis/introduction/bouncing-balls.html">ライブ実行</a> も見てね)で見られます。</p> +</div> + +<h3 id="状態の変化を捉えるのにイベントを使います">状態の変化を捉えるのにイベントを使います</h3> + +<p>すでに学習コース中でイベントについてはお話しています、<a href="/ja/docs/Learn/JavaScript/Building_blocks/Events">イベントの紹介</a> — この記事でクライアント側 Web イベントとは何か、コードの中でどのように使えるのか詳しく見てきました。もしまだクライアント側 WebAPI の仕組みがよくわからいなら、この先に進む前に記事を読み直しておく方が良いでしょう。</p> + +<p>イベントを持たないWebAPIもありますが、ほとんどの WebAPI はいくつか持っています。イベントが発火した際に関数を実行できるイベントハンドラーのプロパティについては、リファレンス記事の独立した"イベントハンドラー"セクションとしておおよそ列挙されています。</p> + +<p class="simple-translate-result" style="color: rgb(0, 0, 0);">上記の Web Audio API の例では、すでにいくつかのイベントハンドラーが使用されています。</p> + +<p>別の例として、<code><a href="/en-US/docs/Web/API/XMLHttpRequest">XMLHttpRequest</a></code> オブジェクトのインスタンス (一つ一つがサーバから何らかの新しいリソースを取得しようとするHTTPリクエストを表わします) にはとてもたくさんのイベントが付随しており、たとえば <code>load</code> イベントは発火したリソースに対する正常なレスポンスが返ってきて、それが使えるようになった時点で発火します。</p> + +<p>次のコードはこれをどう使うのか示す簡単な例です:</p> + +<pre class="brush: js notranslate">let requestURL = 'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json'; +let request = new XMLHttpRequest(); +request.open('GET', requestURL); +request.responseType = 'json'; +request.send(); + +request.onload = function() { + const superHeroes = request.response; + populateHeader(superHeroes); + showHeroes(superHeroes); +}</pre> + +<div class="note"> +<p><strong>注記</strong>: <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/introduction/ajax.html">ajax.html</a> でこの例の動作を見られます(<a href="http://mdn.github.io/learning-area/javascript/apis/introduction/ajax.html">ライブ実行版</a>もどうぞ)。</p> +</div> + +<p>最初の 5 行で取得したいリソースを指定し、<code>XMLHttpRequest()</code> コンストラクタを使って新しいリクエストオブジェクトを生成し、指定のリソースを取得するために <code>GET</code> リクエストを作り、レスポンスを JSON 形式として吐き出すよう指定、そしてリクエストを送信します。</p> + +<p><code>onload</code> ハンドラー関数で私たちがレスポンスに対して何を行なうかを指定します。load イベントが発火した後には、レスポンスが正常に得られて利用できるようになっている (エラーは起きていない) とわかっていますので、JSON であるレスポンスを <code>superHeroes</code> 変数に保存し、以降の処理のために 2 つの異なる関数に引き渡しています。</p> + +<h3 id="必要なところには追加のセキュリティ機構があります">必要なところには追加のセキュリティ機構があります</h3> + +<p>WebAPI 機能は JavaScript や他の Web 技術と同等のセキュリティ上の配慮が必要です (例えば <a href="/docs/Web/Security/Same-origin_policy">same-origin ポリシー</a>) が、追加のセキュリティ機構が必要な場合もあります。例として今時の WebAPI の中に はHTTPS で配信されるページ上でしか動かないものがあり、これは機密とすべきデータをやりとりする可能性があるためです (<a href="/docs/Web/API/Service_Worker_API">ServiceWorkers</a> や <a href="/docs/Web/API/Push_API">Push</a> など)。</p> + +<p>さらには、ある種のWebAPIへの呼び出しがあなたのコードにあると、ユーザに対してそれの許可を要求します。例えば、<a href="/docs/Web/API/Notifications_API">Notifications API (通知 API)</a> はポップアップのダイアログボックスを用いて許可を要求します:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14315/notification-permission.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<p>Web Audio および {{domxref("HTMLMediaElement")}} API には、<a href="/docs/Web/API/Web_Audio_API/Best_practices#Autoplay_policy">自動再生 (autoplay) ポリシー</a> と呼ばれるセキュリティ機構が適用されます。これは、基本的に、ページの読み込み時に音声を自動的に再生できないことを意味します。ユーザーに次のことを許可する必要があります。ボタンのようなコントロールを介して音声再生を開始します。これは、音声の自動再生は通常非常に煩わしいものであり、ユーザーにそれを課すべきではないためです。</p> + +<div class="blockIndicator note"> +<p><strong>注記</strong>: ブラウザーの厳格さによっては、このようなセキュリティ機構により、例がローカルで機能しなくなる場合があります。つまり、ローカルの例のファイルをウェブサーバーから実行するのではなく、ブラウザーに読み込んだ場合です。執筆時点では、Web Audio API の例はローカルでは Google Chrome で動作しません。動作する前に、GitHub にアップロードする必要がありました。</p> +</div> + +<h2 id="まとめ">まとめ</h2> + +<p>ここまで来れば、API とは何か、どう動くのか、あなたのJavaScript コードからどんな事ができるのかよくわかったと思います。何か API を使って楽しいことをやりたくってしょうがなくなってることと思いますので、さあ始めましょう! 次から、<u>D</u>ocument <u>O</u>bject <u>M</u>odel (DOM) を使った文書の操作を見ていきます。</p> + +<p>{{NextMenu("Learn/JavaScript/Client-side_web_APIs/Manipulating_documents", "Learn/JavaScript/Client-side_web_APIs")}}</p> + +<h2 id="このモジュール">このモジュール</h2> + +<ul> + <li><a href="/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Web API の紹介</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">ドキュメントの操作</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">サーバからのデータ取得</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">サードパーティ API</a></li> + <li><a href="/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">グラフィックの描画</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">動画と音声の API</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">クライアント側ストレージ</a></li> +</ul> + +<div id="simple-translate"> +<div> +<div class="simple-translate-button isShow" style="height: 22px; width: 22px; top: 11122px; left: 566px;"></div> + +<div class="simple-translate-panel " style="width: 300px; height: 200px; top: 0px; left: 0px; font-size: 13px; background-color: rgb(255, 255, 255);"> +<div class="simple-translate-result-wrapper" style="overflow: hidden;"> +<div class="simple-translate-move"></div> + +<div class="simple-translate-result-contents"> +<p class="simple-translate-result" style="color: rgb(0, 0, 0);"></p> + +<p class="simple-translate-candidate" style="color: rgb(115, 115, 115);"></p> +</div> +</div> +</div> +</div> +</div> diff --git a/files/ja/learn/javascript/client-side_web_apis/manipulating_documents/index.html b/files/ja/learn/javascript/client-side_web_apis/manipulating_documents/index.html new file mode 100644 index 0000000000..b0c69f9d62 --- /dev/null +++ b/files/ja/learn/javascript/client-side_web_apis/manipulating_documents/index.html @@ -0,0 +1,349 @@ +--- +title: ドキュメントの操作 +slug: Learn/JavaScript/Client-side_web_APIs/Manipulating_documents +tags: + - API + - Article + - Beginner + - CodingScripting + - DOM + - Document + - Document Object Model + - JavaScript + - Learn + - Navigator + - WebAPI + - Window +translation_of: Learn/JavaScript/Client-side_web_APIs/Manipulating_documents +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Introduction", "Learn/JavaScript/Client-side_web_APIs/Fetching_data", "Learn/JavaScript/Client-side_web_APIs")}}</div> + +<p class="summary">ウェブページやアプリを書く場合に、最も多く必要になるのはウェブ文書をどうかして操作する事でしょう。これは普通ドキュメントオブジェクトモデル (Document Object Model、DOM) によって為され、DOM は HTML とスタイルに関する情報を {{domxref("Document")}} オブジェクトを多用して操作する一連の API です。この記事では、DOM の使い方を詳しく見ながら、面白い方法であなたの環境を変える事ができる興味深い他の API もいくつか見ていきます。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>基本的なコンピュータに関する知識と理解、HTML と CSS、JavaScript—JavaScript のオブジェクトについても—基本を理解していること</td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>DOM API の核と、DOM と共によく利用される API、ドキュメントの操作について詳しくなること</td> + </tr> + </tbody> +</table> + +<h2 id="ウェブブラウザーの重要なパーツ">ウェブブラウザーの重要なパーツ</h2> + +<p>ウェブブラウザーはとてもたくさんの動いている部品からなるソフトウェアの複雑な集合体で、部品の多くはウェブ開発者の JavaScript からでは制御したり操作することはできません。こんな制約はよろしくないと思う方もいるかもしれませんが、ブラウザが保護されているのには十分な理由があって、これは主にセキュリティ関係のためです。もしあるウェブサイトがあなたが保存しているパスワードやその他の秘密情報にアクセスできて、あなたのふりをして他のサイトにログインできたらどうですか?</p> + +<p>制限はあっても、ウェブ API は、ウェブページ上でいろいろ素敵な事をできるように、たくさんの機能を提供してくれます。あなたのコードからよく参照するであろう目に見える代物はほんのわずかです — 下の図を見て下さい、この図はウェブページの表示に直接関与しているブラウザーの主要なパーツを表わしています:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14557/document-window-navigator.png" style="display: block; margin: 0 auto;"></p> + +<ul> + <li>ウィンドウはウェブページが読み込まれる部分の回りのブラウザーの枠です。これは JavaScript では {{domxref("Window")}} オブジェクトで表わされます。このオブジェクトに備わるメソッドを使って、ウィンドウの大きさを調べたり ({{domxref("Window.innerWidth")}} と {{domxref("Window.innerHeight")}} を参照)、ウィンドウに読み込まれる文書を操作したり、その文書に関係するデータをクライアント側(例えばローカルデータベースや他のデータ保存機構)で保存したり、現在のウィンドウに対して<a href="/docs/Learn/JavaScript/Building_blocks/Events#A_series_of_fortunate_events">イベントハンドラー</a> を追加したり、などできます。</li> + <li>ナビゲータはブラウザーの状態やウェブで使われているようなブラウザーの身元(つまりユーザーエージェント)を表わします。JavaScript では {{domxref("Navigator")}} オブジェクトで表わされます。このオブジェクトを使って、位置情報、ユーザが好む言語、ユーザのウェブカムからの録画データ、などを取得できます。</li> + <li>ドキュメント(ブラウザーでは DOM として表現されます)はウィンドウに実際に読み込まれているページのことで、JavaScript では {{domxref("Document")}} オブジェクトで表わされます。このオブジェクトを使って文書を構成する HTML と CSS 上の情報を調べたり操作したりできて、例えば DOM の中のある要素に対する参照を得たり、その中身のテキストを変更したり、新しいスタイルを適用したり、新しい要素を作成して現在の要素の子に追加したり、一緒くたに削除したりできます。</li> +</ul> + +<p>この記事では主にドキュメントの操作に着目しますが、それ以外の役に立つこともちょっとお見せしていきます。</p> + +<h2 id="ドキュメントオブジェクトモデル">ドキュメントオブジェクトモデル</h2> + +<p>あなたのブラウザーの一つ一つのタブに今読み込まれているドキュメントは、ドキュメントオブジェクトモデルとして表現されます。これは HTML の構造に対してプログラム言語から簡単にアクセスできるようにブラウザーが作成する、"木構造"による表現です — 例えば、ページをレンダリングする際にはブラウザー自体がスタイルや他の情報を適切な要素に適用するために DOM を使い、ページのレンダリングが終わった後にはあなたのような開発者が JavaScript を使って DOM を操作できます。</p> + +<p><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/dom-example.html">dom-example.html</a> にちょっとした例を作成しました(<a href="http://mdn.github.io/learning-area/javascript/apis/document-manipulation/dom-example.html">ライブ実行</a>もどうぞ)。ブラウザーから開いてみてください — これはとても簡素なページで、{{htmlelement("section")}} 要素の中に画像が一つと、一つのリンクを含む一つのパラグラフがあります。HTML のソースはこんな感じです:</p> + +<pre class="brush: html notranslate"><!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <title>Simple DOM example</title> + </head> + <body> + <section> + <img src="dinosaur.png" alt="A red Tyrannosaurus Rex: A two legged dinosaur standing upright like a human, with small arms, and a large head with lots of sharp teeth."> + <p>Here we will add a link to the <a href="https://www.mozilla.org/">Mozilla homepage</a></p> + </section> + </body> +</html></pre> + +<p>一方これの DOM はこんな具合になります:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14559/dom-screenshot.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<div class="note"> +<p><strong>注記</strong>: この DOM ツリーの図は Ian Hickson の <a href="https://software.hixie.ch/utilities/js/live-dom-viewer/">Live DOM viewer</a> を使って作成しました。</p> +</div> + +<p>これを見ると、それぞれのドキュメント内の要素とちょっとばかりのテキストそれぞれが、ツリーの中でそれ自身のエントリーがあるのがわかるでしょう — これら一つ一つを<strong>ノード</strong>と呼びます。またノードの種類を示す語や、ノードそれぞれの関係によりツリーでの位置があるのがわかるでしょう:</p> + +<ul> + <li><strong>エレメント(要素)ノード</strong>: DOM の中での HTML 要素です。</li> + <li><strong>ルート(根)ノード</strong>: 木の頂点のノードで、HTML の場合であれば常に <code>HTML</code> ノードになります。(SVG や独自の XML といった他のマークアップ言語の方言では異なるルート要素の場合があります)</li> + <li><strong>子ノード</strong>: 他のノードに<em>直結して</em>含まれるノードです。上の例だと、例えば <code>IMG</code> は <code>SECTION</code> の子ノードとなります。</li> + <li><strong>子孫ノード</strong>: 他のノードに<em>どのような形であれ</em>含まれるノードです。上の例だと、例えば <code>IMG</code> は <code>SECTION</code> の子ノードであり、子孫ノードでもあります。<code>IMG</code> は <code>BODY</code> の二段階内側にあるので <code>BODY</code> の子ノードではありませんが、<code>BODY</code> の子孫ノードではあります。</li> + <li><strong>親ノード</strong>: その中に他のノードを持つノードです。例えば上の例だと <code>BODY</code> は <code>SECTION</code> ノードの親ノードになります。</li> + <li><strong>兄弟ノード</strong>: DOM ツリーの同じ階層にあるノードです。上の例だと <code>IMG</code> と <code>P</code> は兄弟ノードになります。</li> + <li><strong>テキストノード</strong>: テキスト文字列を含むノードです。</li> +</ul> + +<p>これからコードを見ていくとこういう語が頻出するので、DOM を使い始める前に、これらの用語をしっかり覚えておくと良いでしょう。CSS の勉強をしているときも、これらの語をみかけることでしょう(子孫セレクター、子セレクターとか)。</p> + +<h2 id="実践学習_基本的なDOM操作">実践学習: 基本的なDOM操作</h2> + +<p>DOM 操作の学習スタートは、実践的な例から始めましょう。</p> + +<ol> + <li><a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/dom-example.html">dom-example.html</a> と <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/dinosaur.png">image</a> のローカルコピーを一緒に作成して下さい。</li> + <li><code><script></script></code> 要素を、閉じ<code></body></code>タグのすぐ上に追加して下さい。</li> + <li>DOM の中の要素を操作するため、まず DOM を選びだしてこれへの参照を変数に保存する必要があります。script 要素の中に、次の行を追加して下さい: + <pre class="brush: js notranslate">const link = document.querySelector('a');</pre> + </li> + <li>要素への参照を変数に保存したので、これが備えているプロパティとメソッドを使って DOM の操作を始められます (利用できるプロパティとメソッドは、たとえば {{htmlelement("a")}} 要素であれば {{domxref("HTMLAnchorElement")}} インターフェース、さらにその汎化した親のインターフェース {{domxref("HTMLElement")}} や {{domxref("Node")}} — これは DOM の全てノードが相当します — で定義されています)。まずは、リンクの中のテキストを、{{domxref("Node.textContent")}} プロパティを更新する事で変更してみましょう。上で書いた行の下に、次の行を追加して下さい: + <pre class="brush: js notranslate">link.textContent = 'Mozilla Developer Network';</pre> + </li> + <li>クリックされたときに変な場所に行かないよう、リンクが指す先の URL も変えておくべきでしょう。また下に、以下の行を追加して下さい: + <pre class="brush: js notranslate">link.href = 'https://developer.mozilla.org';</pre> + </li> +</ol> + +<div> +<p>JavaScript あるあるですが、要素を選んで変数に保存する方法にはいろんなやり方があることを頭に入れておいて下さい。{{domxref("Document.querySelector()")}} を使うのが推奨される今風のやり方ですが、これは CSS セレクタと同じ方法で要素を選別できるからです。上記の <code>querySelector()</code> 呼び出しでは文書に現われる最初の {{htmlelement("a")}} がマッチします。もし複数の要素を選択し処理したいのであれば {{domxref("Document.querySelectorAll()")}} を使うことができて、これはセレクタとマッチする全ての要素にマッチし、それらへの参照を {{domxref("NodeList")}} と呼ばれる<a href="/docs/Learn/JavaScript/First_steps/Arrays">配列</a>のようなオブジェクトに保存します。</p> + +<p>要素への参照を得るための、次のような古いやり方もあります:</p> + +<ul> + <li>{{domxref("Document.getElementById()")}} は要素を指定の <code>id</code> 属性値を使って選択します。<code><p id="myId">My paragraph</p></code> こんなのです。 関数の引数に ID を渡します。 <code>const elementRef = document.getElementById('myId')</code> こんな具合です。</li> + <li>{{domxref("Document.getElementsByTagName()")}} これは指定した種類の全ての要素を配列のようなオブジェクトとして返します、例えば全部の <code><p></code>、全部の <code><a></code>など。 要素の種別は関数の引数として渡します。<code>const elementRefArray = document.getElementsByTagName('p')</code> こんな具合です。</li> +</ul> + +<p>上の二つは <code>querySelector()</code> のような今風のメソッドよりも古いブラウザーで動作しますが、あまり便利ではありません。これ以外にどんなやり方があるかは、あなた自身で探してみて下さい!</p> +</div> + +<h3 id="新しいノードの作成と配置">新しいノードの作成と配置</h3> + +<p>ここまでで、どんな事ができるのかちょっと見えてきたと思いますが、さらに進んで新しい要素を作る方法を見ていきましょう。</p> + +<ol> + <li>今の例題に戻って、{{htmlelement("section")}} 要素を掴むところから始めましょう — すでに書いてあるスクリプトの下に次のコードを追加して下さい(この先の他の行についても、同じようにやって下さい): + <pre class="brush: js notranslate">const sect = document.querySelector('section');</pre> + </li> + <li>{{domxref("Document.createElement()")}} を使って新しいパラグラフを作り、前やったのと同じ方法でテキストを入れてやりましょう: + <pre class="brush: js notranslate">const para = document.createElement('p'); +para.textContent = 'We hope you enjoyed the ride.';</pre> + </li> + <li>この新しいパラグラフを section の最後に {{domxref("Node.appendChild()")}} を使って追加できます: + <pre class="brush: js notranslate">sect.appendChild(para);</pre> + </li> + <li>このパートの締めとして、文章がうまいことまとまるように、リンクを含んでいるパラグラフに対してテキストノードを追加しましょう。まずテキストノードを {{domxref("Document.createTextNode()")}} を使って作成します: + <pre class="brush: js notranslate">const text = document.createTextNode(' — the premier source for web development knowledge.');</pre> + </li> + <li>リンクを含んだパラグラフへの参照を取り出して、そこにテキストノードを追加します: + <pre class="brush: js notranslate">const linkPara = document.querySelector('p'); +linkPara.appendChild(text);</pre> + </li> +</ol> + +<p>以上が DOM にノードを追加するために必要な事のほぼ全てです — 動的なインターフェースを作成する際(あとでそういう例題をいくつか見ていきます)これらのメソッドをめっちゃ使う事になるでしょう。</p> + +<h3 id="要素を移動したり削除したり">要素を移動したり削除したり</h3> + +<p>ノードを移動したり、DOM から削除したくなる場合があると思います。勿論できます。</p> + +<p>リンクを含むパラグラフを section の最後に移動したい場合は、こうするだけです:</p> + +<pre class="brush: js notranslate">sect.appendChild(linkPara);</pre> + +<p>これでパラグラフは section の一番下に移動します。コピーが作成されるだけじゃないのかとお思いかもしれませんが、この場合は違います — <code>linkPara</code> はパラグラフへの参照の唯一のコピーです。もしコピーをした上で同じように追加をしたいのであれば、 {{domxref("Node.cloneNode()")}} をかわりに使う必要があります。</p> + +<p>削除したいノードとその親ノードへの参照を得ていれば、ノードを削除するのも非常に簡単です。今の例題であれば、以下のように {{domxref("Node.removeChild()")}} を使うだけです:</p> + +<pre class="notranslate">sect.removeChild(linkPara);</pre> + +<p>よくあるケースですが、削除したいノードそのものへの参照しかない場合に、{{domxref("ChildNode.remove()")}} が使えます:</p> + +<pre class="brush: js notranslate">linkPara.remove();</pre> + +<p>このメソッドは、古いブラウザではサポートされていません。 ノードにそれ自体を削除するように指示するメソッドはないので、次のようにしなければなりません。</p> + +<pre class="brush: js notranslate">linkPara.parentNode.removeChild(linkPara);</pre> + +<p>上の行をあなたのコードに追加してやってみて下さい。</p> + +<h3 id="スタイルを操作する">スタイルを操作する</h3> + +<p>いろんなやり方で CSS スタイルを JavaScript から操作することができます。</p> + +<p>まず、ドキュメントに付随する全部のスタイルシートのリストは {{domxref("Document.stylesheets")}} を使って得られ、これは {{domxref("CSSStyleSheet")}} オブジェクトを含む配列のようなオブジェクトを返します。そうしたらお望みのままにスタイルを追加したり削除したりできます。ですがこのやり方について詳しくはやりません。なぜならスタイルをいじるにはちょっとばかり古風で難しいやり方だからです。もっと簡単なやり方があります。</p> + +<p>まずは、動的にスタイルを指定したい要素に、インラインスタイルを直接追加するやり方です。これには {{domxref("HTMLElement.style")}} プロパティを使い、このプロパティはドキュメント中の各素要のインラインスタイル情報を保持しています。このオブジェクトのプロパティを更新すれば要素のスタイルを直接変更できます。</p> + +<ol> + <li>例として、作成中の例題に以下の行を追加してみて下さい: + <pre class="brush: js notranslate">para.style.color = 'white'; +para.style.backgroundColor = 'black'; +para.style.padding = '10px'; +para.style.width = '250px'; +para.style.textAlign = 'center';</pre> + </li> + <li>ページをリロードすると指定のパラグラフにスタイルが適用されているはずです。ブラウザーの <a href="/docs/Tools/Page_Inspector">Page Inspector や DOM inspector</a> からパラグラフを見ると、言うまでもなく上の行がドキュメントのインラインスタイルに追加されているはずです: + <pre class="brush: html notranslate"><p style="color: white; background-color: black; padding: 10px; width: 250px; text-align: center;">We hope you enjoyed the ride.</p></pre> + </li> +</ol> + +<div class="note"> +<p><strong>注記</strong>: CSS ではハイフン記法になっているものを、JavaScript プロパティ版の CSS スタイルはどんな風に小文字のキャメルケースで書いている(<code>background-color</code> と <code>backgroundColor</code> とか)か見ておいて下さい。まぜこぜにしないよう注意して下さい、さもないと動きませんよ。</p> +</div> + +<p>ドキュメントのスタイルを動的にいじる際によく使われる別のやり方をこれから見ていきましょう。</p> + +<ol> + <li>さっき JavaScript に追加した 5 行を削除します。</li> + <li>HTML の {{htmlelement("head")}} の中に、以下を追加します: + <pre class="notranslate"><style> +.highlight { + color: white; + background-color: black; + padding: 10px; + width: 250px; + text-align: center; +} +</style></pre> + </li> + <li>さて、多くの HTML 操作においてとても役に立つメソッドをお見せします — {{domxref("Element.setAttribute()")}} — これはに二つの引数、要素に設定したい属性名と、属性に設定したい値、を与えます。この場合だと、我々のパラグラフにクラス名、highlight をセットします: + <pre class="brush: js notranslate">para.setAttribute('class', 'highlight');</pre> + </li> + <li>ページをリロードしても何も変わりません — パラグラフには CSS が今も適用されていますが、今回はクラスを指定して CSS ルールが選んでいて、インライン CSS スタイルによるものではありません。</li> +</ol> + +<p>どうやるかはあなた次第です。それぞれに利点と欠点があります。最初のやり方は少ない設定ですみ、簡単な場合には向いていますが、二つ目のやり方はずっときれいです (よくないやり方とされる、CSS と JavaScript の混在やインラインスタイルの使用がありません)。もっと大規模で複雑なアプリを作り始めたら、多分二つ目のやり方をよく使うようになると思いますが、結局はホントにあなた次第です。</p> + +<p>ここまで、実はそれほど役に立つことをやってません! 静的なコンテンツの作成に JavaScript を使う利点はありません — JavaScript など使わず、普通に HTML に書けば良いんです。HTML よりややこしいですし、コンテンツを JavaScript で作成するのは他にも問題があります (検索エンジンで読めない、とか)。</p> + +<p>次の二つのセクションでは、DOM API のもっと実践的な使い方を見ていきます。</p> + +<div class="note"> +<p><strong>注記</strong>: 私たちによる <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/dom-example-manipulated.html">dom-example.htm l完成版</a> のデモが GitHub にあります (<a href="http://mdn.github.io/learning-area/javascript/apis/document-manipulation/dom-example-manipulated.html">ライブ実行もどうぞー</a>)。</p> +</div> + +<h2 id="実践学習_ウィンドウオブジェクトから使える情報を取り出す">実践学習: ウィンドウオブジェクトから使える情報を取り出す</h2> + +<p>ここまででは文書を操作するための {{domxref("Node")}} と {{domxref("Document")}} の機能ばかり見てきましたが、他のソースからデータを取ってきてあなたの UI で使ったって勿論かまわないわけです。あなたはデータが正しい形式である事を確認するだけです。これは JavaScript が弱い型付け言語であるために、他の多く言語の場合よりも簡単です — 例えば画面に表示しようとしたとき、数値は自動的に文字列に変換されます。</p> + +<p>ここの例題ではよくある問題を解決していきます — あなたのアプリを表示しているウィンドウがどんな大きさであれ、それを同じ大きさになるようにすることです。これはゲームのような、表示する画面領域をできるだけ大きくしたいような場合に、しばしば役に立ちます。</p> + +<p>まずは <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/window-resize-example.html">window-resize-example.html</a> と <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/bgtile.png">bgtile.png</a> ファイルのローカルコピーを作成して下さい。読み込んで見てみて下さい — 背景に画像がタイル表示された、{{htmlelement("div")}} 要素が画面に小さく表示されているでしょう。この領域が、私たちのアプリの UI 領域だとしていきます。</p> + +<ol> + <li>まず最初に、div への参照を取得し、ビューポート (ドキュメントが表示されている内側のウィンドウです) の幅と高さを取得して、これらを変数に保存します。便利なことに幅と高さの値は {{domxref("Window.innerWidth")}} と {{domxref("Window.innerHeight")}} プロパティにあります。以下の行を、もう書いてある {{htmlelement("script")}} の中に書き足します: + <pre class="brush: js notranslate">const div = document.querySelector('div'); +let winWidth = window.innerWidth; +let winHeight = window.innerHeight;</pre> + </li> + <li>次は、動的に div の幅と高さをビューポートのものと同じにします。次の二行を、さっき追加した部分の後に書き足して下さい: + <pre class="brush: js notranslate">div.style.width = winWidth + 'px'; +div.style.height = winHeight + 'px';</pre> + </li> + <li>保存してブラウザーで読み直してみて下さい — どんな大きさの画面を使っているのであれ、div がビューポートと同じ大きさになったはずです。ウィンドウが大きくなるようにリサイズしてみても、div の大きさは変わらないはずです — 一度しか大きさを設定していないからです。</li> + <li>ウィンドウがリサイズされた時に div もリサイズされるよう、イベントを使ってみるのはどうでしょう? {{domxref("Window")}} オブジェクトにはリサイズされた時に呼ばれるイベントがあって、ウィンドウがリサイズされる毎発火します — この機能を {{domxref("Window.onresize")}} イベントハンドラーから使って、リサイズされる毎私たちのコードが再実行されるようにしてみましょう。あなたのコードの最後に以下を書き足して下さい: + <pre class="brush: js notranslate">window.onresize = function() { + winWidth = window.innerWidth; + winHeight = window.innerHeight; + div.style.width = winWidth + 'px'; + div.style.height = winHeight + 'px'; +}</pre> + </li> +</ol> + +<div class="note"> +<p><strong>注記</strong>: もし行き詰まったら、私たちによる <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/window-resize-example-finished.html">完成版ウィンドウリサイズ例題</a> (<a href="http://mdn.github.io/learning-area/javascript/apis/document-manipulation/window-resize-example-finished.html">ライブ実行版もあるよ</a>) を見て下さい。</p> +</div> + +<h2 id="実践学習_動的な買い物リスト">実践学習: 動的な買い物リスト</h2> + +<p>この記事の締めとして、あなたにちょっとした難題を出したいと思います — 単純な買い物リストの例を作ってもらいます。フォーム入力(input)とボタンからリストに動的に商品を追加できるようにします。input に商品を入力してボタンを押したら:</p> + +<ul> + <li>商品がリストに表示されなければならない。</li> + <li>それぞれの商品にはボタンが付いていて、それを押すとその商品をリストから消せなければならない。</li> + <li>次の商品をすぐに入力できるよう、input の中身は消されてフォーカスされていなければならない。</li> +</ul> + +<p>完成版のデモはこんな感じになるでしょう:</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14563/shopping-list.png" style="border-style: solid; border-width: 1px; display: block; height: 225px; margin: 0px auto; width: 369px;"></p> + +<p>この課題を完了させるには、以下のステップに従い、上で説明した通りに買い物リストが動くようにして下さい。</p> + +<ol> + <li>まず私たちが用意した <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/shopping-list.html">shopping-list.html</a> 初期ファイルをダウンロードしてローカルコピーをどこかに作成します。最小限の CSS、ラベルのついたリスト、inputとボタン、空のリストと {{htmlelement("script")}} 要素が書いてあるはずです。この先書き足していくものは全部 script の中に書きます。</li> + <li>({{htmlelement("ul")}}) と {{htmlelement("input")}} と {{htmlelement("button")}} 要素への参照を保持する3つの変数を作成します。</li> + <li>ボタンがクリックされた時の応答として走らせる <a href="/docs/Learn/JavaScript/Building_blocks/Functions">関数</a> を作成します。</li> + <li>関数本体は、input 要素の現在の <a href="/docs/Web/API/HTMLInputElement#Properties">値</a>を変数に保存するところから始めます。</li> + <li>次に、input 要素の値に空文字列(<code>''</code>)を代入して、input 要素を空にします。</li> + <li>3つの要素を作成します — リスト項目({{htmlelement('li')}}) と {{htmlelement('span')}} と {{htmlelement('button')}} で、これらを変数に保存します。</li> + <li>span と button をリスト項目 li の子に追加します。</li> + <li>spanのテキストコンテントに、先程保存した input 要素の値を代入し、ボタンのテキストコンテントを「削除」にします。</li> + <li>できたリスト項目をリストの子に追加します。</li> + <li>削除ボタンにイベントハンドラーを追加して、クリックされたらボタンが含まれているリスト項目全体を削除するようにします。</li> + <li>最後に、<code><a href="/docs/Web/API/HTMLElement/focus">focus()</a></code>メソッドを使って input 要素にフォーカスし、次の買い物リスト商品をすぐに入力できるようにします。</li> +</ol> + +<div class="note"> +<p><strong>注記</strong>: 本当にどうしようもなく詰まったら、私たちの <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/document-manipulation/shopping-list-finished.html">完成版買い物リスト</a> (<a href="http://mdn.github.io/learning-area/javascript/apis/document-manipulation/shopping-list-finished.html">ライブ実行版もあるよ</a>)を見て下さい。</p> +</div> + +<h2 id="まとめ">まとめ</h2> + +<p>私たちのドキュメントと DOM 操作に関する学習はこれで終わりです。ここまでくれば、ドキュメントの制御やユーザのウェブ体験に関するブラウザーの重要な部品は何か、理解できたと思います。一番大事な DOM とは何か、役に立つ機能を作るのにこれをどう使えば良いのか理解できたと思います。</p> + +<h2 id="参考文献">参考文献</h2> + +<p>ドキュメントをいじるのに役立つ機能はたくさんあります。私たちのリファレンスも見て、いろいろ発見して下さい:</p> + +<ul> + <li>{{domxref("Document")}}</li> + <li>{{domxref("Window")}}</li> + <li>{{domxref("Node")}}</li> + <li>{{domxref("HTMLElement")}}, {{domxref("HTMLInputElement")}}, {{domxref("HTMLImageElement")}}, etc.</li> +</ul> + +<p>(私共の <a href="https://developer.mozilla.org/docs/Web/API">Web API index</a> から、MDNにあるウェブAPIに関する全ドキュメント一覧も見て下さい!)</p> + +<div>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Introduction", "Learn/JavaScript/Client-side_web_APIs/Fetching_data", "Learn/JavaScript/Client-side_web_APIs")}}</div> + +<div> +<h2 id="このモジュール内の文書">このモジュール内の文書</h2> + +<ul> + <li><a href="/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">ウェブAPIの紹介</a></li> + <li><a href="/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">ドキュメントの操作</a></li> + <li><a href="/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">サーバからのデータ取得</a></li> + <li><a href="/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">サードパーティAPI</a></li> + <li><a href="/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">絵を描く</a></li> + <li><a href="/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">動画と音声のAPI</a></li> + <li><a href="/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">クライアント側でのデータ保持</a></li> +</ul> +</div> + +<div id="simple-translate"> +<div> +<div class="simple-translate-button isShow" style="height: 22px; width: 22px; top: 12775px; left: 144px;"></div> + +<div class="simple-translate-panel " style="width: 300px; height: 200px; top: 0px; left: 0px; font-size: 13px; background-color: rgb(255, 255, 255);"> +<div class="simple-translate-result-wrapper" style="overflow: hidden;"> +<div class="simple-translate-move"></div> + +<div class="simple-translate-result-contents"> +<p class="simple-translate-result" style="color: rgb(0, 0, 0);"></p> + +<p class="simple-translate-candidate" style="color: rgb(115, 115, 115);"></p> +</div> +</div> +</div> +</div> +</div> diff --git a/files/ja/learn/javascript/client-side_web_apis/third_party_apis/index.html b/files/ja/learn/javascript/client-side_web_apis/third_party_apis/index.html new file mode 100644 index 0000000000..b44a2b17bf --- /dev/null +++ b/files/ja/learn/javascript/client-side_web_apis/third_party_apis/index.html @@ -0,0 +1,442 @@ +--- +title: サードパーティ API +slug: Learn/JavaScript/Client-side_web_APIs/Third_party_APIs +tags: + - 3rd party + - API + - Beginner + - CodingScripting + - Google Maps + - Learn + - NYTimes + - Third party + - youtube +translation_of: Learn/JavaScript/Client-side_web_APIs/Third_party_APIs +--- +<div>{{LearnSidebar}}{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Fetching_data", "Learn/JavaScript/Client-side_web_APIs/Drawing_graphics", "Learn/JavaScript/Client-side_web_APIs")}}</div> + +<p class="summary">これまで説明してきた API はブラウザーに組み込まれていましたが、すべての API がそうというわけではありません。Google Maps・Twitter・Facebook・PayPal などの大規模なサイトやサービスの多くは開発者がそれらのデータ (ブログに Twitter のストリームを表示するなど) やサービス (ユーザーのログインに Facebook ログインを利用するなど) を利用できるように API を提供しています。この記事ではブラウザー API とサードパーティ API の違いを見て、後者の典型的な使い方について説明します。</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提知識:</th> + <td>JavaScript の基礎 (<a href="/ja/docs/Learn/JavaScript/First_steps">JavaScript の第一歩</a>, <a href="/ja/docs/Learn/JavaScript/Building_blocks">JavaScript の構成要素</a>, <a href="/ja/docs/Learn/JavaScript/Objects">JavaScript オブジェクト入門</a> をご覧ください),<a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs">クライアントサイド API の基礎</a></td> + </tr> + <tr> + <th scope="row">到達目標:</th> + <td>サードパーティ API の仕組み、それらを利用してウェブサイトを強化する方法を学習する</td> + </tr> + </tbody> +</table> + +<h2 id="サードパーティAPIとは">サードパーティAPIとは?</h2> + +<p>サードパーティ API は、サードパーティ (通常は Facebook、Twitter、Google などの企業) が提供する API で、JavaScript を介して機能にアクセスしてサイトで使用することができます。最もわかりやすい例の 1 つとして、マッピング API を使用してページにカスタムマップを表示することがあります。</p> + +<p><a href="https://mdn.github.io/learning-area/javascript/apis/third-party-apis/mapquest/">Simple Mapquest API の例</a>を参考に、サードパーティ API とブラウザー API の違いを説明します。</p> + +<div class="note"> +<p><strong>注意</strong>: 一度に<a href="/ja/docs/Learn#Getting_our_code_examples">すべてのコード例を取得</a>したい場合があります。その場合は、各セクションで必要なサンプルファイルをレポジトリーで検索するだけで済みます。</p> +</div> + +<h3 id="それらはサードパーティのサーバーにあります">それらはサードパーティのサーバーにあります</h3> + +<p>ブラウザー API はブラウザーに組み込まれており、すぐに JavaScript からアクセスできます。たとえば、<a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Introduction#How_do_APIs_work">紹介記事で見た</a>Web Audio API は、ネイティブの {{domxref("AudioContext")}} オブジェクトを使ってアクセスします。例えば:</p> + +<pre class="brush: js notranslate">const audioCtx = new AudioContext(); + ... +const audioElement = document.querySelector('audio'); + ... +const audioSource = audioCtx.createMediaElementSource(audioElement); +// etc.</pre> + +<p>一方、サードパーティの API はサードパーティのサーバーにあります。JavaScript からこれらにアクセスするには、まず API 機能に接続してページで利用できるようにする必要があります。 これは通常、Mapquest の例で見られるように、{{htmlelement("script")}} 要素を介してサーバー上で利用可能な JavaScript ライブラリーへの最初のリンクを含めます。</p> + +<pre class="brush: js notranslate"><script src="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.js"></script> +<link type="text/css" rel="stylesheet" href="https://api.mqcdn.com/sdk/mapquest-js/v1.3.2/mapquest.css"/></pre> + +<p>そのライブラリーで利用可能なオブジェクトを使い始めることができます。例えば:</p> + +<pre class="brush: js notranslate">let map = L.mapquest.map('map', { + center: [53.480759, -2.242631], + layers: L.mapquest.tileLayer('map'), + zoom: 12 +});</pre> + +<p>ここでは、マップ情報を格納するための変数を作成し、次に <code>mapquest.map()</code> メソッドを使用して新しいマップを作成します。このメソッドは、必要な {{htmlelement("div")}} 要素の ID を受け取ります。('map') で地図を表示し、表示したい特定の地図の詳細を含む options オブジェクトを表示します。この場合は、地図の中心座標、表示する <code>map</code> 型の地図レイヤー (<code>mapquest.tileLayer()</code> メソッドを使用して作成)、およびデフォルトのズームレベルを指定します。</p> + +<p>これが、Mapquest API が単純な地図を描くために必要なすべての情報です。接続しているサーバーは、表示されている地域の正しい地図タイルを表示するなど、複雑なものをすべて処理します。</p> + +<div class="note"> +<p><strong>メモ</strong>: API の中には、機能へのアクセスをわずかに異なる方法で処理するものがあり、開発者はデータを取得するために特定の URL パターンに対して HTTP リクエストを行う必要があります。これらは <a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs#A_RESTful_API_—_NYTimes">RESTful API と呼ばれ、後で例が出てきます</a>。</p> +</div> + +<h3 id="通常は_API_キーが必要です">通常は API キーが必要です</h3> + +<p><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Introduction#They_have_additional_security_mechanisms_where_appropriate">最初の記事で説明した</a>ように、ブラウザー API のセキュリティは許可プロンプトによって処理される傾向があります。これらの目的は、<span class="tlid-translation translation" lang="ja"><span title="">ユーザーが訪問したウェブサイトで何が起こっているのかをユーザー自身が認識できるようにし</span><span title="">、悪意のある方法で API を使用している人の被害にあう可能性を低くすることです。</span></span></p> + +<p>サードパーティの API には、少し異なる権限システムがあります。開発者が API 機能にアクセスできるようにするために開発者キーを使用する傾向があります。</p> + +<p>Mapquest API の例には、次のような行があります。</p> + +<pre class="notranslate">L.mapquest.key = 'YOUR-API-KEY-HERE';</pre> + +<p>この行では、アプリケーションで使用する API キーまたは開発者キーを指定します。アプリケーションの開発者は、キーを取得して API の機能へのアクセス許可を得るためにコードに含める必要があります。この例では、プレースホルダーを用意しました。</p> + +<div class="blockIndicator note"> +<p><strong>メモ</strong>: 独自の例を作成するときは、プレースホルダーの代わりに独自の API キーを使用します。</p> +</div> + +<p>他の API では、少し異なる方法でキーを含める必要があるかもしれませんが、ほとんどのパターンは比較的似ています。</p> + +<p>キーを要求することで、API プロバイダーは API のユーザーに自分のアクションに対する責任を持たせることができます。開発者がキーを登録すると、それらは API プロバイダに認識され、彼らが API に悪意のあることをし始めたらアクション (たとえば、人々の位置を追跡したり、APIを機能させないために大量のリクエストで API をスパムしようとするなど) を取ることができます。最も簡単なアクションは、単にそれらの API 特権を取り消すことです。</p> + +<h2 id="Mapquest_の例を拡張する">Mapquest の例を拡張する</h2> + +<p>API の他の機能の使用方法を示すために、Mapquest の例にさらに機能を追加しましょう。</p> + +<ol> + <li> + <p>この章を始めるにあたり、新しいディレクトリーに<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/third-party-apis/mapquest/starter-file.html">mapquest starter file</a>をコピーしましょう。もしもすでに<a href="/ja/docs/Learn#Getting_our_code_examples">examples repository</a> をクローンしているようなら、必要な <em>javascript/apis/third-party-apis/mapquest</em> を見つけてコピーしてください。</p> + </li> + <li> + <p>次に <a href="https://developer.mapquest.com/">Mapquest developer site</a>に行ってください。アカウントを作り、デベロッパーキーを使用してあなたのサンプルに利用してください。(アカウント作成時、デベロッパーキーは "consumer key" と呼ばれています。そして、"callback URL" を尋ねられると思いますが、その入力欄は空欄でかまいません)</p> + </li> + <li>starting fileを開き、APIキーのプレスホルダーにあなたのキーを入力してください。</li> +</ol> + +<h3 id="地図の種類を変更する">地図の種類を変更する</h3> + +<p>Mapquest API で表示できるマップには、さまざまな種類があります。 これを行うには、次の行を見つけます。</p> + +<pre class="brush: js notranslate">layers: L.mapquest.tileLayer('map')</pre> + +<p>hybrid-style map にするために <code>'map'</code> を <code>'hybrid'</code> に変えてみてください。他にも様々な値があります。<a href="https://developer.mapquest.com/documentation/mapquest-js/v1.3/l-mapquest-tile-layer/"><code>tileLayer</code> reference page</a> には使える様々なオプションや情報が載っています。</p> + +<h3 id="さまざまなコントロールを追加する">さまざまなコントロールを追加する</h3> + +<p>この地図には様々な機能を実装できますが、デフォルトでは、ズームコントロールのみが表示されます。<code>map.addControl()</code> メソッドを使うことで機能を拡張することが出来ます。以下のコードを<code>window.onload</code>ハンドラーに追加してみてください。</p> + +<pre class="brush: js notranslate">map.addControl(L.mapquest.control());</pre> + +<p><a href="https://developer.mapquest.com/documentation/mapquest-js/v1.3/l-mapquest-control/"><code>mapquest.control()</code></a> メソッドは、単純なフル機能のコントロールセットを作成するだけで、デフォルトでは右上隅に配置されます。<code>position</code> プロパティを含むコントロールのパラメータとしてオプションオブジェクトを指定することで、位置を調整することができます。例えば、次のようにしてみてください。</p> + +<pre class="brush: js notranslate"> map.addControl(L.mapquest.control({ position: 'bottomright' }));</pre> + +<p>他にも、<code><a href="https://developer.mapquest.com/documentation/mapquest-js/v1.3/l-mapquest-search-control/">mapquest.searchControl()</a></code> や <code><a href="https://developer.mapquest.com/documentation/mapquest-js/v1.3/l-mapquest-satellite-control/">mapquest.satelliteControl()</a></code> など、利用可能なコントロールの種類があり、中には非常に複雑で強力なものもあります。実際に遊んでみて、何ができるか見てみましょう。</p> + +<h3 id="カスタムマーカーを追加する">カスタムマーカーを追加する</h3> + +<p>マップ上の特定のポイントにマーカー (アイコン) を追加するのは簡単です。<code><a href="https://leafletjs.com/reference-1.3.0.html#marker">L.marker()</a></code> メソッドを使用するだけです (関連する Leaflet.js のドキュメントに記載されているようです)。次のコードを <code>window.onload</code> に追加します。</p> + +<pre class="brush: js notranslate">L.marker([53.480759, -2.242631], { + icon: L.mapquest.icons.marker({ + primaryColor: '#22407F', + secondaryColor: '#3B5998', + shadow: true, + size: 'md', + symbol: 'A' + }) +}) +.bindPopup('This is Manchester!') +.addTo(map);</pre> + +<p>ご覧のように、最もシンプルな方法では、2 つのパラメータを取ります。マーカーを表示する座標を含む配列と、その時点で表示するアイコンを定義する <code>icon</code> プロパティを含むオプションオブジェクトです。</p> + +<p>アイコンは、<code><a href="https://developer.mapquest.com/documentation/mapquest-js/v1.3/l-mapquest-icons/">mapquest.icons.marker()</a></code> メソッドを使用して定義され、ご覧のようにマーカーの色やサイズなどの情報が含まれています。</p> + +<p>最初のメソッド呼び出しの最後に <code>.bindPopup('This is Manchester!')</code> を連鎖させ、マーカーがクリックされたときに表示されるコンテンツを定義します。</p> + +<p>最後に、<code>.addTo(map)</code> を連鎖させて、実際にマーカーをマップに追加します。</p> + +<p>ドキュメントに記載されているその他のオプションを試してみて、何ができるか見てみましょう。Mapquest には、道案内や検索など、かなり高度な機能があります。</p> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: サンプルがうまく動作しない場合は、完成版のコードをチェックしてみてください。<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/third-party-apis/mapquest/expanded-example.html">expanded-example.html</a> を参照してください (<a href="https://mdn.github.io/learning-area/javascript/apis/third-party-apis/mapquest/expanded-example.html">ここでライブで実行しているのも見てください</a>)。</p> +</div> + +<h2 id="Google_マップはどうですか?">Google マップはどうですか?</h2> + +<p>Google Maps は間違いなく最も人気のある地図 API です。使用方法を示すために<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/third-party-apis/google-maps/finished-maps-example.html">例を作成しました</a>が、最終的にはいくつかの理由から Mapquest を使用しました:</p> + +<ul> + <li>使い始めるのがはるかに簡単だからです。一般的な Google API の場合、Google アカウントを作成して <a href="https://console.cloud.google.com">Google Cloud Platform Console</a> にログインして API キーなどを作成する必要があり、そのプロセスはかなり複雑です。特に <a href="https://cloud.google.com/maps-platform/">Google Maps API</a> の場合は、課金目的でクレジットカードを提供する必要があり (基本的な利用は無料ですが)、基本的なチュートリアルとしては受け入れられないと思いました。</li> + <li>他にも選択肢があることを示したかったのです。</li> +</ul> + +<h2 id="RESTful_API_—_NYTimes">RESTful API — NYTimes</h2> + +<p>では、もう一つのAPIの例を見てみましょう — <a href="https://developer.nytimes.com/">New York Times API</a> です。この API を使用すると、New York Times のニュースストーリー情報を取得して、サイトに表示することができます。このタイプの API は <strong>RESTful API</strong> として知られています。Mapquest で行ったように JavaScript ライブラリーの機能を使用してデータを取得するのではなく、特定の URL にHTTP リクエストを行い、検索語やその他のプロパティのようなデータを URL 内にエンコードしてデータを取得します (多くの場合、URL パラメーターとして)。これは、API でよく見られるパターンです。</p> + +<h2 id="サードパーティAPIを利用するためのアプローチ">サードパーティAPIを利用するためのアプローチ</h2> + +<p>以下では、NYTimes API の使用方法を示すエクササイズを紹介しますが、新しい API を使用するためのアプローチとして、より一般的なステップのセットを提供します。</p> + +<h3 id="ドキュメントを探す">ドキュメントを探す</h3> + +<p>サードパーティの API を利用したい場合、その API がどのような機能を持っているのか、どのように利用するのかなどを知るために、ドキュメントがどこにあるのかを知ることは欠かせません。New York Times API のドキュメントは <a href="https://developer.nytimes.com/">https://developer.nytimes.com/</a> にあります。</p> + +<h3 id="開発者キーを取得">開発者キーを取得</h3> + +<p>ほとんどの API では、セキュリティと説明責任のために、何らかの開発者キー使用する必要があります。NYTimes API キーの登録には、<a href="https://developer.nytimes.com/get-started">https://developer.nytimes.com/get-started</a> の指示に従ってください。</p> + +<ol> + <li> + <p>記事検索 API のキーを要求してみよう — 新規アプリを作成し、これを利用したい API として選択します (名前と説明を記入し、「記事検索 API 」の下のスイッチをオンに切り替えて「作成」をクリックします)。</p> + </li> + <li> + <p>結果のページから API キーを取得します。</p> + </li> + <li> + <p>さて、例題を始めるために、<a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/third-party-apis/nytimes/nytimes_start.html">nytimes_start.html</a> と <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/third-party-apis/nytimes/nytimes.css">nytimes.css</a> のコピーをコンピュータ上の新しいディレクトリに作成してください。すでに <a href="/ja/docs/Learn#Getting_our_code_examples">examples リポジトリをクローン</a>している場合は、<em>javascript/apis/third-party-apis/nytimes</em> ディレクトリにあるこれらのファイルのコピーをすでに持っているでしょう。最初に <code><script></code> 要素には、例のセットアップに必要な変数がいくつか含まれています。</p> + </li> +</ol> + +<p>このアプリは、検索用語とオプションの開始日と終了日を入力することを可能にし、Article Search API をクエリして検索結果を表示するために使用します。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14821/nytimes-search.png" style="border-style: solid; border-width: 1px; display: block; height: 374px; margin: 0px auto; width: 700px;"></p> + +<h3 id="API_をアプリに接続する">API をアプリに接続する</h3> + +<p>まず、API とアプリ間の接続を行う必要があります。この API の場合、サービスから正しい URL でデータを要求するたびに、API キーを<a href="/ja/docs/Web/HTTP/Methods/GET">取得</a>パラメーターとして含める必要があります。</p> + +<ol> + <li> + <p>次の行を探します。</p> + + <pre class="brush: js notranslate">let key = ' ... ';</pre> + + <p>既存の API キーを、前のセクションで取得した実際の API キーに置き換えます。</p> + + <p>JavaScriptに次の行を追加してください。<code>// Event listeners to control the functionality</code> コメントの下に、次の行を追加します。これは、フォームが送信されたとき (ボタンが押されたとき) に submitSearch() という関数を実行します。</p> + + <pre class="brush: js notranslate">searchForm.addEventListener('submit', submitSearch);</pre> + </li> + <li> + <p>前の行の下に submitSearch() と fetchResults() 関数の定義を追加します。</p> + + <pre class="brush: js notranslate">function submitSearch(e) { + pageNumber = 0; + fetchResults(e); +} + +function fetchResults(e) { + // Use preventDefault() to stop the form submitting + e.preventDefault(); + + // Assemble the full URL + url = baseURL + '?api-key=' + key + '&page=' + pageNumber + '&q=' + searchTerm.value <span class="blob-code-inner"><span class="pl-s1"><span class="pl-k x">+</span><span class="x"> </span><span class="pl-s"><span class="pl-pds x">'</span><span class="x">&fq=document_type:("article")</span><span class="pl-pds x x-last">'</span></span></span></span>; + + if(startDate.value !== '') { + url += '&begin_date=' + startDate.value; + }; + + if(endDate.value !== '') { + url += '&end_date=' + endDate.value; + }; + +}</pre> + </li> +</ol> + +<p><code>submitSearch()</code> は最初にページ番号を 0 に戻してから <code>fetchResults()</code> を呼び出します。これは最初にイベントオブジェクトの <code>preventDefault()</code> を呼び出し、フォームが実際に送信されるのを止めるためです (これでは例が壊れてしまいます)。次に、文字列を操作してリクエスト先の完全な URL を組み立てます。このデモで必須と思われる部分を組み立てることから始めます。</p> + +<ul> + <li>ベース URL (<code>baseURL</code> 変数から取得)。</li> + <li>API キー。これは <code>api-key</code> URL パラメーターで指定する必要があります (値は key 変数から取得されます)。</li> + <li>ページ番号。これは <code>page</code> URL パラメーターで指定する必要があります (値は <code>pageNumber</code> 変数から取得されます)。</li> + <li><code>q</code> URL パラメーターで指定しなければならない検索語 (値は <code>searchTerm</code> テキスト {{htmlelement("input")}} の値から取得されます)。</li> + <li><code>fq</code> URL パラメーターで渡された式で指定された、結果を返すドキュメントの種類。この例では、記事を返したいとします。</li> +</ul> + +<p>次に、いくつかの <code><a href="/ja/docs/Web/JavaScript/Reference/Statements/if...else">if()</a></code> ステートメントを使用して、<code>startDate</code> と <code>endDate</code> <code><input></code> に値が入力されているかどうかをチェックします。記入されている場合は、それぞれ <code>begin_date</code> と <code>end_date</code> の URL パラメーターで指定された値を URL に追加します。</p> + +<p>そのため、完全な URL は次のような形になってしまいます。</p> + +<pre class="notranslate">https://api.nytimes.com/svc/search/v2/articlesearch.json?api-key=YOUR-API-KEY-HERE&page=0&q=cats +&<span class="blob-code-inner"><span class="pl-s1"><span class="pl-s"><span class="x">fq=document_type:("article")</span></span></span></span>&begin_date=20170301&end_date=20170312</pre> + +<div class="note"> +<p><strong>Note</strong>: どのようなURLパラメーターを含めることができるかについての詳細は、<a href="https://developer.nytimes.com/">NYTimes developer docs</a> を参照してください。</p> +</div> + +<div class="note"> +<p><strong>Note</strong>: この例では初歩的なフォームデータの検証を行っています — 検索語フィールドは、フォームを送信する前に入力しなければなりません (<code>required</code> 属性を使用して達成されます)。日付フィールドには <code>pattern</code> 属性が指定されており、値が 8 個の数字 (<code>pattern="[0-9]{8}"</code>) で構成されていないと送信されません。これらがどのように機能するかについての詳細は <a href="/ja/en-US/docs/Learn/HTML/Forms/Form_validation">Form data validation</a> を参照してください。</p> +</div> + +<h3 id="API_からデータを要求する">API からデータを要求する</h3> + +<p>これで URL を作成したので、それにリクエストしてみましょう。これは <a href="/ja/docs/Web/API/Fetch_API/Using_Fetch">Fetch API</a> を使って行います。</p> + +<p>以下のコードブロックを <code>fetchResults()</code> 関数の中に追加します:</p> + +<pre class="brush: js notranslate">// Use fetch() to make the request to the API +fetch(url).then(function(result) { + return result.json(); +}).then(function(json) { + displayResults(json); +});</pre> + +<p>ここでは、<code>url</code> 変数を <code><a href="/ja/docs/Web/API/WindowOrWorkerGlobalScope/fetch">fetch()</a></code> に渡してリクエストを実行し、<code><a href="/ja/docs/Web/API/Body/json">json()</a></code> 関数を使用してレスポンスボディを JSON に変換し、結果の JSON を <code>displayResults()</code> 関数に渡して、データを UI に表示できるようにします。</p> + +<h3 id="データを表示する">データを表示する</h3> + +<p>それでは、データを表示する方法を見てみましょう。 <code>fetchResults()</code> 関数の下に以下の関数を追加します。</p> + +<pre class="brush: js notranslate">function displayResults(json) { + while (section.firstChild) { + section.removeChild(section.firstChild); + } + + const articles = json.response.docs; + + if(articles.length === 10) { + nav.style.display = 'block'; + } else { + nav.style.display = 'none'; + } + + if(articles.length === 0) { + const para = document.createElement('p'); + para.textContent = 'No results returned.' + section.appendChild(para); + } else { + for(var i = 0; i < articles.length; i++) { + const article = document.createElement('article'); + const heading = document.createElement('h2'); + const link = document.createElement('a'); + const img = document.createElement('img'); + const para1 = document.createElement('p'); + const para2 = document.createElement('p'); + const clearfix = document.createElement('div'); + + let current = articles[i]; + console.log(current); + + link.href = current.web_url; + link.textContent = current.headline.main; + para1.textContent = current.<span class="blob-code-inner"><span class="pl-s1"><span class="pl-smi x x-first x-last">snippet</span></span></span>; + para2.textContent = 'Keywords: '; + for(let j = 0; j < current.keywords.length; j++) { + const span = document.createElement('span'); + span.textContent += current.keywords[j].value + ' '; + para2.appendChild(span); + } + + if(current.multimedia.length > 0) { + img.src = 'http://www.nytimes.com/' + current.multimedia[0].url; + img.alt = current.headline.main; + } + + clearfix.setAttribute('class','clearfix'); + + article.appendChild(heading); + heading.appendChild(link); + article.appendChild(img); + article.appendChild(para1); + article.appendChild(para2); + article.appendChild(clearfix); + section.appendChild(article); + } + } +}</pre> + +<p>ここにはたくさんのコードがあります:</p> + +<ul> + <li><code><a href="/ja/docs/Web/JavaScript/Reference/Statements/while">while</a></code> ループは、DOM 要素のすべてのコンテンツを削除するために使われる一般的なパターンで、この場合は {{htmlelement("section")}} 要素です。私たちは <code><section></code> に最初の子があるかどうかをチェックし続け、ある場合は最初の子を削除します。ループは <code><section></code> に子がいなくなった時点で終了します。</li> + <li>次に、<code>articles</code> 変数を <code>json.response.docs</code> と等しくなるように設定します — これは検索によって返された記事を表すすべてのオブジェクトを保持する配列です。これは、以下のコードを少しシンプルにするために行われています。</li> + <li>最初の <code><a href="/ja/docs/Web/JavaScript/Reference/Statements/if...else">if()</a></code> ブロックは、10 個の記事が返されるかどうかをチェックします ( API は一度に10個までの記事を返します。) もし返された場合、前の10個 / 次の10個のページネーションボタンを含む {{htmlelement("nav")}} を表示します。10記事未満の記事が返された場合、それらはすべて 1 ページに収まるので、ページ分割ボタンを表示する必要はありません。次のセクションでは、ページ分割機能の配線を行います。</li> + <li>次の <code>if()</code> ブロックは記事が返ってこないかどうかをチェックします。もしそうならば、何も表示しようとしません — "No results returned." というテキストを含む {{htmlelement("p")}} を作成して、それを <code><section></code> に挿入します。</li> + <li>いくつかの記事が返された場合、私たちはまず、それぞれのニュース記事を表示するために使用したいすべての要素を作成し、それぞれに適切なコンテンツを挿入し、適切な場所で DOM に挿入します。記事オブジェクトのどのプロパティに表示すべき正しいデータが含まれているかを調べるために、Article Search API リファレンスを参照しました (<a href="https://developer.nytimes.com/apis">NYTimes APIs</a>)。これらの操作のほとんどはかなり明白ですが、いくつかは呼び出す価値があります: + <ul> + <li>私たちは <a href="/ja/docs/Web/JavaScript/Reference/Statements/for">for loop</a> を使用しました (<code>for(var j = 0; j < current.keywords.length; j++) { ... }</code>) を使って、それぞれの記事に関連するすべてのキーワードをループさせ、それぞれのキーワードを {{htmlelement("span")}} に挿入し、<code><p></code> の中に入れています。これは、それぞれの記事のスタイルを簡単にするために行われました。</li> + <li><code>if()</code> ブロック ( <code>if(current.multimedia.length > 0) { ... }</code>) を使って、各記事に関連する画像があるかどうかをチェックしています (記事によってはないものもあります)。</li> + <li><code><div></code> 要素に "clearfix" というクラスを与えたので、簡単にクリアリングを適用することができます。</li> + </ul> + </li> +</ul> + +<h3 id="ページネーションボタンの配線">ページネーションボタンの配線</h3> + +<p>ページ分割ボタンを動作させるために、<code>pageNumber</code> 変数の値をインクリメント (またはデクリメント) し、ページ URL パラメーターに含まれる新しい値でフェッチリクエストを再実行します。これは、NYTimes API が一度に 10 件の結果しか返さないからです — 10 件以上の結果が利用可能な場合、<code>page</code> URL パラメーターが 0 に設定されている場合は最初の 10 (0-9) を (または全く含まれない — 0 がデフォルト値です。) 1 に設定されている場合は次の 10 (10-19) を返します。</p> + +<p>これにより、単純なページネーション関数を簡単に書くことができるようになりました。</p> + +<ol> + <li> + <p>既存の <code><a href="/ja/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code> コールの下に、関連するボタンがクリックされたときに <code>nextPage()</code> および <code>previousPage()</code> 関数が呼び出されるように、これら 2 つの新しいものを追加します:</p> + + <pre class="brush: js notranslate">nextBtn.addEventListener('click', nextPage); +previousBtn.addEventListener('click', previousPage);</pre> + </li> + <li> + <p>前回の追加の下に、2 つの関数を定義してみましょう — 今すぐこのコードを追加します:</p> + + <pre class="brush: js notranslate">function nextPage(e) { + pageNumber++; + fetchResults(e); +}; + +function previousPage(e) { + if(pageNumber > 0) { + pageNumber--; + } else { + return; + } + fetchResults(e); +};</pre> + + <p>最初の関数は単純で、変数 <code>pageNumber</code> をインクリメントしてから、次のページの結果を表示するために <code>fetchResults()</code> 関数を再度実行します。</p> + + <p>2 番目の関数は逆の方法でほぼ正確に同じように動作しますが、<code>pageNumber</code> がすでに 0 ではないことを確認するという余分なステップを踏まなければなりません — もしフェッチリクエストがマイナスの <code>page</code> パラメーターで実行された場合、エラーを引き起こす可能性があります。もし <code>pageNumber</code> がすでに 0 であれば、処理能力を無駄にしないように、単に関数から <code><a href="/ja/docs/Web/JavaScript/Reference/Statements/return">return</a></code> します (すでに最初のページにいるのであれば、同じ結果を再び読み込む必要はありません)。</p> + </li> +</ol> + +<div class="blockIndicator note"> +<p><strong>Note</strong>: 完成した <a href="https://github.com/mdn/learning-area/blob/master/javascript/apis/third-party-apis/nytimes/index.html">nytimes API のサンプルコードは GitHub で見ることができます</a> (<a href="https://mdn.github.io/learning-area/javascript/apis/third-party-apis/nytimes/">ここでもライブで動作しています</a>) 。</p> +</div> + +<h2 id="YouTube_の例">YouTube の例</h2> + +<p>また、<a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/third-party-apis/youtube">YouTube video search example</a> をご覧ください。これは 2 つの関連する API を使用しています。</p> + +<ul> + <li>YouTube の動画を検索して結果を返す <a href="https://developers.google.com/youtube/v3/docs/">YouTube Data API</a>。</li> + <li>返された動画の例を IFrame ビデオプレーヤー内に表示して視聴できるようにするための <a href="https://developers.google.com/youtube/iframe_api_reference">YouTube IFrame Player API</a> です。</li> +</ul> + +<p>この例は、2つの関連するサードパーティ API を一緒に使用してアプリを構築していることを示しているので興味深いです。1 つ目は RESTful API で、2 つ目は Mapquest のように動作します (API 固有のメソッドなどがあります)。ただし、どちらの API もページに適用するために JavaScript ライブラリを必要とする点は注目に値します。RESTful API には、HTTP リクエストを行い、結果を返すための関数が用意されています。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/14823/youtube-example.png" style="border-style: solid; border-width: 1px; display: block; height: 389px; margin: 0px auto; width: 700px;"></p> + +<p>この例については、記事の中ではあまり多くを語るつもりはありません。<a href="https://github.com/mdn/learning-area/tree/master/javascript/apis/third-party-apis/youtube">ソースコード</a>には、それがどのように動作するかを説明するために、その中に詳細なコメントが挿入されています。</p> + +<p>稼動させるために必要です:</p> + +<ul> + <li><a href="https://cloud.google.com/">Google Cloud</a> から API キーを取得します。</li> + <li>ソースコードから <code>ENTER-API-KEY-HERE</code> という文字列を見つけ、それを API キーに置き換えます。</li> + <li>ウェブサーバー経由でサンプルを実行します。ブラウザーで直接実行した場合 (つまり <code>file://</code> URL を経由した場合) は動作しません。</li> +</ul> + +<h2 id="まとめ">まとめ</h2> + +<p>この記事では、サードパーティ API を使用してウェブサイトに機能を追加するための便利な方法を紹介しました。</p> + +<p>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Fetching_data", "Learn/JavaScript/Client-side_web_APIs/Drawing_graphics", "Learn/JavaScript/Client-side_web_APIs")}}</p> + +<h2 id="このモジュール">このモジュール</h2> + +<div> +<ul> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Web API の紹介</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">ドキュメントの操作</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">サーバーからのデータ取得</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">サードパーティ API</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">グラフィックの描画</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">動画と音声の API</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">クライアント側ストレージ</a></li> +</ul> +</div> diff --git a/files/ja/learn/javascript/client-side_web_apis/video_and_audio_apis/index.html b/files/ja/learn/javascript/client-side_web_apis/video_and_audio_apis/index.html new file mode 100644 index 0000000000..09e6e27ca7 --- /dev/null +++ b/files/ja/learn/javascript/client-side_web_apis/video_and_audio_apis/index.html @@ -0,0 +1,507 @@ +--- +title: 動画と音声の API +slug: Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs +tags: + - API + - Audio + - Beginner + - CodingScripting + - Guide + - JavaScript + - Learn + - Video + - 記事 +translation_of: Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs +--- +<div>{{LearnSidebar}}</div> + +<div>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Drawing_graphics", "Learn/JavaScript/Client-side_web_APIs/Client-side_storage", "Learn/JavaScript/Client-side_web_APIs")}}</div> + +<p class="summary">HTML5 comes with elements for embedding rich media in documents — {{htmlelement("video")}} and {{htmlelement("audio")}} — which in turn come with their own APIs for controlling playback, seeking, etc. This article shows you how to do common tasks such as creating custom playback controls.</p> + +<table class="learn-box standard-table"> + <tbody> + <tr> + <th scope="row">前提条件:</th> + <td>JavaScript basics (see <a href="/en-US/docs/Learn/JavaScript/First_steps">first steps</a>, <a href="/en-US/docs/Learn/JavaScript/Building_blocks">building blocks</a>, <a href="/en-US/docs/Learn/JavaScript/Objects">JavaScript objects</a>), the <a href="/en-US/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">basics of Client-side APIs</a></td> + </tr> + <tr> + <th scope="row">目的:</th> + <td>To learn how to use browser APIs to control video and audio playback.</td> + </tr> + </tbody> +</table> + +<h2 id="HTML5_video_と_audio">HTML5 video と audio</h2> + +<p>The {{htmlelement("video")}} and {{htmlelement("audio")}} elements allow us to embed video and audio into web pages. As we showed in <a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">Video and audio content</a>, a typical implementation looks like this:</p> + +<pre class="brush: html notranslate"><video controls> + <source src="rabbit320.mp4" type="video/mp4"> + <source src="rabbit320.webm" type="video/webm"> + <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p> +</video></pre> + +<p>This creates a video player inside the browser like so:</p> + +<p>{{EmbedGHLiveSample("learning-area/html/multimedia-and-embedding/video-and-audio-content/multiple-video-formats.html", '100%', 380)}}</p> + +<p>You can review what all the HTML features do in the article linked above; for our purposes here, the most interesting attribute is {{htmlattrxref("controls", "video")}}, which enables the default set of playback controls. If you don't specify this, you get no playback controls:</p> + +<p>{{EmbedGHLiveSample("learning-area/html/multimedia-and-embedding/video-and-audio-content/multiple-video-formats-no-controls.html", '100%', 380)}}</p> + +<p>This is not as immediately useful for video playback, but it does have advantages. One big issue with the native browser controls is that they are different in each browser — not very good for cross-browser support! Another big issue is that the native controls in most browsers aren't very keyboard-accessible.</p> + +<p>You can solve both these problems by hiding the native controls (by removing the <code>controls</code> attribute), and programming your own with HTML, CSS, and JavaScript. In the next section we'll look at the basic tools we have available to do this.</p> + +<h2 id="The_HTMLMediaElement_API">The HTMLMediaElement API</h2> + +<p>Part of the HTML5 spec, the {{domxref("HTMLMediaElement")}} API provides features to allow you to control video and audio players programmatically — for example {{domxref("HTMLMediaElement.play()")}}, {{domxref("HTMLMediaElement.pause()")}}, etc. This interface is available to both {{htmlelement("audio")}} and {{htmlelement("video")}} elements, as the features you'll want to implement are nearly identical. Let's go through an example, adding features as we go.</p> + +<p>Our finished example will look (and function) something like the following:</p> + +<p>{{EmbedGHLiveSample("learning-area/javascript/apis/video-audio/finished/", '100%', 360)}}</p> + +<h3 id="Getting_started">Getting started</h3> + +<p>To get started with this example, <a href="https://github.com/mdn/learning-area/raw/master/javascript/apis/video-audio/start/media-player-start.zip">download our media-player-start.zip</a> and unzip it into a new directory on your hard drive. If you <a href="https://github.com/mdn/learning-area">downloaded our examples repo</a>, you'll find it in <code>javascript/apis/video-audio/start/</code></p> + +<p>At this point, if you load the HTML you should see a perfectly normal HTML5 video player, with the native controls rendered.</p> + +<h4 id="Exploring_the_HTML">Exploring the HTML</h4> + +<p>Open the HTML index file. You'll see a number of features; the HTML is dominated by the video player and its controls:</p> + +<pre class="notranslate"><div class="player"> + <video controls> + <source src="video/sintel-short.mp4" type="video/mp4"> + <source src="video/sintel-short.webm" type="video/webm"> + <!-- fallback content here --> + </video> + <div class="controls"> + <button class="play" data-icon="P" aria-label="play pause toggle"></button> + <button class="stop" data-icon="S" aria-label="stop"></button> + <div class="timer"> + <div></div> + <span aria-label="timer">00:00</span> + </div> + <button class="rwd" data-icon="B" aria-label="rewind"></button> + <button class="fwd" data-icon="F" aria-label="fast forward"></button> + </div> +</div> +</pre> + +<ul> + <li>The whole player is wrapped in a {{htmlelement("div")}} element, so it can all be styled as one unit if needed.</li> + <li>The {{htmlelement("video")}} element contains two {{htmlelement("source")}} elements so that different formats can be loaded depending on the browser viewing the site.</li> + <li>The controls HTML is probably the most interesting: + <ul> + <li>We have four {{htmlelement("button")}}s — play/pause, stop, rewind, and fast forward.</li> + <li>Each <code><button></code> has a <code>class</code> name, a <code>data-icon</code> attribute for defining what icon should be shown on each button (we'll show how this works in the below section), and an <code>aria-label</code> attribute to provide an understandable description of each button, since we're not providing a human-readable label inside the tags. The contents of <code>aria-label</code> attributes are read out by screenreaders when their users focus on the elements that contain them.</li> + <li>There is also a timer {{htmlelement("div")}}, which will report the elapsed time when the video is playing. Just for fun, we are providing two reporting mechanisms — a {{htmlelement("span")}} containing the elapsed time in minutes and seconds, and an extra <code><div></code> that we will use to create a horizontal indicator bar that gets longer as the time elapses. To get an idea of what the finished product will look like, <a href="https://mdn.github.io/learning-area/javascript/apis/video-audio/finished/">check out our finished version</a>.</li> + </ul> + </li> +</ul> + +<h4 id="Exploring_the_CSS">Exploring the CSS</h4> + +<p>Now open the CSS file and have a look inside. The CSS for the example is not too complicated, but we'll highlight the most interesting bits here. First of all, notice the <code>.controls</code> styling:</p> + +<pre class="brush: css notranslate">.controls { + visibility: hidden; + opacity: 0.5; + width: 400px; + border-radius: 10px; + position: absolute; + bottom: 20px; + left: 50%; + margin-left: -200px; + background-color: black; + box-shadow: 3px 3px 5px black; + transition: 1s all; + display: flex; +} + +.player:hover .controls, player:focus .controls { + opacity: 1; +} +</pre> + +<ul> + <li>We start off with the {{cssxref("visibility")}} of the custom controls set to <code>hidden</code>. In our JavaScript later on, we will set the controls to <code>visible</code>, and remove the <code>controls</code> attribute from the <code><video></code> element. This is so that, if the JavaScript doesn't load for some reason, users can still use the video with the native controls.</li> + <li>We give the controls an {{cssxref("opacity")}} of 0.5 by default, so that they are less distracting when you are trying to watch the video. Only when you are hovering/focusing over the player do the controls appear at full opacity.</li> + <li>We lay out the buttons inside the control bar using Flexbox ({{cssxref("display")}}: flex), to make things easier.</li> +</ul> + +<p>Next, let's look at our button icons:</p> + +<pre class="brush: css notranslate">@font-face { + font-family: 'HeydingsControlsRegular'; + src: url('fonts/heydings_controls-webfont.eot'); + src: url('fonts/heydings_controls-webfont.eot?#iefix') format('embedded-opentype'), + url('fonts/heydings_controls-webfont.woff') format('woff'), + url('fonts/heydings_controls-webfont.ttf') format('truetype'); + font-weight: normal; + font-style: normal; +} + +button:before { + font-family: HeydingsControlsRegular; + font-size: 20px; + position: relative; + content: attr(data-icon); + color: #aaa; + text-shadow: 1px 1px 0px black; +}</pre> + +<p>First of all, at the top of the CSS we use a {{cssxref("@font-face")}} block to import a custom web font. This is an icon font — all the characters of the alphabet equate to common icons you might want to use in an application.</p> + +<p>Next we use generated content to display an icon on each button:</p> + +<ul> + <li>We use the {{cssxref("::before")}} selector to display the content before each {{htmlelement("button")}} element.</li> + <li>We use the {{cssxref("content")}} property to set the content to be displayed in each case to be equal to the contents of the <code><a href="/en-US/docs/Learn/HTML/Howto/Use_data_attributes">data-icon</a></code> attribute. In the case of our play button, <code>data-icon</code> contains a capital "P".</li> + <li>We apply the custom web font to our buttons using {{cssxref("font-family")}}. In this font, "P" is actually a "play" icon, so therefore the play button has a "play" icon displayed on it.</li> +</ul> + +<p>Icon fonts are very cool for many reasons — cutting down on HTTP requests because you don't need to download those icons as image files, great scalability, and the fact that you can use text properties to style them — like {{cssxref("color")}} and {{cssxref("text-shadow")}}.</p> + +<p>Last but not least, let's look at the CSS for the timer:</p> + +<pre class="brush: css notranslate">.timer { + line-height: 38px; + font-size: 10px; + font-family: monospace; + text-shadow: 1px 1px 0px black; + color: white; + flex: 5; + position: relative; +} + +.timer div { + position: absolute; + background-color: rgba(255,255,255,0.2); + left: 0; + top: 0; + width: 0; + height: 38px; + z-index: 2; +} + +.timer span { + position: absolute; + z-index: 3; + left: 19px; +}</pre> + +<ul> + <li>We set the outer <code>.timer</code> <code><div></code> to have flex: 5, so it takes up most of the width of the controls bar. We also give it {{cssxref("position")}}<code>: relative</code>, so that we can position elements inside it conveniently according to it's boundaries, and not the boundaries of the {{htmlelement("body")}} element.</li> + <li>The inner <code><div></code> is absolutely positioned to sit directly on top of the outer <code><div></code>. It is also given an initial width of 0, so you can't see it at all. As the video plays, the width will be increased via JavaScript as the video elapses.</li> + <li>The <code><span></code> is also absolutely positioned to sit near the left hand side of the timer bar.</li> + <li>We also give our inner <code><div></code> and <code><span></code> the right amount of {{cssxref("z-index")}} so that the timer will be displayed on top, and the inner <code><div></code> below that. This way, we make sure we can see all the information — one box is not obscuring another.</li> +</ul> + +<h3 id="Implementing_the_JavaScript">Implementing the JavaScript</h3> + +<p>We've got a fairly complete HTML and CSS interface already; now we just need to wire up all the buttons to get the controls working.</p> + +<ol> + <li> + <p>Create a new JavaScript file in the same directory level as your index.html file. Call it <code>custom-player.js</code>.</p> + </li> + <li> + <p>At the top of this file, insert the following code:</p> + + <pre class="brush: js notranslate">const media = document.querySelector('video'); +const controls = document.querySelector('.controls'); + +const play = document.querySelector('.play'); +const stop = document.querySelector('.stop'); +const rwd = document.querySelector('.rwd'); +const fwd = document.querySelector('.fwd'); + +const timerWrapper = document.querySelector('.timer'); +const timer = document.querySelector('.timer span'); +const timerBar = document.querySelector('.timer div'); +</pre> + + <p>Here we are creating constants to hold references to all the objects we want to manipulate. We have three groups:</p> + + <ul> + <li>The <code><video></code> element, and the controls bar.</li> + <li>The play/pause, stop, rewind, and fast forward buttons.</li> + <li>The outer timer wrapper <code><div></code>, the digital timer readout <code><span></code>, and the inner <code><div></code> that gets wider as the time elapses.</li> + </ul> + </li> + <li> + <p>Next, insert the following at the bottom of your code:</p> + + <pre class="brush: js notranslate">media.removeAttribute('controls'); +controls.style.visibility = 'visible';</pre> + + <p>These two lines remove the default browser controls from the video, and make the custom controls visible.</p> + </li> +</ol> + +<h4 id="Playing_and_pausing_the_video">Playing and pausing the video</h4> + +<p>Let's implement probably the most important control — the play/pause button.</p> + +<ol> + <li> + <p>First of all, add the following to the bottom of your code, so that the <code>playPauseMedia()</code> function is invoked when the play button is clicked:</p> + + <pre class="brush: js notranslate">play.addEventListener('click', playPauseMedia); +</pre> + </li> + <li> + <p>Now to define <code>playPauseMedia()</code> — add the following, again at the bottom of your code:</p> + + <pre class="brush: js notranslate">function playPauseMedia() { + if(media.paused) { + play.setAttribute('data-icon','u'); + media.play(); + } else { + play.setAttribute('data-icon','P'); + media.pause(); + } +}</pre> + + <p>Here we use an <code><a href="/en-US/docs/Web/JavaScript/Reference/Statements/if...else">if</a></code> statement to check whether the video is paused. The {{domxref("HTMLMediaElement.paused")}} property returns true if the media is paused, which is any time the video is not playing, including when it is set at 0 duration after it first loads. If it is paused, we set the <code>data-icon</code> attribute value on the play button to "u", which is a "paused" icon, and invoke the {{domxref("HTMLMediaElement.play()")}} method to play the media.</p> + + <p>On the second click, the button will be toggled back again — the "play" icon will be shown again, and the video will be paused with {{domxref("HTMLMediaElement.pause()")}}.</p> + </li> +</ol> + +<h4 id="Stopping_the_video">Stopping the video</h4> + +<ol> + <li> + <p>Next, let's add functionality to handle stopping the video. Add the following <code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code> lines below the previous one you added:</p> + + <pre class="brush: js notranslate">stop.addEventListener('click', stopMedia); +media.addEventListener('ended', stopMedia); +</pre> + + <p>The {{event("click")}} event is obvious — we want to stop the video by running our <code>stopMedia()</code> function when the stop button is clicked. We do however also want to stop the video when it finishes playing — this is marked by the {{event("ended")}} event firing, so we also set up a listener to run the function on that event firing too.</p> + </li> + <li> + <p>Next, let's define <code>stopMedia()</code> — add the following function below <code>playPauseMedia()</code>:</p> + + <pre class="brush: js notranslate">function stopMedia() { + media.pause(); + media.currentTime = 0; + play.setAttribute('data-icon','P'); +} +</pre> + + <p>there is no <code>stop()</code> method on the HTMLMediaElement API — the equivalent is to <code>pause()</code> the video, and set its {{domxref("HTMLMediaElement.currentTime","currentTime")}} property to 0. Setting <code>currentTime</code> to a value (in seconds) immediately jumps the media to that position.</p> + + <p>All there is left to do after that is to set the displayed icon to the "play" icon. Regardless of whether the video was paused or playing when the stop button is pressed, you want it to be ready to play afterwards.</p> + </li> +</ol> + +<h4 id="Seeking_back_and_forth">Seeking back and forth</h4> + +<p>There are many ways that you can implement rewind and fast forward functionality; here we are showing you a relatively complex way of doing it, which doesn't break when the different buttons are pressed in an unexpected order.</p> + +<ol> + <li> + <p>First of all, add the following two <code><a href="/en-US/docs/Web/API/EventTarget/addEventListener">addEventListener()</a></code> lines below the previous ones:</p> + + <pre class="brush: js notranslate">rwd.addEventListener('click', mediaBackward); +fwd.addEventListener('click', mediaForward); +</pre> + </li> + <li> + <p>Now on to the event handler functions — add the following code below your previous functions to define <code>mediaBackward()</code> and <code>mediaForward()</code>:</p> + + <pre class="brush: js notranslate">let intervalFwd; +let intervalRwd; + +function mediaBackward() { + clearInterval(intervalFwd); + fwd.classList.remove('active'); + + if(rwd.classList.contains('active')) { + rwd.classList.remove('active'); + clearInterval(intervalRwd); + media.play(); + } else { + rwd.classList.add('active'); + media.pause(); + intervalRwd = setInterval(windBackward, 200); + } +} + +function mediaForward() { + clearInterval(intervalRwd); + rwd.classList.remove('active'); + + if(fwd.classList.contains('active')) { + fwd.classList.remove('active'); + clearInterval(intervalFwd); + media.play(); + } else { + fwd.classList.add('active'); + media.pause(); + intervalFwd = setInterval(windForward, 200); + } +} +</pre> + + <p>You'll notice that first we initialize two variables — <code>intervalFwd</code> and <code>intervalRwd</code> — you'll find out what they are for later on.</p> + + <p>Let's step through <code>mediaBackward()</code> (the functionality for <code>mediaForward()</code> is exactly the same, but in reverse):</p> + + <ol> + <li>We clear any classes and intervals that are set on the fast forward functionality — we do this because if we press the <code>rwd</code> button after pressing the <code>fwd</code> button, we want to cancel any fast forward functionality and replace it with the rewind functionality. If we tried to do both at one, the player would break.</li> + <li>We use an <code>if</code> statement to check whether the <code>active</code> class has been set on the <code>rwd</code> button, indicating that it has already been pressed. The {{domxref("Element.classList", "classList")}} is a rather handy property that exists on every element — it contains a list of all the classes set on the element, as well as methods for adding/removing classes, etc. We use the <code>classList.contains()</code> method to check whether the list contains the <code>active</code> class. This returns a boolean <code>true</code>/<code>false</code> result.</li> + <li>If <code>active</code> has been set on the <code>rwd</code> button, we remove it using <code>classList.remove()</code>, clear the interval that has been set when the button was first pressed (see below for more explanation), and use {{domxref("HTMLMediaElement.play()")}} to cancel the rewind and start the video playing normally.</li> + <li>If it hasn't yet been set, we add the <code>active</code> class to the <code>rwd</code> button using <code>classList.add()</code>, pause the video using {{domxref("HTMLMediaElement.pause()")}}, then set the <code>intervalRwd</code> variable to equal a {{domxref("WindowOrWorkerGlobalScope.setInterval", "setInterval()")}} call. When invoked, <code>setInterval()</code> creates an active interval, meaning that it runs the function given as the first parameter every x milliseconds, where x is the value of the 2nd parameter. So here we are running the <code>windBackward()</code> function every 200 milliseconds — we'll use this function to wind the video backwards constantly. To stop a {{domxref("WindowOrWorkerGlobalScope.setInterval", "setInterval()")}} running, you have to call {{domxref("WindowOrWorkerGlobalScope.clearInterval", "clearInterval()")}}, giving it the identifying name of the interval to clear, which in this case is the variable name <code>intervalRwd</code> (see the <code>clearInterval()</code> call earlier on in the function).</li> + </ol> + </li> + <li> + <p>Finally, we need to define the <code>windBackward()</code> and <code>windForward()</code> functions invoked in the <code>setInterval()</code> calls. Add the following below your two previous functions:</p> + + <pre class="brush: js notranslate">function windBackward() { + if(media.currentTime <= 3) { + rwd.classList.remove('active'); + clearInterval(intervalRwd); + stopMedia(); + } else { + media.currentTime -= 3; + } +} + +function windForward() { + if(media.currentTime >= media.duration - 3) { + fwd.classList.remove('active'); + clearInterval(intervalFwd); + stopMedia(); + } else { + media.currentTime += 3; + } +}</pre> + + <p>Again, we'll just run through the first one of these functions as they work almost identically, but in reverse to one another. In <code>windBackward()</code> we do the following — bear in mind that when the interval is active, this function is being run once every 200 milliseconds.</p> + + <ol> + <li>We start off with an <code>if</code> statement that checks to see whether the current time is less than 3 seconds, i.e., if rewinding by another three seconds would take it back past the start of the video. This would cause strange behaviour, so if this is the case we stop the video playing by calling <code>stopMedia()</code>, remove the <code>active</code> class from the rewind button, and clear the <code>intervalRwd</code> interval to stop the rewind functionality. If we didn't do this last step, the video would just keep rewinding forever.</li> + <li>If the current time is not within 3 seconds of the start of the video, we simply remove three seconds from the current time by executing <code>media.currentTime -= 3</code>. So in effect, we are rewinding the video by 3 seconds, once every 200 milliseconds.</li> + </ol> + </li> +</ol> + +<h4 id="Updating_the_elapsed_time">Updating the elapsed time</h4> + +<p>The very last piece of our media player to implement is the time elapsed displays. To do this we'll run a function to update the time displays every time the {{event("timeupdate")}} event is fired on the <code><video></code> element. The frequency with which this event fires depends on your browser, CPU power, etc (<a href="http://stackoverflow.com/questions/9678177/how-often-does-the-timeupdate-event-fire-for-an-html5-video">see this stackoverflow post</a>).</p> + +<p>Add the following <code>addEventListener()</code> line just below the others:</p> + +<pre class="brush: js notranslate">media.addEventListener('timeupdate', setTime);</pre> + +<p>Now to define the <code>setTime()</code> function. Add the following at the bottom of your file:</p> + +<pre class="brush: js notranslate">function setTime() { + let minutes = Math.floor(media.currentTime / 60); + let seconds = Math.floor(media.currentTime - minutes * 60); + let minuteValue; + let secondValue; + + if (minutes < 10) { + minuteValue = '0' + minutes; + } else { + minuteValue = minutes; + } + + if (seconds < 10) { + secondValue = '0' + seconds; + } else { + secondValue = seconds; + } + + let mediaTime = minuteValue + ':' + secondValue; + timer.textContent = mediaTime; + + let barLength = timerWrapper.clientWidth * (media.currentTime/media.duration); + timerBar.style.width = barLength + 'px'; +} +</pre> + +<p>This is a fairly long function, so let's go through it step by step:</p> + +<ol> + <li>First of all, we work out the number of minutes and seconds in the {{domxref("HTMLMediaElement.currentTime")}} value.</li> + <li>Then we initialize two more variables — <code>minuteValue</code> and <code>secondValue</code>.</li> + <li>The two <code>if</code> statements work out whether the number of minutes and seconds are less than 10. If so, they add a leading zero to the values, in the same way that a digital clock display works.</li> + <li>The actual time value to display is set as <code>minuteValue</code> plus a colon character plus <code>secondValue</code>.</li> + <li>The {{domxref("Node.textContent")}} value of the timer is set to the time value, so it displays in the UI.</li> + <li>The length we should set the inner <code><div></code> to is worked out by first working out the width of the outer <code><div></code> (any element's {{domxref("Element.clientWidth", "clientWidth")}} property will contain its length), and then multiplying it by the {{domxref("HTMLMediaElement.currentTime")}} divided by the total {{domxref("HTMLMediaElement.duration")}} of the media.</li> + <li>We set the width of the inner <code><div></code> to equal the calculated bar length, plus "px", so it will be set to that number of pixels.</li> +</ol> + +<h4 id="Fixing_play_and_pause">Fixing play and pause</h4> + +<p>There is one problem left to fix. If the play/pause or stop buttons are pressed while the rewind or fast forward functionality is active, they just don't work. How can we fix it so that they cancel the <code>rwd</code>/<code>fwd</code> button functionality and play/stop the video as you'd expect? This is fairly easy to fix.</p> + +<p>First of all, add the following lines inside the <code>stopMedia()</code> function — anywhere will do:</p> + +<pre class="brush: js notranslate">rwd.classList.remove('active'); +fwd.classList.remove('active'); +clearInterval(intervalRwd); +clearInterval(intervalFwd); +</pre> + +<p>Now add the same lines again, at the very start of the <code>playPauseMedia()</code> function (just before the start of the <code>if</code> statement).</p> + +<p>At this point, you could delete the equivalent lines from the <code>windBackward()</code> and <code>windForward()</code> functions, as that functionality has been implemented in the <code>stopMedia()</code> function instead.</p> + +<p>Note: You could also further improve the efficiency of the code by creating a separate function that runs these lines, then calling that anywhere it is needed, rather than repeating the lines multiple times in the code. But we'll leave that one up to you.</p> + +<h2 id="Summary">Summary</h2> + +<p>I think we've taught you enough in this article. The {{domxref("HTMLMediaElement")}} API makes a wealth of functionality available for creating simple video and audio players, and that's only the tip of the iceberg. See the "See also" section below for links to more complex and interesting functionality.</p> + +<p>Here are some suggestions for ways you could enhance the existing example we've built up:</p> + +<ol> + <li> + <p>The time display currently breaks if the video is an hour long or more (well, it won't display hours; just minutes and seconds). Can you figure out how to change the example to make it display hours?</p> + </li> + <li> + <p>Because <code><audio></code> elements have the same {{domxref("HTMLMediaElement")}} functionality available to them, you could easily get this player to work for an <code><audio></code> element too. Try doing so.</p> + </li> + <li> + <p>Can you work out a way to turn the timer inner <code><div></code> element into a true seek bar/scrobbler — i.e., when you click somewhere on the bar, it jumps to that relative position in the video playback? As a hint, you can find out the X and Y values of the element's left/right and top/bottom sides via the <code><a href="/en-US/docs/Web/API/Element/getBoundingClientRect">getBoundingClientRect()</a></code> method, and you can find the coordinates of a mouse click via the event object of the click event, called on the {{domxref("Document")}} object. For example:</p> + + <pre class="brush: js notranslate">document.onclick = function(e) { + console.log(e.x) + ',' + console.log(e.y) +}</pre> + </li> +</ol> + +<h2 id="See_also">See also</h2> + +<ul> + <li>{{domxref("HTMLMediaElement")}}</li> + <li><a href="/en-US/docs/Learn/HTML/Multimedia_and_embedding/Video_and_audio_content">Video and audio content</a> — simple guide to <code><video></code> and <code><audio></code> HTML.</li> + <li><a href="/en-US/docs/Web/Apps/Fundamentals/Audio_and_video_delivery">Audio and video delivery</a> — detailed guide to delivering media inside the browser, with many tips, tricks, and links to further more advanced tutorials.</li> + <li><a href="/en-US/docs/Web/Apps/Fundamentals/Audio_and_video_manipulation">Audio and video manipulation</a> — detailed guide to manipulating audio and video, e.g. with <a href="/en-US/docs/Web/API/Canvas_API">Canvas API</a>, <a href="/en-US/docs/Web/API/Web_Audio_API">Web Audio API</a>, and more.</li> + <li>{{htmlelement("video")}} and {{htmlelement("audio")}} reference pages.</li> + <li><a href="/en-US/docs/Web/Media/Formats">Guide to media types and formats on the web</a></li> +</ul> + +<p>{{PreviousMenuNext("Learn/JavaScript/Client-side_web_APIs/Drawing_graphics", "Learn/JavaScript/Client-side_web_APIs/Client-side_storage", "Learn/JavaScript/Client-side_web_APIs")}}</p> + +<h2 id="このモジュール">このモジュール</h2> + +<ul> + <li><a href="/docs/Learn/JavaScript/Client-side_web_APIs/Introduction">Web API の紹介</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Manipulating_documents">ドキュメントの操作</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Fetching_data">サーバからのデータ取得</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Third_party_APIs">サードパーティ API</a></li> + <li><a href="/docs/Learn/JavaScript/Client-side_web_APIs/Drawing_graphics">グラフィックの描画</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Video_and_audio_APIs">動画と音声の API</a></li> + <li><a href="/ja/docs/Learn/JavaScript/Client-side_web_APIs/Client-side_storage">クライアント側ストレージ</a></li> +</ul> |