このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
ブラウザでJavaScriptのプログラムを動かすには、HTMLファイルに埋め込 むか、HTMLファイルよりプログラムファイルを読み込みます。 これには、HTMLの script 要素を使用します。
そのため、DOMに対するプログラムを動作させるには、次のような方法が あります。
さらに、表示したHTML文書に対して、ユーザからのインタラクティブな操 作に対応するには、 ユーザ操作などで生じるイベントに対応した動作を記述する必要がありま す。 特定の要素に対するイベントを処理するプログラムを書くには、 その要素にイベントハンドラコンテンツ属性を指定し、 必要な処理をイベントハンドラとして渡す必要があります。
HTML からJavaScript を動かすには次の方法があります。
ブラウザで HTML の読み込みにより、プログラムを動かしたい場合、 以下の方法がある。
属性にプログラムを直接書く場合、ダブルクォーテーションマーク(")を " でエスケープする必要があります。
script 要素の属性 src で URL でファイルを指定する場合、読み込み方 を指定することができる。 デフォルトでは、指定された場合、そのファイルを読み込んでプログラム を実行します。 そのため、プログラムの内容が単なる関数定義で、ユーザ操作に連動する ような場合、ファイル読み込みの時間にHTMLの表示などが止まることがあ ります。
head 内に置かれた script は body 文書が読み込まれる前に実行されま す。 そのため、文書に関する DOM はすべて参照できません。 プログラムと文書を独立させるために、プログラムを置くのには有効です が、文書を直接操作するプログラムは書けません。
body 要素内の script 要素でプログラムが置かれた場合、デフォルトの 動作では、そこまで文書が解釈された状態で実行されます。 そのため、script 以降のDOM要素は参照できません。 DOM全体に作用させるには defer 属性を指定させるか、 body に onload で関数を呼び出すかする必要がある。
ブラウザでは、クリックされるなどの様々なイベントが発生します。 イベントはオブジェクトで、 DOMのオブジェクトに登録されたイベントハンドラと言う、イベントを引 数とした関数オブジェクトが呼び出されます。
イベントに関しては、 HTML standard の Eventsの章 にまとめてあります。 但し、ここにあるほかにも、メディア要素イベントやドラッグアンドドロップ イベントが定義されています。 8.1.8.2 章のEvent handlers on elements, Document objects, and Window objectsにも載ってます。 主要なものを下記に示します。
HTMLの要素には共通のイベントハンドラコンテンツ属性がたくさん定義さ れています。 "on"+イベント名がそれぞれのHTML要素で許されていて、イベントハンドラを 指定できます。
古くは、input のボタンなどに onclick を指定して文字列の中にプログ ラムを埋め込んでいました。 新しい DOM では、イベントを定義していて、 イベントリスナにイベントと関数の対を追加するようになっています。 例12-1
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>例12-1</title>
<style>
.white{background:white;}
.red { background: red;}
.blue {background: blue;}
.green {background: green;}
</style>
</head>
<body>
<p id="target">
Hello world
</p>
<p>
<input type="button" value="red" id="inputelement">
<input type="button" value="blue" onclick="change('blue')">
<a id="aelement">green</a>
<a onclick="change('blue');">blue</a>
<select id="selector">
<option>white</option>
<option>red</option>
<option>blue</option>
<option>green</option>
</select>
</p>
<p id="pelement">
hover
</p>
<script>
const target=document.getElementById("target");
function change(x){
target.setAttribute('class',x);
}
const inputElement = document.getElementById('inputelement');
inputElement.addEventListener('click',(event)=>{
change('red');});
const aElement = document.getElementById('aelement');
aElement.addEventListener('click',(event)=>{
change('green');});
const selectElement = document.getElementById('selector');
selectElement.addEventListener('change', (event) => {
change(event.target.value);});
const pElement = document.getElementById('pelement');
pElement.addEventListener('mouseenter',(event)=>{
change('red');});
pElement.addEventListener('click',(event)=>{
change('blue');});
pElement.addEventListener('mouseleave',(event)=>{
change('white');});
</script>
</body>
</html>
なお、イベントは、受け取ったオブジェクトからDOMの木構造 の Document オブジェクトまでのオブジェクトのパスを辿るように、イベ ントが伝搬されます。これはバブリングと呼ばれます。
また、イベント自体は予め定義されていたものだけではなく、自由に文字 列で定義して、プログラムから dispatch できます。
バブリングの例 ex1.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>バブリングの例</title>
<style>
.red {background-color: red;}
.blue {background-color: blue;}
.white {background-color: white;}
</style>
</head>
<body>
<h1 class="white">バブリングの例</h1>
<ol id="www">
<li id="rrr">red</li>
<li id="bbb">blue</li>
</ol>
<script>
function dispatchred(e) {
this.dispatchEvent(new Event("r", { bubbles: true }));
}
function dispatchblue(e) {
this.dispatchEvent(new Event("b", { bubbles: true }));
}
function dispatchwhite(e) {
this.dispatchEvent(new Event("w", { bubbles: true }));
}
function setblue(e) {
this.setAttribute("class", "blue");
}
function setred(e) {
this.setAttribute("class", "red");
}
function setwhite(e) {
this.setAttribute("class", "white");
}
function setEventListeners(elem, disp) {
elem.addEventListener("click", disp, { capture: true });
elem.addEventListener("w", setwhite, { capture: true });
elem.addEventListener("b", setblue, { capture: true });
elem.addEventListener("r", setred, { capture: true });
}
setEventListeners(document.body, dispatchwhite);
setEventListeners(document.getElementById("www"), dispatchwhite);
setEventListeners(document.getElementById("rrr"), dispatchred);
setEventListeners(document.getElementById("bbb"), dispatchblue);
</script>
</body>
</html>
onload など、ブラウザの状態などにより、イベントが生成された場合、 それに対応させたプログラムを実行させることができます。
例12-3はJavaScriptの読む位置やオプショ ンにより、参照できる範囲が異なることを示しています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Defer テスト</title>
<style>
.redmark {background:red;}
</style>
<script>
// × head では DOM は見えない
document.getElementById("id1").setAttribute("class","redmark");
</script>
</head>
<body onload="document.getElementById("id2").setAttribute("class","redmark");">
<!-- 〇 onload は全てを読んでから実行される -->
<p id="id1">1</p>
<p id="id2">2</p>
<p id="id3">3</p>
<script>
// 〇 前のDOMは読める
document.getElementById("id3").setAttribute("class","redmark");
</script>
<script>
// × 後ろのDOMは読めない
document.getElementById("id4").setAttribute("class","redmark");
</script>
<p id="id4">4</p>
<script defer>
// × defer は src を指定するときだけ
document.getElementById("id5").setAttribute("class","redmark");
</script>
<p id="id5">5</p>
</body>
</html>
今まで HTML 文書において、マークアップされた様々な要素は、DOM とし て木構造のデータ構造を持ちながら、実際に画面にレンダリングされます。 このようなDOM の構造をlight tree と呼ぶことがあります。
というのも、それとは別に HTML 文書中に表示されない
Shadow Treeというデータ構造も作成できます。
要素に Shadow Treeをつけるには
JavaScriptで 要素.attachShadow({mode: "open"})
とします。
Shadow Treeを付加できる要素は カスタム要素の他、article, asde, blockquote, body, div , h1...h6, header, main, nav, p, section, span だけです。
要素に Shadow Tree と Light Tree の両方が付与されている場合、 Shadow Tree が表示されます。 但し、Shadow Treeに slot 要素がある場合、slot要素にlight tree の要 素が埋め込まれます。
template 要素を使うと、HTML 文書内に、レンダリングはされずに、プロ グラムから参照可能な文書片を埋め込めます。 すると、その文書片をコピーすることにより、文書片の構造をそのまま文 書を構成する構造として使用することができます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>template テスト</title>
<style>
th,td {border: 1px solid black;}
</style>
</head>
<body>
<h1>テンプレートテスト</h1>
<table>
<thead>
<tr><th>商品番号</th><th>商品名</th><th>単価</th></tr>
</thead>
<tbody>
<template id="shohin">
<tr><td id="no"></td><td id="name"></td><td id="price"></td></tr>
</template>
</tbody>
</table>
<script>
const data =[
{"no": 999001, "name": "リンゴ", "price": 200},
{"no": 999002, "name": "みかん", "price": 100},
{"no": 999003, "name": "もも", "price": 500}
];
</script>
<script>
const temp = document.getElementById("shohin");
for(let i=0; i< data.length; i++){
const d = data[i];
const c = temp.content.cloneNode(true);
for(let key in d){
c.querySelector("#"+key).innerText=d[key];
}
temp.parentNode.appendChild(c);
}
</script>
</body>
</html>
slot要素を shadow tree内に使うと、 shadow host が含む light tree を slot 要素に埋め込みます。 light tree の要素の slot 属性に名前を指定すると、 同じ名前をname 属 性に指定した slot 要素に埋め込まれます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Slot element</title>
</head>
<body>
<div class="type1" id="a1">
<span slot="id">99ec999</span>
<span slot="name">坂本一郎</span>
</div>
<div class="type2">
<span slot="id">99ec998</span>
<span slot="name">坂本次郎</span>
</div>
<div class="type1">
<span slot="id">99ec997</span>
<span slot="name">坂本三郎</span>
</div>
<div class="type2">
<span slot="id">99ec996</span>
<span slot="name">坂本四郎</span>
</div>
<template id="t1">
<p>
idは<slot name="id"></slot>で、名前は<slot name="name"></slot>
</p>
</template>
<template id="t2">
<style>
.border1 { border-collapse: collapse;
caption-side: top;
border: 1px solid black;}
.border1 td{ border: 1px solid black;}
.border1 th{ border: 1px solid black;}
</style>
<table class="border1">
<tr><th>id</th><th>名前</th></tr>
<tr>
<td><slot name="id"></slot></td>
<td><slot name="name"></slot></td>
</tr>
</table>
</template>
<script>
for(let j of [['type1','t1'],['type2','t2']]){
console.log(j);
let a = document.querySelectorAll('.'+j[0]);
let t = document.getElementById(j[1]);
for(let i=0 ; i< a.length; i++){
let c =a[i].attachShadow({mode:"open"});
c.appendChild(t.content.cloneNode(true));
}
}
</script>
</body>
</html>
HTML Standard では要素を自作することができます。 但し、要素名に -(ハイフン)を含む必要があります
カスタム要素を作るに:
super();
を呼び出しますthis.attachShadow({mode:'open'});
でShadow
Tree を作ります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>Custom element</title>
</head>
<body>
<my-element-1>
First
</my-element-1>
<my-element-2>
Second
</my-element-2>
<my-element-3>
Third
</my-element-3>
<my-element-4>
<div slot="pqr">
Fourth
</div>
<div slot="stu">
Fifth
</div>
</my-element-4>
<script>
class MyElement1 extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
}
}
class MyElement2 extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML= "<div>あいうえお</div>";
}
}
class MyElement3 extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML= "<div>かきくけこ</div><slot>";
}
}
class MyElement4 extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML= '<slot name="stu"></slot>'+
'<div>さしすせそ</div>'+
'<slot name="pqr"></slot>';
}
}
customElements.define('my-element-1', MyElement1);
customElements.define('my-element-2', MyElement2);
customElements.define('my-element-3', MyElement3);
customElements.define('my-element-4', MyElement4);
</script>
</body>
</html>
本教材で使用した、 Java と Python でプログラムリストを切り替えるた めの selectable-div 要素の簡略版のソースを示します。
input 要素でイベントをブロードキャストし、指定の slot だけ表示する ようにイベントリスナを設定します。
head 要素内で script 要素を使って、sdarray で選ぶ項目名の配列を定 義し、 body 要素内で、 script 要素に defer 属性を指定して、以下のプログラ ムを読み込んで使用します。
selectable-div 要素に、項目名を slot 属性で指定した要素を入れると、 指定した項目だけが表示されるようになります。
function broadcast(message){
const ev = new CustomEvent('sdChange', {
detail: { value: message }
});
const sds = document.querySelectorAll('selectable-div');
for(x of sds){
const c = x.shadowRoot.children;
c[0].firstChild.dispatchEvent(ev);
for(let i=1; i<c.length; i++){
c[i].dispatchEvent(ev);
}
}
}
function changed(event){
broadcast(event.target.value);
}
class SelectableDiv extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
const divframe = document.createElement('div');
const selectelem = document.createElement('select');
selectelem.addEventListener('change',event => changed(event));
selectelem.addEventListener('sdChange',
function(event){
selectelem.value=event.detail.value;}
);
for(let t of sdarray){
const optionelem = document.createElement('option');
optionelem.setAttribute('value',t);
optionelem.innerHTML=t;
selectelem.appendChild(optionelem);
}
divframe.appendChild(selectelem);
shadow.appendChild(divframe);
for(let t of sdarray){
const slotelem = document.createElement('slot');
slotelem.setAttribute('name',t);
slotelem.addEventListener('sdChange',
function(event){
if(slotelem.name===event.detail.value){
slotelem.style.display='block';
}else{
slotelem.style.display='none';
}
});
shadow.appendChild(slotelem);
}
}
}
customElements.define('selectable-div', SelectableDiv);
broadcast(getCurrentTarget());
camvas 要素は width と height 属性を指定することで、画面上に描画領 域を作り、JavaScript の API によりグラフィックを描画することができ ます。
canvas 要素に対して、 getContext でコンテキストを取得した後、 そのコンテキストに対して描画に関する API を指定して描画します。
一方、線を書くには beginPathを宣言した後、 moveToで始点を決めた後 lineTo で通過点を指定していき、最後に stroke で描画します。
一方、文字を書く場合は、font でフォントを指定した後、 fillText や strokeText で文字を書きます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>cambus test</title>
</head>
<body>
<p>aaa</p>
<canvas id="imgcanvas" width="600" height="500">
<p>ccc</p>
</canvas>
<p>bbb</p>
<script>
const imgcanvas=document.getElementById("imgcanvas");
if(imgcanvas.getContext) {
const ctx=imgcanvas.getContext('2d');
ctx.fillStyle="rgba(255,0,0,1)";
ctx.strokeStyle="#000000";
ctx.fillRect(0, 0, 100, 100);
ctx.strokeRect(0, 0, 100, 100);
ctx.fillRect(50, 50, 100, 200);
ctx.strokeRect(80, 130, 200, 100);
ctx.beginPath();
ctx.moveTo(0,100);
ctx.lineTo(200,200);
ctx.stroke();
ctx.font = "16pt 'Arial'";
ctx.fillText("abc",250,50);
ctx.strokeText("xyz",250,150);
}
</script>
</body>
</html>
与えたデータの配列に関して、折れ線グラフを書くプログラムを作れ
<script>
const data=[3,4,2,6,10,3];
</script>
アナログの針を表示する時計を作成せよ。但し、 setInterval 関数は、第一引数の関数を第二引数で指定したミリ秒ごとに実行するものとする。
与えたデータの配列に関して、折れ線グラフを書くプログラムを作れ
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>折れ線グラフ</title>
</head>
<body>
<canvas id="graph" width="600" height="400"></canvas>
<script>
const data = [3, 4, 2, 6, 10, 3];
</script>
<script>
const graph = document.getElementById("graph");
if (graph.getContext) {
const ctx = graph.getContext('2d');
ctx.fillStyle = "rgba(255,0,0,1)";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(0, 100);
for (let i = 0; i < data.length; i++) {
ctx.lineTo(i * 100+100, 200 - data[i] * 10);
}
ctx.stroke();
}
</script>
</body>
</html>
アナログの針を表示する時計を作成せよ。但し、 setInterval 関数は、第一引数の関数を第二引数で指定したミリ秒ごとに実行するものとする。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>時計</title>
</head>
<body>
<p id="debug"></p>
<canvas id="tokei" width="600" height="400"></canvas>
<script>
function plothari(ctx, deg, radius) {
deg -= Math.PI / 2;
ctx.fillStyle = "rgba(255,0,0,1)";
ctx.strokeStyle = "#000000";
ctx.beginPath();
ctx.moveTo(300, 200);
ctx.lineTo(Math.cos(deg) * radius + 300,
Math.sin(deg) * radius + 200);
ctx.stroke();
}
var tokei = document.getElementById("tokei");
var debug = document.getElementById("debug");
debug.textContent = "aaa" + tokei.getContext;
if (tokei.getContext) {
var ctx = tokei.getContext('2d');
function tick() {
var date = new Date();
debug.textContent = "" + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
ctx.clearRect(0, 0, 600, 400);
plothari(ctx, (date.getHours() % 12) * Math.PI / 6, 100);
plothari(ctx, date.getMinutes() * Math.PI / 30, 150);
plothari(ctx, date.getSeconds() * Math.PI / 30, 200);
}
setInterval(tick, 500);
}
</script>
</body>
</html>