マウスホイール について

Posted コメントするカテゴリー: javascript

マウスホイール について

これまでDOM要素をドラッグ&ドロップについて試しましたが、
マウスの操作としてホイールのコントロールについて、javascriptでどのように制御できるのか試してみます。

ホイールのイベントハンドラは、ブラウザごとに完全に同一ではないので、
実装時にはどんなイベントハンドラが、何のブラウザで「有効か無効か」に注意する必要があります。

ホイールのイベントハンドラの一つとして「mousewheel」がありますが、これは現在は非推奨になっています。
構文としては以下のように書きます。

addEventListener('mousewheel', (event) => {});

もしくは

onmousewheel = (event) => { };

ただし、このイベントは非推奨であることと、ブラウザによってサポートされていないブラウザ(firefox)があるので、
おおよそ実用的なイベントとは言えません。

また、DOM level3の仕様では「wheel」という名前で提唱されています。

このイベントは構文としては次のように書きます。

document.addEventListener("wheel", function(event) {
    console.log("wheel start");
});

この簡単な記述を、HTML内に記載し画面表示をすると、マウスのホイールを取得して、
イベントが発火していることがわかります。

console.logで確認する方法でもいいのですが、都度、開発者ツールを開いて確認する手間もあるので、
画面上にイベント内容を出力するようにしました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style type="text/css">
body, html {
	overflow: hidden;
}
#disp_mouse_wheel {
	position: absolute;
	height: 500px;
	width: 300px;
	background-color: #c0e5ff;
	overflow-y: scroll;
}
</style>
</head>
<body>

マウスホイールの「wheel」イベント情報を表示する
<div id="disp_mouse_wheel"></div>

<script type="text/javascript">
document.addEventListener("wheel", function(event) {

	// 開発者ツールで表示する
    console.log("event Delta -> " + event.wheelDelta);

	// 画面上のDOM要素を取得
	let disp_mouse_wheel = document.getElementById("disp_mouse_wheel");
	disp_mouse_wheel.innerHTML = event.wheelDelta + "<br />" + disp_mouse_wheel.innerHTML;
});
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスし、マウスホイールを上下に回転すると、イベントハンドラの wheelDelta プロパティが取得できます。

画面のイベント情報を表示する領域に、このwheelDeltaプロパティの値を出力すると
「120」や「-120」といった数値が出力されます(chrome、firefoxで確認済)。
現在はブラウザのサポート状況は「Safari on iOS」だけは非対応となっています。
「Safari on iOS」以外のブラウザではほとんどのブラウザで動作します。

値とホイールの回転方向については、次のとおりです。

マウスホイールを下方向に回転した時:-120
マウスホイールを上方向に回転した時:120

画面上でマウスホイールを操作すると、上記の値が1スクロール(マウスにもよりますが、1ホイール)ごとに
取得できることがわかります。

ホイールイベントのdeltaX、deltaY、deltaZ、プロパティについて

上記のHTMLを基に、ホイール操作時のイベントのプロパティである

deltaX
deltaY
deltaZ

の値を出力してみて、実際にどのような値が取得できるのかを試してみます。

deltaX  水平方向のスクロール
deltaY  垂直方向のスクロール
deltaZ  Z軸方向のスクロール

上記のプロパティの値を把握しやすいように、簡単なサンプルHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style type="text/css">
body, html {
	overflow: hidden;
}

.title {
	width: 200px;
	top: 10px;
	margin-left: 5px;
	float: left;
}	

#disp_mouse_wheel {
	position: absolute;
	height: 500px;
	width: 200px;
	top: 30px;
	background-color: #c0e5ff;
	overflow-y: scroll;
}

#disp_mouse_wheel_deltaX {
	position: absolute;
	height: 500px;
	width: 200px;
	top: 30px;
	left: 210px;
	background-color: #c0e5ff;
	overflow-y: scroll;
}

#disp_mouse_wheel_deltaY {
	position: absolute;
	height: 500px;
	width: 200px;
	top: 30px;
	left: 420px;
	background-color: #c0e5ff;
	overflow-y: scroll;
}

#disp_mouse_wheel_deltaZ {
	position: absolute;
	height: 500px;
	width: 200px;
	top: 30px;
	left: 630px;
	background-color: #c0e5ff;
	overflow-y: scroll;
}
</style>
</head>
<body>

<div class="title">wheel</div>
<div id="disp_mouse_wheel"></div>

<div class="title">deltaX</div>
<div id="disp_mouse_wheel_deltaX"></div>

<div class="title">deltaY</div>
<div id="disp_mouse_wheel_deltaY"></div>

<div class="title">deltaZ</div>
<div id="disp_mouse_wheel_deltaZ"></div>

<script type="text/javascript">
document.addEventListener("wheel", function(event) {

	// 開発者ツールで表示する
    console.log("event Delta -> " + event.wheelDelta);
    console.log("event deltaX -> " + event.deltaX);
    console.log("event deltaY -> " + event.deltaY);
    console.log("event deltaZ -> " + event.deltaZ);

	// 画面上のDOM要素を取得
	let disp_mouse_wheel = document.getElementById("disp_mouse_wheel");
	disp_mouse_wheel.innerHTML = event.wheelDelta + "<br />" + disp_mouse_wheel.innerHTML;

	let disp_mouse_wheel_deltaX = document.getElementById("disp_mouse_wheel_deltaX");
	disp_mouse_wheel_deltaX.innerHTML = event.deltaX + "<br />" + disp_mouse_wheel_deltaX.innerHTML;

	let disp_mouse_wheel_deltaY = document.getElementById("disp_mouse_wheel_deltaY");
	disp_mouse_wheel_deltaY.innerHTML = event.deltaY + "<br />" + disp_mouse_wheel_deltaY.innerHTML;

	let disp_mouse_wheel_deltaZ = document.getElementById("disp_mouse_wheel_deltaZ");
	disp_mouse_wheel_deltaZ.innerHTML = event.deltaZ + "<br />" + disp_mouse_wheel_deltaZ.innerHTML;

});
</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

画面にアクセスして、マウスホイールを操作すると、「wheel」プロパティと「deltaY」プロパティの
値が取得できることがわかります。(deltaXとdeltaZはマウスの形状により、取得できます)

一般的なマウスの形状は、縦(垂直)方向のスクロールなので、上記のうち deltaY の値が取得できることになります。
注意する点は wheelとdeltaYとでは、取得できる符号が逆になっている点です。
wheelが正の値を出力する時は、deltaYは負の値を出力しています。

また、ブラウザごとに取得できるdeltaYの値は異なります。

実際にchromeで試してみると次のような画面になります。

同じ操作をfirefoxで試すと次のような画面になります。

「deltaY」の値を確認すると、
chromeでは「166.66665649414062」「-166.66665649414062」等の値ですが、
firefoxでは「120」「-120」となり、ブラウザごとに値が違うことがわかります。

実際のプログラム実装時にはこの違いについて注意が必要です。

マウスドラッグ、マウスドロップ時の dataTransferプロパティ について

Posted コメントするカテゴリー: javascript

マウスドラッグ、マウスドロップ時の dataTransferプロパティ について

前回の投稿では、マウスでDOM要素をドラッグ&ドロップすることを試してみました。

このドラッグ&ドロップには単にDOM要素をドラッグして、特定のDOM要素にドロップするという
動作だけではなく、ドラッグ時、ドロップ時にデータを転送する。という利用方法があります。

このデータを転送する処理については

移動
コピー
リンク

という諸方法があり、ドロップ時にそれらの方法を選択した上で処理を決定する。
という方法がアプリケーションの質を向上させるポイントにもなります。

また、最近のブラウザではドラッグ&ドロップを標準化したAPIも整備されてきました。

次の例では、ドラッグ時とドロップ時に、データ転送をする方法として
dataTransferプロパティを使った例を試してみます。

dataTransferプロパティは、DataTransferオブジェクトのプロパティです。

dataTransferを試してみる

画面上のとあるDOM要素をドラッグする際、dragstartイベントが発動されます。

このイベントハンドラの実行時に、dataTransferプロパティに対して、データをセットする
ことができます。

前回の投稿を例にすると、

// ドラッグ開始したとき
element_drag_dom.addEventListener('dragstart', function(e){
	display_status('DRAG DOM : dragstart', 'drag_c');
});

上記の箇所の「ドラッグを開始したとき」の dragstart イベントの箇所です。

このイベントの処理に対して、引数のイベントターゲットに、dataTransferプロパティの
setDataメソッドを使って、値を設定します。

具体的には、以下のように記述します。

// ドラッグ開始したとき
element_drag_dom.addEventListener('dragstart', function(e){
	display_status('DRAG DOM : dragstart', 'drag_c');
	
	e.dataTransfer.setData("text/plain" , e.target.textContent);
	
	e.stopPropagation();
});

上記サンプルの「dataTransfer.setData」の箇所がデータをセットしている箇所です。

この「setData」メソッドは、第一引数にデータタイプを指定し、第二引数に格納するデータを指定します。

上記の例でいうと、データタイプは text/plain 、データは e.target.textContent(ドラッグ中のDOM要素のテキスト「ドラッグ用DOM」)です。

上記のdataTransferプロパティに文字列が格納されている前提として、
今度はドロップ側のDOM要素に対して、dropイベントの中でdataTransferプロパティにgetDataメソッドを使って
中の値を取り出すことができます。

前回のドロップ時の処理は以下のように書きました。

// ドラッグしている要素をドロップした時
element_drop_dom.addEventListener('drop', function(e){
	e.preventDefault();
	display_status('DROP DOM : drop', 'drop_c');
	
	// ドラッグ用DOMを削除する
	element_drag_dom.parentNode.removeChild(element_drag_dom);
	
});

このドロップ時の処理に、「let droptxt = e.dataTransfer.getData(“text/plain”);」を追記し、
dataTransferの値を取り出します。

取り出した値がわかりやすいように、一旦droptxtの変数に格納し、
その変数を「element_drop_dom.innerHTML = droptxt;」に出力します。

処理としては、次のように変更しました。

// ドラッグしている要素をドロップした時
element_drop_dom.addEventListener('drop', function(e){
	e.preventDefault();
	display_status('DROP DOM : drop', 'drop_c');
	
	// ドラッグ用DOMを削除する
	element_drag_dom.parentNode.removeChild(element_drag_dom);
	
	let droptxt = e.dataTransfer.getData("text/plain");
	element_drop_dom.innerHTML = droptxt;

});

このように書くことにより、ドラッグ用のDOMをドロップ領域用のDOMにドロップしたタイミングで
ドラッグ時にdataTransferに格納した文字列が、ドロップ時にドロップ領域用のDOMに出力されることが確認できます。

この初期画面の右下のドロップ要素のDOMの文字列が

この文字列に変更されます。

HTMLの全体は以下のようになります。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style type="text/css">

#drag_dom {
	position: absolute;
	height: 100px;
	width: 150px;
	top: 10px;
	left: 10px;
	background-color: #c0e5ff;
}

#drop_dom {
	position: absolute;
	height: 200px;
	width: 300px;
	top: 300px;
	left: 500px;
	padding: 8px;
	border: 5px solid;
	border-color: #ffbaba;
}

#drop_dom_hover {
	background-color: #998877;
}

#dnd_status_area {
	position: absolute;
	top: 120px;
}

.drag_c {
	color: #28a4fb;
}

.drop_c {
	color: #ff5050;
}

</style>
</head>
<body>

<div id="drag_dom" draggable="true">
	ドラッグ用DOM
</div>

<div id="drop_dom">
	ドロップ領域用DOM
</div>

<div id="dnd_status_area">
	<div>ドラッグ&ドロップの状態【日時 - ステータス】</div>
	<div id="dnd_status"></div>
</div>

<script type="text/javascript">

// ドラッグするDOM要素を取得
let element_drag_dom = document.getElementById("drag_dom");

// ドロップするDOM要素を取得
let element_drop_dom = document.getElementById("drop_dom");

// ステータス表示用配列を用意
let status_queue = [];
let status_i = 0;

//------------------------------------------------
// ドラッグ対象に登録するイベントハンドラ
//------------------------------------------------
// 要素をドラッグしている時
element_drag_dom.addEventListener('drag', function(e){
	display_status('DRAG DOM : drag', 'drag_c');

	// ドラッグ中は、文字列を変更
	element_drag_dom.innerHTML = "ドラッグ中です";

});

// ドラッグ開始したとき
element_drag_dom.addEventListener('dragstart', function(e){
	display_status('DRAG DOM : dragstart', 'drag_c');
	
	e.dataTransfer.setData("text/plain" , e.target.textContent);
	
	e.stopPropagation();
});

// ドラッグが終了した時
element_drag_dom.addEventListener('dragend', function(e){
	display_status('DRAG DOM : dragend', 'drag_c');

	// ドラッグが終わったら、文字列、cssを戻す
	element_drag_dom.innerHTML = "ドラッグ用DOM";
	element_drop_dom.style.backgroundColor = "#ffffff";
});

//------------------------------------------------
// ドロップ対象に登録するイベントハンドラ
//------------------------------------------------
// ドラッグ中の要素がドロップできる場所の上に入ったとき
element_drop_dom.addEventListener('dragenter', function(e){
	display_status('DROP DOM : dragenter', 'drop_c');
});

// dragexitは「dragleave」と同義イベントなので、ここではコメントアウト
//element_drop_dom.addEventListener('dragexit', function(e){
//	display_status('dragexit');
//});

// ドラッグしている要素がドロップできる場所から離れたとき
element_drop_dom.addEventListener('dragleave', function(e){
	display_status('DROP DOM : dragleave', 'drop_c');

	element_drop_dom.style.backgroundColor = "#ffffff";

});

// ドラッグ中の要素がドロップできる場所の上にあるとき
element_drop_dom.addEventListener('dragover', function(e){
	e.preventDefault();
	display_status('DROP DOM : dragover', 'drop_c');
	
	// ドロップできる場所の上にあるとき、メッセージとcssを変更する
	element_drag_dom.innerHTML = "離すと消えるよ";
	element_drop_dom.style.backgroundColor = "#ff5050";
});

// ドラッグしている要素をドロップした時
element_drop_dom.addEventListener('drop', function(e){
	e.preventDefault();
	display_status('DROP DOM : drop', 'drop_c');
	
	// ドラッグ用DOMを削除する
	element_drag_dom.parentNode.removeChild(element_drag_dom);
	
	let droptxt = e.dataTransfer.getData("text/plain");
	element_drop_dom.innerHTML = droptxt;

});

/**
 * 処理ステータスを画面出力する
 */
function display_status(status, dd_class)
{
	// 画面上のDOM要素を取得
	let dnd_status = document.getElementById("dnd_status");

	// 現在日時を取得
	let date_tmp = new Date();
	let date_disp = date_tmp.getFullYear() + "/"
	 + ('0' + (date_tmp.getMonth() + 1)).slice(-2)  + "/"
	 + ('0' + date_tmp.getDate()).slice(-2) + " "
	 + ('0' + date_tmp.getHours()).slice(-2) + ":"
	 + ('0' + date_tmp.getMinutes()).slice(-2) + ":"
	 + ('0' + date_tmp.getSeconds()).slice(-2) + "."
	 + ('00' + date_tmp.getMilliseconds()).slice(-3);

	// ステータスを10個づつの表示用配列へ格納する
	status_queue[status_i] = status;
	status_i++;
	if (status_i >= 30) {
		status_i = 0;
	}

	// キューの内容を画面へ描画する
	dnd_status.innerHTML = '';
	for (let i = 0; i < status_queue.length; i++) {
		dnd_status.innerHTML += date_disp + " - <span class='" + dd_class + "'>" + status_queue[i] + "</span><br />";
	}
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスして、ドラッグ用DOMをドロップ領域用のDOMへドラッグ&ドロップすると、
ドロップ側のDOMの表示が変わることが確認できます。

dataTransferプロパティに格納できるデータについて

dataTransferプロパティに格納できるデータは文字列だけはないです。

例えば、ブラウザ経由でファイルがドロップされた場合はdataTransfer.filesプロパティにファイル内容がオブジェクトとして格納されます。

また、画面上の単純なDOM要素だけではなく、リスト表示をしている

<ul>タグ内の<li>タグ

に対して、
リストの順をドラッグ&ドロップで並び変える。等の動作も可能になります。

マウスドラッグ、マウスドロップについて

Posted コメントするカテゴリー: javascript

マウスドラッグ、マウスドロップについて

前回の投稿では、マウスでDOM要素をドラッグすることを試してみましたが、
要素をドラッグしたあと、ドロップする方法も試してみます。

ドラッグ&ドロップに関するイベントの種類は次のものがあります。

drag

dragstart

dragend

dragenter

dragleave

dragexit

dragover

drop

それぞれ、イベントハンドラの意味は下記のようになります。

要素をドラッグしている時
drag

ドラッグ開始したとき
dragstart

ドラッグが終了した時
dragend

ドラッグ中の要素がドロップできる場所の上に入ったとき
dragenter

ドラッグしている要素がドロップできる場所から離れたとき
dragleave

上記の「dragleave」と同義
dragexit

ドラッグ中の要素がドロップできる場所の上にあるとき
dragover

ドラッグしている要素をドロップした時
drop

上記のイベントハンドラの意味を踏まえ、シンプルなドラッグ&ドロップのサンプルを試してみます。
下記のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style type="text/css">

#drag_dom {
	position: absolute;
	height: 100px;
	width: 150px;
	top: 10px;
	left: 10px;
	background-color: #9ed7ff;
}

#drop_dom {
	position: absolute;
	height: 200px;
	width: 300px;
	top: 300px;
	left: 500px;
	padding: 8px;
	border: 5px solid;
	border-color: #ffbaba;
}

#drop_dom_hover {
	background-color: #998877;
}

#dnd_status_area {
	position: absolute;
	top: 120px;
}

.drag_c {
	color: #28a4fb;
}

.drop_c {
	color: #ff5050;
}

</style>
</head>
<body>

<div id="drag_dom" draggable="true">
	ドラッグ用DOM
</div>

<div id="drop_dom">
	ドロップ領域用DOM
</div>

<div id="dnd_status_area">
	<div>ドラッグ&ドロップの状態【日時 - ステータス】</div>
	<div id="dnd_status"></div>
</div>

<script type="text/javascript">

// ドラッグするDOM要素を取得
let element_drag_dom = document.getElementById("drag_dom");

// ドロップするDOM要素を取得
let element_drop_dom = document.getElementById("drop_dom");

// ステータス表示用配列を用意
let status_queue = [];
let status_i = 0;

//------------------------------------------------
// ドラッグ対象に登録するイベントハンドラ
//------------------------------------------------
// 要素をドラッグしている時
element_drag_dom.addEventListener('drag', function(e){
	display_status('DRAG DOM : drag', 'drag_c');

	// ドラッグ中は、文字列を変更
	element_drag_dom.innerHTML = "ドラッグ中です";
	element_drag_dom.style.backgroundColor = "#d3edff";

});

// ドラッグ開始したとき
element_drag_dom.addEventListener('dragstart', function(e){
	display_status('DRAG DOM : dragstart', 'drag_c');
});

// ドラッグが終了した時
element_drag_dom.addEventListener('dragend', function(e){
	display_status('DRAG DOM : dragend', 'drag_c');

	// ドラッグが終わったら、文字列、cssを戻す
	element_drag_dom.innerHTML = "ドラッグ用DOM";
	element_drag_dom.style.backgroundColor = "#9ed7ff";
	element_drop_dom.style.backgroundColor = "#ffffff";
});

//------------------------------------------------
// ドロップ対象に登録するイベントハンドラ
//------------------------------------------------
// ドラッグ中の要素がドロップできる場所の上に入ったとき
element_drop_dom.addEventListener('dragenter', function(e){
	display_status('DROP DOM : dragenter', 'drop_c');
});

// dragexitは「dragleave」と同義イベントなので、ここではコメントアウト
//element_drop_dom.addEventListener('dragexit', function(e){
//	display_status('dragexit');
//});

// ドラッグしている要素がドロップできる場所から離れたとき
element_drop_dom.addEventListener('dragleave', function(e){
	display_status('DROP DOM : dragleave', 'drop_c');

	element_drop_dom.style.backgroundColor = "#ffffff";

});

// ドラッグ中の要素がドロップできる場所の上にあるとき
element_drop_dom.addEventListener('dragover', function(e){
	e.preventDefault();
	display_status('DROP DOM : dragover', 'drop_c');
	
	// ドロップできる場所の上にあるとき、メッセージとcssを変更する
	element_drag_dom.innerHTML = "離すと消えるよ";
	element_drop_dom.style.backgroundColor = "#ff5050";
});

// ドラッグしている要素をドロップした時
element_drop_dom.addEventListener('drop', function(e){
	e.preventDefault();
	display_status('DROP DOM : drop', 'drop_c');
	
	// ドラッグ用DOMを削除する
	element_drag_dom.parentNode.removeChild(element_drag_dom);
	
});

/**
 * 処理ステータスを画面出力する
 */
function display_status(status, dd_class)
{
	// 画面上のDOM要素を取得
	let dnd_status = document.getElementById("dnd_status");

	// 現在日時を取得
	let date_tmp = new Date();
	let date_disp = date_tmp.getFullYear() + "/"
	 + ('0' + (date_tmp.getMonth() + 1)).slice(-2)  + "/"
	 + ('0' + date_tmp.getDate()).slice(-2) + " "
	 + ('0' + date_tmp.getHours()).slice(-2) + ":"
	 + ('0' + date_tmp.getMinutes()).slice(-2) + ":"
	 + ('0' + date_tmp.getSeconds()).slice(-2) + "."
	 + ('00' + date_tmp.getMilliseconds()).slice(-3);

	// ステータスを30個づつの表示用配列へ格納する
	status_queue[status_i] = status;
	status_i++;
	if (status_i >= 30) {
		status_i = 0;
	}

	// イベントハンドラの状態を画面へ描画する
	dnd_status.innerHTML = '';
	for (let i = 0; i < status_queue.length; i++) {
		dnd_status.innerHTML += date_disp
		 + " - <span class='" + dd_class + "'>"
		 + status_queue[i] + "</span><br />";
	}

}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

単純にドラッグ&ドロップの部分だけでも動作はしますが、
ドラッグ用のDOMと、ドロップ領域のDOMに対して、ドラッグ&ドロップをする際の
それぞれのイベントが見えるように、
画面上に「イベント発生時の日時」と「イベントハンドラの状態(ステータス)」を出力してみました。

上記の出力は、サンプルの動作がわかりやすいように出力してます。
実際にはこのような出力はせず、必要な処理のみを書くように注意します。

余計な画面表示を行わず、シンプルな動作例を確認する場合は、
ソース上の「display_status」を全て削除し、下記のメソッドごと削除しても動作します。

/**
 * 処理ステータスを画面出力する
 */
function display_status(status, dd_class)
{
	// 画面上のDOM要素を取得
	let dnd_status = document.getElementById("dnd_status");

	// 現在日時を取得
	let date_tmp = new Date();
	let date_disp = date_tmp.getFullYear() + "/"
	 + ('0' + (date_tmp.getMonth() + 1)).slice(-2)  + "/"
	 + ('0' + date_tmp.getDate()).slice(-2) + " "
	 + ('0' + date_tmp.getHours()).slice(-2) + ":"
	 + ('0' + date_tmp.getMinutes()).slice(-2) + ":"
	 + ('0' + date_tmp.getSeconds()).slice(-2) + "."
	 + ('00' + date_tmp.getMilliseconds()).slice(-3);

	// ステータスを30個づつの表示用配列へ格納する
	status_queue[status_i] = status;
	status_i++;
	if (status_i >= 30) {
		status_i = 0;
	}

	// キューの内容を画面へ描画する
	dnd_status.innerHTML = '';
	for (let i = 0; i < status_queue.length; i++) {
		dnd_status.innerHTML += date_disp + " - <span class='" + dd_class + "'>" + status_queue[i] + "</span><br />";
	}
}

画面にアクセスするとわかるように、まず最初に、以下のような
ドラッグ用のDOMと、ドロップ領域用のDOMが画面に表示されます。

この状態から、ドラッグ用のDOM(画面上の青い領域)をマウスでドラッグ開始します。

そうすると、ドラッグ中の状態として、下記のステータスが画面に出力されます。
(実際にはマウスの挙動に応じて、出力される内容が変わりますので、色々と操作して確かめてみていただければと思います)

要素をドラッグしている時
ドラッグ開始したとき
ドラッグが終了した時
ドラッグ中の要素がドロップできる場所の上に入ったとき
ドラッグしている要素がドロップできる場所から離れたとき
ドラッグ中の要素がドロップできる場所の上にあるとき
ドラッグしている要素をドロップした時

ここでプログラム上の注意が必要な点としては、

各イベントハンドラの登録は「ドラッグ用のDOMに対して登録するイベント」と「ドロップ領域用のDOMに対して登録するイベント」がそれぞれ異なっている点です。

「ドラッグ用のDOMに対して登録するイベント」に対しては

drag
dragstart
dragend

であることに対し、

「ドロップ領域用のDOMに対して登録するイベント」に対しては

dragenter
dragexit(実際には未登録)
dragleave
dragover
drop

としている点です。

ドラッグ元のDOMに対して、ドロップ領域用のイベントハンドラを登録しても意図しない動作になるので、
「何をドラッグ」して、「どこにドロップ」するのか、という点に注意が必要になります。

マウスドラッグイベントについて

Posted コメントするカテゴリー: javascript

マウスドラッグイベントについて

マウスでDOMの要素をドラッグするイベントを試してみます。

DOMの要素をドラッグするイベントは、documentやHTMLElementのdragイベントを使う方法がありますが、
ここではシンプルな例として、DOM要素をクリックした際のonmousedownイベントを使い、
その中でDOM要素の座標を制御する方法を試してみます。

下記のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style type="text/css">
#test1 {
	position:absolute;
	height:100px;
	width:150px;
	top:10px;
	left:10px;
	background-color:rgb(0 74 207 / 15%);
}
</style>
</head>
<body>

<div id="test1">
	ドラッグテスト
</div>

<script type="text/javascript">

let element_test1 = document.getElementById("test1");

element_test1.onmousedown = function(event) {

	// DOM要素の表示順をあげる (他のDOM要素がある場合、考慮する)
	element_test1.style.zIndex = 999;

	// マウスダウン時、マウスアップ時のイベントハンドラを登録する
	document.addEventListener('mousemove', mousemovehandler, false);
	document.addEventListener('mouseup', mouseuphandler, false);

	// マウスダウン時 (マウス移動時) のイベントハンドラ
	function mousemovehandler(event)
	{
		// イベントオブジェクトから座標情報を取得し、移動に使用する
		element_test1.style.left = event.pageX - element_test1.offsetWidth / 2 + 'px';
		element_test1.style.top = event.pageY - element_test1.offsetHeight / 2 + 'px';
	}

	// マウスアップ時のイベントハンドラ
	function mouseuphandler(event)
	{
		// DOM要素の表示順を下げる
		element_test1.style.zIndex = 0;

		document.removeEventListener('mousemove', mousemovehandler, false);
		document.removeEventListener('mouseup', mouseuphandler, false);
	}

};

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

実際に画面にアクセスすると、画面上に「ドラッグテスト」というDOM要素が見えます。

このDOMをマウスでクリックすると、mousemovehandler のイベントハンドラが実行され、
DOM要素の座標がマウス位置の座標に代入され、DOM要素がドラッグできるようになります。

課題としては、マウスクリック時の座標が、DOM要素の座標の2分の1の箇所を指しているので、
要素内のどの位置をクリックしても常にDOM要素の中心をドラッグする動きになる点があります。(この制御は別途、計算処理を入れると解消できます)

また、マウスをドラッグした後、マウスをアップすると mouseuphandler のイベントハンドラが実行され、
それまでドラッグしていたDOM要素の座標の計算、およびマウスアップ時のイベントをそれぞれ 削除(remove)しています。
こうすることでDOM要素をドロップした位置にその要素を固定することができます。

この簡単な例の他、documentやHTMLElementのdragイベントを使った例については、別途書いてみようと思います。

マウスクリック時の座標を計算し、修正

上記の例で課題としてあげた「マウスクリック時のDOM要素の位置がずれる」という点に対して、
修正版のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
<style type="text/css">
#test1 {
	position:absolute;
	height:100px;
	width:150px;
	top:10px;
	left:10px;
	background-color:rgb(0 74 207 / 15%);
}
</style>
</head>
<body>

<div id="test1">
	ドラッグテスト
</div>

<script type="text/javascript">

let element_test1 = document.getElementById("test1");

element_test1.onmousedown = function(event) {

	// DOM要素の表示順をあげる (他のDOM要素がある場合、考慮する)
	element_test1.style.zIndex = 999;

	let margin_x = event.pageX - element_test1.getBoundingClientRect().left;
	let margin_y = event.pageY - element_test1.getBoundingClientRect().top;

	// マウスダウン時、マウスアップ時のイベントハンドラを登録する
	document.addEventListener('mousemove', mousemovehandler, false);
	document.addEventListener('mouseup', mouseuphandler, false);

	// マウスダウン時 (マウス移動時) のイベントハンドラ
	function mousemovehandler(event)
	{
		// イベントオブジェクトから座標情報を取得し、移動に使用する
		element_test1.style.left = event.pageX - margin_x + 'px';
		element_test1.style.top = event.pageY - margin_y + 'px';
	}

	// マウスアップ時のイベントハンドラ
	function mouseuphandler(event)
	{
		// DOM要素の表示順を下げる
		element_test1.style.zIndex = 0;

		document.removeEventListener('mousemove', mousemovehandler, false);
		document.removeEventListener('mouseup', mouseuphandler, false);
	}

};

</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

画面にアクセスして「ドラッグテスト」のDOM要素のどこをドラッグしてもマウス位置がずれないようになります。

ポイントとしては、マウスクリック時の座標をあらかじめマージンのx座標、y座標として取得しておき、

	let margin_x = event.pageX - element_test1.getBoundingClientRect().left;
	let margin_y = event.pageY - element_test1.getBoundingClientRect().top;

それを、移動時の座標として計算に入れている点です。

		element_test1.style.left = event.pageX - margin_x + 'px';
		element_test1.style.top = event.pageY - margin_y + 'px';

pageX、pageY、を使わずに、clientX、clientY、でも同様の動きになりますが、
画面上での座標計算と、DOM上での座標計算の違いがあり、ページをスクロールした上でドラッグする際(つまり、画面の初期表示外へドラッグする際)に挙動が違ってきます。

実際の開発シーンでは、どんなDOM上で何をどこまでドラッグするのか、という設計次第で使うプロパティを分けるとよいと思います。

マウスイベントとキーボードの修飾について

Posted コメントするカテゴリー: javascript

マウスイベントとキーボードの修飾について

前回は、マウスイベントについての投稿をしましたが、
サンプルではマウスイベントのみを試してみました。

実際のアプリケーションではマウスイベントと同時に、キーボードの修飾キーを組み合わせて
アプリケーションの挙動を変えるシーンがあります。

修飾キーは「Ctrlキー」「Shiftキー」「Altキー」「メタキー」あります。
メタキーは、windowsでいうとウィンドウズキー、macでいうとコマンドキーです。

具体的にマウスイベントと修飾キーの組み合わせをどのように書くのか、試してみます。
下記のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>

<body>

<div id="test1" style="width: 1000px; height: 900px;">
	<div style="position:fixed; top:10px;">

		<div>clientX :<span id="client_x"></span></div>
		<div>clientY :<span id="client_y"></span></div>
		<div>screenX:<span id="screen_x"></span></div>
		<div>screenY:<span id="screen_y"></span></div>
		<div>pageX:<span id="page_x"></span></div>
		<div>pageY:<span id="page_y"></span></div>
		<div>offsetX:<span id="offset_x"></span></div>
		<div>offsetY:<span id="offset_y"></span></div>

		修飾キー
		<div>Ctrl:<span id="ctrl"></span></div>
		<div>Shift:<span id="shift"></span></div>
		<div>Alt:<span id="alt"></span></div>
		<div>Meta:<span id="meta"></span></div>

	</div>
</div>

<script type="text/javascript">

	let element_test1 = document.getElementById("test1");

	element_test1.onmousemove = function(event) {

		// イベントオブジェクトから座標を取得
		let client_x = event.clientX;
		let client_y = event.clientY;
		let screen_x = event.screenX;
		let screen_y = event.screenY;
		let page_x = event.pageX;
		let page_y = event.pageY;
		let offset_x = event.offsetX;
		let offset_y = event.offsetY;

		// キー情報を取得する
		let ctrl = event.ctrlKey;
		let shift = event.shiftKey;
		let alt = event.altKey;
		let meta = event.metaKey;

		// x座標出力用の要素を取得
		let ele_client_x = document.getElementById("client_x");
		let ele_client_y = document.getElementById("client_y");
		let ele_screen_x = document.getElementById("screen_x");
		let ele_screen_y = document.getElementById("screen_y");
		let ele_page_x = document.getElementById("page_x");
		let ele_page_y = document.getElementById("page_y");
		let ele_offset_x = document.getElementById("offset_x");
		let ele_offset_y = document.getElementById("offset_y");

		let ele_ctrl = document.getElementById("ctrl");
		let ele_shift = document.getElementById("shift");
		let ele_alt = document.getElementById("alt");
		let ele_meta = document.getElementById("meta");

		// 要素を更新する
		ele_client_x.innerHTML = client_x;
		ele_client_y.innerHTML = client_y;
		ele_screen_x.innerHTML = screen_x;
		ele_screen_y.innerHTML = screen_y;
		ele_page_x.innerHTML = page_x;
		ele_page_y.innerHTML = page_y;
		ele_offset_x.innerHTML = offset_x;
		ele_offset_y.innerHTML = offset_y;

		// キー情報を表示する
		ele_ctrl.innerHTML = ctrl;
		ele_shift.innerHTML = shift;
		ele_alt.innerHTML = alt;
		ele_meta.innerHTML = meta;

	};

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスして、マウスイベントが発生した際に、
キーボードの「Ctrlキー」「Shiftキー」「Altキー」「メタキー」を押すと修飾キーの
画面上に「true」と表示されます。(何も押していないキーについてはfalse表示されます)

Ctrlキーを押した状態の出力 (Firefoxの表示)

Ctrlキーを押した状態の出力 (Chromeの表示)

この時、メタキーについては、OSやその他のブラウザ以外のアプリケーションで使われていることが多く、
Chromeではキー情報が取得できていますが、Firefoxではキー情報が取得できておりません。

また、複数のキーを同時に押しても、押したキーについて「true」と表示されることがわかります。

Ctrlキー + Shiftキー + Altキー + メタキー を同時に押した状態の出力 (Firefoxの表示)

Ctrlキー + Shiftキー + Altキー + メタキー を同時に押した状態の出力 (Chromeの表示)

ブラウザの違いで、メタキーの取得ができないことがあるので、
アプリケーションを開発する際には注意が必要です。

マウスイベント

Posted コメントするカテゴリー: javascript

マウスイベント

マウスイベントについて試してみます。

マウスイベントの種類には以下のものがあります。

click
contextmenu
dblclick
mousedown
mouseup
mousemove
mouseover
mouseout
mouseenter
mouseleave

それぞれイベント伝播はバブリングします(mouseenter、mouseloaveは例外)。
マウスイベントの内容は以下になります。

click
マウスボタンを押した後、要素をアクティブ(活性化)した時に発生

contextmenu
右クリック時のコンテキストメニューを表示

dblclick
ダブルクリック時に発生

mousedown
マウスボタンを押した時に発生

mouseup
マウスボタンを離した時に発生

mousemove
マウスを動かした時に発生

mouseover
マウスが要素の上に入ったときに発生

mouseout
マウスが要素の上から離れたときに発生

mouseenter
マウスが要素の上から離れたときに発生(バブリング無し)

mouseleave
マウスが要素の上から離れたときに発生(バブリング無し)

イベントハンドラの引数のイベントオブジェクトには以下のプロパティが定義されています。

ブラウザ全体のX座標、Y座標
clientX
clientY

スクリーン(ディスプレイ)全体のX座標、Y座標
screenX
screenY

ページ全体のX座標、Y座標
  縦や横に長いページで、ブラウザのスクロールが最下部にある場合でも、
  ページの左上を基点として座標を取得します。
pageX
pageY

要素のX座標、Y座標(※)
offsetX
offsetY

※…要素が親+子+孫でネストされている場合は、
    親要素に登録したイベントハンドラがイベント伝播します。
    マウスが孫要素の上にある場合は、その孫要素のX座標、Y座標になります。

実際に簡単なサンプルを書いて試してみます。

イベントの登録は「DOM要素プロパティにイベントハンドラを登録する」方法で書いていますが、
これは実際には他の方法でイベントハンドラを登録する方法もあります。(後述します)

以下のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>

<body>

<div id="test1" style="width: 5000px; height: 5000px;">
	<div style="position:fixed; top:10px;">
		<div>clientX :<span id="client_x"></span></div>
		<div>clientY :<span id="client_y"></span></div>
		<div>screenX:<span id="screen_x"></span></div>
		<div>screenY:<span id="screen_y"></span></div>
		<div>pageX:<span id="page_x"></span></div>
		<div>pageY:<span id="page_y"></span></div>
		<div>offsetX:<span id="offset_x"></span></div>
		<div>offsetY:<span id="offset_y"></span></div>
	</div>
</div>

<script type="text/javascript">

	// 方法① DOM要素プロパティにイベントハンドラを登録する
	let element_test1 = document.getElementById("test1");

	element_test1.onmousemove = function(event) {

		// イベントオブジェクトから座標を取得
		let client_x = event.clientX;
		let client_y = event.clientY;
		let screen_x = event.screenX;
		let screen_y = event.screenY;
		let page_x = event.pageX;
		let page_y = event.pageY;
		let offset_x = event.offsetX;
		let offset_y = event.offsetY;

		// x座標出力用の要素を取得
		let ele_client_x = document.getElementById("client_x");
		let ele_client_y = document.getElementById("client_y");
		let ele_screen_x = document.getElementById("screen_x");
		let ele_screen_y = document.getElementById("screen_y");
		let ele_page_x = document.getElementById("page_x");
		let ele_page_y = document.getElementById("page_y");
		let ele_offset_x = document.getElementById("offset_x");
		let ele_offset_y = document.getElementById("offset_y");

		// 要素を更新する
		ele_client_x.innerHTML = client_x;
		ele_client_y.innerHTML = client_y;
		ele_screen_x.innerHTML = screen_x;
		ele_screen_y.innerHTML = screen_y;
		ele_page_x.innerHTML = page_x;
		ele_page_y.innerHTML = page_y;
		ele_offset_x.innerHTML = offset_x;
		ele_offset_y.innerHTML = offset_y;

	};

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面にアクセスすると、画面上には

clientX :130
clientY :190
screenX:594
screenY:545
pageX:130
pageY:690
offsetX:122
offsetY:682

※・・・数値は、サンプル用のダミーです。

のように各プロパティに応じた、マウスカーソル位置のX座標、Y座標が表示されます。

この例では、pageX、pageYの値を確認しやすいように、サンプル用にHTMLドキュメントのdivタグを

<div id="test1" style="width: 5000px; height: 5000px;">

というように大きく領域をとっています。

画面の下、あるいは右方向に大きくスクロールをして、
pageXとpageYを確認すると、「ページ全体から」のX座標、Y座標を取得していることがわかります。
(offsetX、offsetYもそうですが、これは要素を基準にしているので、別な考え方をします)

また、clientX、clientY、screenX、screenYの値がスクロールしても、
常に「ブラウザ全体」「スクリーン(ディスプレイ)全体」からの座標を取得していることがわかります。

プロパティの違いにより、取得できる値が異なるのでプログラミングする際には注意が必要です。

また、addEventListenerでイベントハンドラを登録する方法も試してみます。
以下のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>

<body>

<div id="test1" style="width: 5000px; height: 5000px;">
	<div style="position:fixed; top:10px;">
		<div>clientX :<span id="client_x"></span></div>
		<div>clientY :<span id="client_y"></span></div>
		<div>screenX:<span id="screen_x"></span></div>
		<div>screenY:<span id="screen_y"></span></div>
		<div>pageX:<span id="page_x"></span></div>
		<div>pageY:<span id="page_y"></span></div>
		<div>offsetX:<span id="offset_x"></span></div>
		<div>offsetY:<span id="offset_y"></span></div>
	</div>
</div>

<script type="text/javascript">

	// 方法② addEventListenerメソッドでイベントハンドラを登録する
	document.getElementById('test1').addEventListener('mousemove', function(e) {

		// イベントオブジェクトから座標を取得
		let client_x = event.clientX;
		let client_y = event.clientY;
		let screen_x = event.screenX;
		let screen_y = event.screenY;
		let page_x = event.pageX;
		let page_y = event.pageY;
		let offset_x = event.offsetX;
		let offset_y = event.offsetY;

		// x座標出力用の要素を取得
		let ele_client_x = document.getElementById("client_x");
		let ele_client_y = document.getElementById("client_y");
		let ele_screen_x = document.getElementById("screen_x");
		let ele_screen_y = document.getElementById("screen_y");
		let ele_page_x = document.getElementById("page_x");
		let ele_page_y = document.getElementById("page_y");
		let ele_offset_x = document.getElementById("offset_x");
		let ele_offset_y = document.getElementById("offset_y");

		// 要素を更新する
		ele_client_x.innerHTML = client_x;
		ele_client_y.innerHTML = client_y;
		ele_screen_x.innerHTML = screen_x;
		ele_screen_y.innerHTML = screen_y;
		ele_page_x.innerHTML = page_x;
		ele_page_y.innerHTML = page_y;
		ele_offset_x.innerHTML = offset_x;
		ele_offset_y.innerHTML = offset_y;

	});

</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

画面にアクセスすると、画面上にはtest1.htmlと同様の結果になることがわかります。

注意が必要な点は、
addEventListenerの時には、イベントハンドラ登録は「mousemove」と書きましたが、
DOM要素へのイベントハンドラ登録は「onmousemove」と書いている点です。

DOMContentLoadedイベント、loadイベント、readystatechangeイベント

Posted コメントするカテゴリー: javascript

DOMContentLoadedイベント

DOMContentLoadedイベントはドキュメントの読み込み、HTMLのパース、DOMの解釈(DOMツリーの構築)、
遅延実行スクリプトの実行が完了したら発生します。

プログラムからDOMContentLoadedを利用する方法はjavascriptのソース内でaddEventListenerの登録で使用できます。
ここで注意が必要なのは、DOMContentLoadedイベントはdocumentオブジェクトのイベントです。

loadイベント

loadイベントはwindowオブジェクトのイベントです。
DOMContentLoadedイベントと似ていますが、このloadイベントはページ全体が画像、CSSを含めて読み込まれた時に発生するイベントです。

readyStateプロパティ

ドキュメントをロードするとき、document.readyStateプロパティはドキュメントの状態を示します。
プログラム内では下記のように書いてその状態を取得します。

let state = document.readyState

この状態については、

loading        読み込み中
interactive    HTMLパース、DOMの解釈は完了、画像、css等のリソースは読み込み中
complete       HTMLパース、DOMの解釈、その他リソースの読み込みが完了

readystatechangeイベント

上記のイベントとは別に、HTML5ではreadystatechangeイベントを標準化しています。
readystatechangeはdocument.readyStateの値が変更したタイミングで発火します。
loadイベントの直前に発生します。

サンプル

実際の動きをHTMLを用意して試してみます。

まず、DOMContentLoadedイベントの動きをみてみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>

<script type="text/javascript">

	console.log("DOMContentLoaded state -> " + document.readyState);

	// DOMContentLoadedイベント
	document.addEventListener('DOMContentLoaded', function() {
		console.log("DOMContentLoaded イベント発火");
		console.log("画面の読み込み完了(DOMContentLoaded)");
		console.log("DOMContentLoaded state -> " + document.readyState);
	}, false);
</script>

<body>

<div>
	<div>
		DOMContentLoadedテスト<br />
	</div>
</div>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

上記のHTMLにアクセスし、console.logを確認すると、次のように出力されます。

DOMContentLoaded state -> loading
DOMContentLoaded イベント発火
画面の読み込み完了(DOMContentLoaded)
DOMContentLoaded state -> interactive

では、次にloadイベントの動きをみてみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>

<script type="text/javascript">

	console.log("load state -> " + document.readyState);

	// loadイベント
	window.addEventListener('load', function() {
		console.log("load イベント発火");
		console.log("画面の読み込み完了(load)");
		console.log("load state -> " + document.readyState);
	}, false);
</script>

<body>

<div>
	<div>
		loadテスト<br />
	</div>
</div>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

上記のHTMLにアクセスし、console.logを確認すると、次のように出力されます。

load state -> loading
load イベント発火
画面の読み込み完了(load)
load state -> complete

最後にreadystatechangeイベントの動きをみてみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>

<script type="text/javascript">

	console.log("readystatechange state -> " + document.readyState);

	// readystatechangeイベント
	document.addEventListener('readystatechange', function() {
		console.log("readystatechange イベント発火");
		console.log("readystatechange state -> " + document.readyState);
	}, false);

	// DOMContentLoadedイベント
	document.addEventListener('DOMContentLoaded', function() {
		console.log("DOMContentLoaded イベント発火");
		console.log("画面の読み込み完了(DOMContentLoaded)");
		console.log("DOMContentLoaded state -> " + document.readyState);
	}, false);

	// loadイベント
	window.addEventListener('load', function() {
		console.log("load イベント発火");
		console.log("画面の読み込み完了(load)");
		console.log("load state -> " + document.readyState);
	}, false);

</script>

<body>

<div>
	<div>
		readystatechange テスト<br />
	</div>
</div>

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

上記のHTMLにアクセスし、console.logを確認すると、次のように出力されます。

readystatechange state -> loading
readystatechange イベント発火
readystatechange state -> interactive
DOMContentLoaded イベント発火
画面の読み込み完了(DOMContentLoaded)
DOMContentLoaded state -> interactive
readystatechange イベント発火
readystatechange state -> complete
load イベント発火
画面の読み込み完了(load)
load state -> complete

ここでわかることは、DOMContentLoadedとloadイベントの処理順がわかります。

最初にloadイベントは、HTMLドキュメント内の画像やCSS等のリソース情報を「完全に」読み込んだ後に実行されます。
DOMContentLoadedはリソース情報の読み込みの前に、HTMLドキュメントのパース、DOMツリーの解析が終わった段階で実行されるので、loadイベントより先に実行されます。

また、readystatechangeについては、各イベントの状態(document.readyState)が変化したタイミングでそれぞれ呼ばれていることがわかります。

イベントのキャンセル

Posted コメントするカテゴリー: javascript

イベントのキャンセル

イベントのキャンセルについて試してみます。

イベントのキャンセルは以下の3つの書き方(メソッド)があり、それぞれイベント伝播がキャンセルされます。

preventDefault
stopPropagation
stopImmediatePropagation

キャンセルの方法はそれぞれ異なる動作仕様にもとづいてキャンセルされます。

それぞれのメソッドを詳しくみていきます。

preventDefaultについて

preventDefaultはデフォルトのイベントをキャンセルします。

ここでいうデフォルトとは、「aタグ」の場合、リンクを押下した時の動き(画面遷移)をキャンセルできます。

実際に下記のサンプルを書いて試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1l">
	<div>
		<a href="./test1linktest.html" target="blank" id="test1link">リンクテスト1</a>
	</div>
	<div>
		<a href="./test1linktest.html" target="blank" id="test2link">リンクテスト2</a>
	</div>
</div>

<script type="text/javascript">

	// リンク要素のDOMを取得
	let element_test1link = document.getElementById('test1link');

	// 外部関数化せず無名関数で書いてもOK
	element_test1link.addEventListener('click',  testclick1link, false);

	function testclick1link(event)
	{
		// preventDefaultの検証
		alert('testclick1link preventDefault START');
		
		event.preventDefault();
		
		alert('testclick1link preventDefault END');
	}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

HTMLにアクセスすると、画面上には

<a href="./test1linktest.html" target="blank" id="test1link">リンクテスト1</a>

<a href="./test1linktest.html" target="blank" id="test2link">リンクテスト2</a>

が表示されます。

「リンクテスト1」のほうには、イベントを登録してあり、その中で「preventDefault」メソッドを呼んでいます。
「リンクテスト2」には特にイベントを登録していません。

「リンクテスト1」と「リンクテスト2」をそれぞれクリックしてみると、
「リンクテスト1」のほうはアラートが2回表示され、その後は何も変化しません。(a hrefの機能が抑止されている)

「リンクテスト2」のほうは、イベントは登録していないので、そのまま確認用のテスト用HTMLへ画面遷移することがわかります。

このようにpreventDefault()はDOM要素のデフォルトの動きを抑止する働きがあります。
aタグ以外にもデフォルトのイベントを持つDOM要素は全て同様に抑止されます。

stopPropagationについて

イベントの伝播を抑止します。

同じイベントハンドラが他のDOMに定義されている場合(ネストされている場合)、子要素のイベントの処理で、stopPropagationを呼び出した後は、親要素のイベントハンドラは呼び出されません。
伝播については、キャプチャリングでもバブリングでも自分自身でも抑止されます。

具体的なサンプルを書いて試してみます。
前回の投稿「イベントの伝播(2022/06/10)」では、下記のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
 
<body>
 
<div id="test1p">
    テスト親
    <div id="test1c">
        テスト子
    </div>
</div>
 
<script type="text/javascript">
// 親要素にクリックイベントを登録
let element_test1p = document.getElementById('test1p');
element_test1p.addEventListener('click',  testclick1p, false);
function testclick1p() {
    alert('on p !');
}
 
// 子要素にクリックイベントを登録
let element_test1c = document.getElementById('test1c');
element_test1c.addEventListener('click',  testclick1c, false);
function testclick1c() {
    alert('on c !');
}
 
</script>
 
</body>
</html>

サーバ上のHTMLはこちら(test2.html)

画面上の「テスト子」をクリックすると、子要素のクリックイベントが動き、その後に親要素のクリックイベントも動きます。

ではこのサンプルの 子要素側の処理に stopPropagation を呼ぶように変更してみます。
以下のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
 
<body>
 
<div id="test1p">
    テスト親
    <div id="test1c">
        テスト子
    </div>
</div>
 
<script type="text/javascript">
// 親要素にクリックイベントを登録
let element_test1p = document.getElementById('test1p');
element_test1p.addEventListener('click',  testclick1p, false);
function testclick1p() {
    alert('on p !');
}
 
// 子要素にクリックイベントを登録
let element_test1c = document.getElementById('test1c');
element_test1c.addEventListener('click',  testclick1c, false);
function testclick1c(event) {
    alert('on c !');
    event.stopPropagation();
}
 
</script>
 
</body>
</html>

サーバ上のHTMLはこちら(test2_1.html)

テスト子のメソッド内で

event.stopPropagation();

という追記しています。

アクセスして「テスト子」をクリックすると、先程のサンプルとは違い、「on c !」のアラートのみが表示され、
「on o !」のアラートは表示されません。
stopPropagation()が実行されて親要素にイベントが伝播されなくなったことがわかります。

stopImmediatePropagationについて

同じDOM要素に対して、複数の同じイベントハンドラ(処理内容は別)が定義されている場合、
任意のイベント処理で stopImmediatePropagation を呼び出すと、その他のイベントハンドラは呼び出されません。
伝播についてもstopPropagationと同様に抑止されます。

具体的に次のHTMLを用意して試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
 
<body>
 
<div id="test1p">
    テスト親
</div>
 
<script type="text/javascript">

// 要素を取得
let element_test1p = document.getElementById('test1p');

// 要素にクリックイベント1を登録
element_test1p.addEventListener('click',  testclick1p, false);
function testclick1p(event) {
    alert('on p 1 !');
}

// 要素にクリックイベント2を登録
element_test1p.addEventListener('click',  testclick2p, false);
function testclick2p(event) {
    alert('on p 2 !');
}

// 要素にクリックイベント3を登録
element_test1p.addEventListener('click',  testclick3p, false);
function testclick3p(event) {
    alert('on p 3 !');
}

// 要素にクリックイベント4を登録
element_test1p.addEventListener('click',  testclick4p, false);
function testclick4p(event) {
    alert('on p 4 !');
}

</script>
 
</body>
</html>

サーバ上のHTMLはこちら(test3.html)

画面にアクセスして「テスト親」をクリックすると「on p 1 !」から「on p 4 !」まで、4回アラートが表示されます。

では、このHTMLに stopImmediatePropagation を追加してみます。
わかりやすいように3回目のアラート後に追記します。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
 
<body>
 
<div id="test1p">
    テスト親
</div>
 
<script type="text/javascript">

// 要素を取得
let element_test1p = document.getElementById('test1p');

// 要素にクリックイベント1を登録
element_test1p.addEventListener('click',  testclick1p, false);
function testclick1p(event) {
    alert('on p 1 !');
}

// 要素にクリックイベント2を登録
element_test1p.addEventListener('click',  testclick2p, false);
function testclick2p(event) {
    alert('on p 2 !');
}

// 要素にクリックイベント3を登録
element_test1p.addEventListener('click',  testclick3p, false);
function testclick3p(event) {
    alert('on p 3 !');
    event.stopImmediatePropagation();
}

// 要素にクリックイベント4を登録
element_test1p.addEventListener('click',  testclick4p, false);
function testclick4p(event) {
    alert('on p 4 !');
}

</script>
 
</body>
</html>

サーバ上のHTMLはこちら(test3_1.html)

クリックイベント3の処理内で

    event.stopImmediatePropagation();

と書くことでイベントの伝播を抑止しています。

画面にアクセスして「テスト親」をクリックすると「on p 1 !」「on p 2 !」「on p 3 !」までアラート表示され、
「on p 4 !」のイベント処理が抑止されていることがわかります。

イベントの伝播

Posted コメントするカテゴリー: javascript

イベントの伝播

前回の投稿でイベントの伝播(バブリングとキャプチャリング)について書きました。

バブリングはDOMツリーの各要素でイベントハンドラが呼び出された後、ツリーの親要素のイベントハンドラが呼び出されます。
親要素で登録したイベントハンドラは、その子要素でもイベントハンドラとして機能します。

実際にどんな動きになるのか、試してみます。
下記の簡単なサンプルを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>

<body>

<div id="test1p">
	テスト親
	<div id="test1c">
		テスト子
	</div>
</div>

<script type="text/javascript">
// 親要素にクリックイベントを登録
let element_test1p = document.getElementById('test1p');
element_test1p.addEventListener('click',  testclick1p, false);
function testclick1p() {
	alert('on p !');
}

// 子要素にクリックイベントを登録
let element_test1c = document.getElementById('test1c');
element_test1c.addEventListener('click',  testclick1c, false);
function testclick1c() {
	alert('on c !');
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

画面上には2つのDOM要素「テスト親」と「テスト子」があります。
親要素には「test1p」、子要素には「test1c」のidを割り当てています。

この時、「テスト親」をクリックすると「on p !」のアラートだけが表示されます。
次に「テスト子」をクリックすると「on c !」のアラートが表示され、続いて「on p !」のアラートだけが表示されます。
(呼び出される順としては 子要素 → 親要素 でした)

このように、親要素で登録したイベントハンドラはネストされている子要素のDOMにも同時に登録されます。

イベントのバブリングについて注意点

バブリングする際の注意点は
・forcus、blur、scrollはDocumentまでバブリングされ、それ以上のDOMにはバブリングされない
・Windowsオブジェクトのloadイベントはドキュメント全体がロードされた時のみに発生する

イベントのキャプチャリングについて

イベントのキャプチャリングは、イベントのバブリングとは伝播する順番が異なります。
Windowオブジェクト→Dobumentオブジェクト→bodyオブジェクト→HTMLのDOM構造(親要素→子要素→孫要素…)、の順でのキャプチャリングハンドラが呼び出されます。
DOM構造がネストされている場合は、最後の末端のDOM要素まで順に呼び出されます。

イベントキャプチャリング

キャプチャリングの際にイベントターゲットにイベントが伝播する前にイベント情報を参照することができます。
具体的な例は別な投稿で試す予定です。

イベントの呼び出し順序

Posted コメントするカテゴリー: javascript

イベントの呼び出し順序

要素に対して、複数のイベントハンドラを登録した場合に、その要素にイベントが発生した場合、
全てのイベントハンドラが呼び出されます。

その呼び出し時の動きとしては下記の仕様で順序で呼び出されます。(DOM level 3)

まず、オブジェクトプロパティやHTML属性を設定する方法で登録したイベントハンドラが存在すればそのイベントを呼び、
次にaddEventListener()で登録したイベントハンドラがあれば、登録した順で呼び出されます。

イベントハンドラの引数について

Posted コメントするカテゴリー: javascript

イベントハンドラの引数について

イベントハンドラが呼び出される際、引数にイベントオブジェクトが渡されます。
イベントオブジェクトのプロパティは、発生したイベントの種類によって異なります。

イベントの種類によらず共通したプロパティは下記のものがあります。
非推奨、廃止したプロパティは除き、それぞれの詳細な意味は書きません。(実装時に調べる等します)

Event.bubbles
Event.cancelable
Event.composed
Event.currentTarget
Event.defaultPrevented
Event.eventPhase
Event.target
Event.timeStamp
Event.type
Event.isTrusted

イベントハンドラのスコープについて

イベントハンドラはスコープを持ちます。
定義された時のスコープ内で実行されます。

イベントハンドラの戻り値について

オブジェクトプロパティ、HTML属性にイベントハンドラを登録した場合、戻り値が重要です。
submitボタンのonclickイベントに戻り値でfalseを指定するとサブミットの動作を無効化することができます。

addEventListener

Posted コメントするカテゴリー: javascript

addEventListener

イベントの登録をaddEventListener()を使って行うことができます。

基本構文は次のようになります。

対象の要素.addEventListener(イベントタイプ, 関数, イベントの伝播の判断(論理値))

対象の要素は、画面上のDOMを指定します。(通常はDOMに記述したidやclassです)

第一引数はイベントタイプを指定します。
イベントタイプは以下のものがあります。

load
DOMContentLoaded
click
mousedown
mouseup
mousemove
keydown
keyup
keypress
change
submmit
scroll

第二引数はイベントが発生した時に実行する関数を書きます。

第三引数は論理値なのでtrueかfalseを指定します。
デフォルトはfalseですが、明示的にfalseを書きます。

まずは簡単な例を試してみます。
下記のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用
addEventListener(element_test, testclick, false);

function testclick() {
	alert('on !');
}
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

上記の書き方は第二引数の関数を別途記載した関数名にしていますが、
第二引数の関数を無名関数にしてaddEventListenerの構文の中に記載する方法もあります。

具体的に次のhtmlを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用(その2)
element_test.addEventListener('click', function() {
	// 無名関数として定義、処理
	alert('on !');
}, false);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

画面上の「テスト」の文字列をクリックすると、最初のサンプルと同様の動きをしてアラートが表示されます。

また、ES2015の書き方としてアロー関数を使う方法もあります。
基本的には無名関数の書き方と似ていますが、関数を呼び出す箇所の書き方がアロー関数の書き方に変わります。

具体的に次のサンプルを書いて試してみました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用(その3)
element_test.addEventListener('click', () => {
	// アロー関数として定義、処理
	alert('on !');
}, false);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

アロー関数を使って書いた場合でも同じ動きをすることがわかります。

addEventListenerの重複登録について

addEventListenerの書き方と動作については上記の通りですが、
イベントを2重に登録する場合は、または一つの要素に複数の処理(関数)をつけたい場合は、
意図した動きと違う結果になるので、注意が必要です。

実際にaddEventListenerを2重に登録すると、どのような動きになるのか試してみます。
上記の例の1番目の登録方法で2重にイベントを登録してみます。

下記のサンプルを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用
element_test.addEventListener('click', testclick, false);

// 2重に登録する
element_test.addEventListener('click', testclick, false);

function testclick() {
	alert('on !');
}
</script>

</body>
</html>

要素「element_test」に対して、関数「testclick」を2回登録しています。

実際のサンプルはサーバ上のHTMLはこちら(test4.html)です。

実際に動かしてみるとわかりますが、画面上の「テスト」の文字列をクリックしても
1回のアラートしか表示されません。

では次に、同じサンプルを改造して、2回目に登録する関数名を変えて別名の関数を書いてみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用
element_test.addEventListener('click', testclick, false);

// 2重に登録する
element_test.addEventListener('click', testclick_sub, false);

function testclick() {
	alert('on !');
}

function testclick_sub() {
	alert('on sub !');
}
</script>

</body>
</html>

実際のサンプルはサーバ上のHTMLはこちら(test5.html)です。

この画面の「テスト」をクリックすると、2つアラートが起動することがわかります。

それでは、上記のサンプル4とサンプル5を、無名関数で書いてみます。

サンプル6として次のHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

// addEventListenerの使用(その2)
element_test.addEventListener('click', function() {
	// 無名関数として定義、処理
	alert('on !');
}, false);

// addEventListenerの使用(その2) 重複登録
element_test.addEventListener('click', function() {
	// 無名関数として定義、処理
	alert('on 2 !');
}, false);

</script>

</body>
</html>

実際のサンプルはサーバ上のHTMLはこちら(test6.html)です。

このサンプル6のテストをクリックすると、無名関数で登録した関数が2回呼び出されることがわかります。

以上のことをまとめると、addEventListenerは、

・同じ要素に、addEventListenerで同じ関数を重複登録しても、1つの処理(関数)のみ動作する
・同じ要素に、addEventListenerで違う関数を重複登録すると、2つの処理(関数)が動作する
・同じ要素に、無名関数で同一の処理を割り当ててると、2つの処理(関数)が動作する

ということが言えます。

イベントハンドラの登録と実行

Posted コメントするカテゴリー: javascript

イベントハンドラの登録と実行

ここからは実際にサンプルを書いて、動きを試してみます。

イベントハンドラについて、まずはイベントハンドラの登録をします。
登録方法はaddEventListener()のメソッドか、attacheEvent()のメソッドです。
後者は古いブラウザ(IE)でサポートされており、時期に使われることがなくなる為
原則的にaddEventListenerメソッドを使います。

イベントハンドラ登録の簡単な例を書いて試してみます。
具体的には以下のように書きます。

let element_test = document.getElementById('test1');

element_test.onclick = function() {
	alert('on !');
}

DOM要素である「test1」を参照し、その参照結果を変数に代入したうえで、onclickイベントを登録しています。

HTML全体は以下のようになります。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">テスト</div>

<script type="text/javascript">
let element_test = document.getElementById('test1');

element_test.onclick = function() {
	alert('on !');
}
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

アクセスして動きを確認してみると、画面上の「テスト」の文字列をクリックするとダイアログが表示されます。

このイベントハンドラの登録方法は、簡単な書き方ですが、実際に開発を進めるとイベント処理が複雑になってくるケースがあるので別な書き方でイベントハンドラを登録します。(後に投稿します)

イベントハンドラを直接HTML DOM要素内に記述する方法

イベントハンドラの登録方法は、上記サンプルの他、HTMLタグ内に記述する方法もあります。
以下のサンプルを試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1" onclick="alert('test1');">テスト1</div>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

動作をみてみると、「テスト1」という文字をクリックするとtest1.htmlと同様に、onclickイベントが動いていることがわかります。

ブラウザ全体に対して作用するイベントハンドラ

これまでのサンプルでは、画面上のDOM要素に対してイベントハンドラを登録して動かしてみました。
DOM要素ではなく、画面(ブラウザ)全体に対して登録するイベントハンドラもあります。

具体的にはwindowに

onafterprint
onbeforeprint
onbeforeunload
oncancel
onhashchange
onmessage
onoffline
ononline
onpagehide
onpageshow
onpopstate
onresize
onshow
onstorage

試しに「onresize」について、サンプルを書いて動かしてみます。

下記のサンプルHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">テスト</div>

<script type="text/javascript">
// ウィンドウ取得
let window_test = window;

window_test.onresize = function() {
	alert('onresize !');
}
</script>

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

実際にアクセスして、ブラウザのサイズを変更するとわかるのですが、
上記のサンプルでは、onresizeのイベントが発生する間隔が短く、ウィンドウサイズをいろいろと変更すると
何度もonresizeイベントが呼ばれていることがわかります(アラートのダイアログが何度も表示されます)。

最近の開発では、実際にはネイティブのjavascriptコードでイベントハンドラの登録や、制御を行うことは非常にすくなく、
外部javascirptライブラリやフレームワークの利用がメインなので、上記の書き方は参考程度にしておいたほうがよいかと思います。

DOMイベント

Posted コメントするカテゴリー: javascript

DOMイベント

DOMイベントはW3Cが仕様を策定しており、現在はLevel1からLevel4まで勧告されています。(※)


※・・・DOMとHTMLの仕様策定については、歴史的な背景があり、
W3C(World Wide Web Consortium(ワールド・ワイド・ウェブ・コンソーシアム))という標準化団体がDOM level1、level2、level3を策定していましたが、
その団体とは別にWHATWG(Web Hypertext Application Technology Working Group)という関連技術の開発をするためのコミュニティがW3Cとは別な形でDOMとHTMLの仕様を策定していました。
2019年にW3CとWHATWGが合意し、新しくDOM Living Standard という形で仕様を策定しました。
(2022年現在はこれが事実上の最新バージョンと言えます)

javascriptから操作できるイベントはDOMと密接な関係があり、Levelが増える毎に新しく使えるイベントが増えています(同時に廃止された仕様もあります)

また、各Levelごとに使えるイベントが増える代わりに、それまで書いていた命令より、
新しいLevelの書き方を推奨するケースがあります。

例えばforcusを書く代わりにforcusinにしたり、blurの代わりにforcusoutにするという具合です。
マウスホイールのイベントのwheelや、テキスト入力時のイベントtextinput等も新しく策定されたlevelの命令を書くように推奨されています

HTML5イベント

HTML5と関連するイベントも仕様追加されています。

audioの操作系イベントは下記のメソッドとプロパティがあります。

プロパティ
src
controls
volume
muted
defaultMuted
loop
currentTime
autoplay
ended

メソッド
play()
pause()
pause()

videoの操作系イベントは下記のメソッドとプロパティがあります。

プロパティ
src
poster
width
height
controls
volume
muted
defaultMuted
loop
currentTime
autoplay
ended

メソッド
play()
pause()
pause()

ドラッグ&ドロップもHTML5で追加されたAPIで、定義されているイベントは7つです。

dragstart // ドラッグ開始時
dragend   // ドラッグ継続時
drag      // ドロップ要素に入った時
dragenter // ドロップ要素から出た時
dragleave // ドロップ要素に重なっている時
dragover  // ドラッグ終了時
drop      // ドロップ時

また、ドラッグ操作の際、データを「DataTransferオブジェクト」にセットしています。
このオブジェクト格納するプロパティとしてDataTransferプロパティがイベントオブジェクトとして定義されています。

その他のHTML5イベント

その他のHTML5イベントとしては
オフラインWebアプリケーション、WebStorage(クライアントサイドでjavascriptアプリケーション内の情報を保持する方法)、FileAPI等があります。
今後の投稿で実際に動作を試していきます。

イベントの分類

Posted コメントするカテゴリー: javascript

イベントタイプ

webブラウザのバージョンアップに伴い、javascriptで取り扱えるイベントの種類は増えてきました。
また、DOMの仕様を策定するW3Cの活動や、HTML5の策定、
スマートフォン上でのタッチインタフェースの登場等に伴い新しいイベントが増えています。

注意が必要なのは、全てのイベントが全てのブラウザに標準搭載されているわけではなく、
動作するブラウザ環境やデバイスにより、javascriptで書けるイベントが異なる点です。

イベントの分類

イベントは、その内容により大まかに分類ができます。
ここでは分類ごとにざっと把握します。

■デバイス依存の入力系イベント
keydown、keypress、keyup、等のキーボード系イベント、
mousedown、mousemove、mouseup、等のマウス系イベント、
touchmove、等のスマートフォン系イベント
等があげられます。

■デバイス独立の入力系イベント
textinputイベント、等のテキストボックスへ文字入力時に発生する入力イベント

■ユーザインタフェースイベント
HTMLフォーム要素で発生します。
前述のtextinputイベントもそうですが、フォームのセレクトボックスの切り替え時に発生するchangeイベント、
フォームのボタン押下時のsubmitイベント等

■状態変化イベント
ブラウザにオブジェクトがロードされた際のロードイベント、ブラウザの履歴系イベント、サーバ間通信時のイベント、ローカルファイルの読み書きで使用するイベント等

■API固有イベント
HTML5により登場した、ドラッグ&ドロップイベント、映像(video)や音声要素(audio)をとりあつかうイベント

■タイマー、エラー
javascriptの非同期イベント、タイマーやエラー処理等

レガシーイベント

レガシーイベントは古くから実装されているマウス、キーボード、HTMLフォーム、Windowオブジェクト関連のイベントです。
以降、ひとつひとつイベントを試して理解を進めます。

レガシーイベント:フォームイベント

フォームはwebページ内のフォーム要素に対してのイベントです。
HTML要素としてフォームは

<form>

</form>

タグとしてフォーム領域を定義していますが、フォームイベントはフォームに対してユーザが操作したイベントを実行することができます。
具体的にはフォームの送信時にsubmitイベント、フォームリセット時にresetイベント、フォーム要素を操作した時にはclickイベント、チェックボックスやセレクトボックスの操作でchangeイベント、テキストボックスには文字列を入力状態にした時にfocusイベント、入力状態を解除した際にはblurイベント、等のイベントがあります。

レガシーイベント:windowイベント

次にwindowイベントですが、ブラウザに関連したイベントが定義されています。
ブラウザにDOM構造を読み込み終わった(画面に表示された)際に発生するloadイベントや、その反対を意味するunloadイベント、
javascriptの実行時エラーに呼ばれるonerrorイベント、
また、ブラウザ全体ではなく、imgタグの単体のドキュメントに対してもloadイベントがを呼ぶことができます。

また、ブラウザの大きさを変更した場合のreziseイベント、ブラウザ上でスクロールをした時のscrollイベント、等もあります。

レガシーイベント:マウスイベント

画面上でマウスを動かしたり、クリックした際にマウスイベントが発生します。

マウスイベントが発生する箇所(DOM要素)は、マウスポインタのある箇所の要素で発生します。
ネストされている場合は、ポインタが指し示す一番深いDOM要素になります。
イベントオブジェクトにはマウスの座標等の情報が定義されています。

マウス操作のイベントには

mousemove
mousedown
mouseup
mouseover
mousewheel

等のイベントがあります。
上記のうち、mousedownとmouseupはclickイベントも発生します。
また、画面上のDOM要素の同じ箇所を2回clickするとdblclick(ダブルクリック)イベントが発生します。

画面上のDOMに対して、マウスが要素の上に移動した時にはmouseoverイベントが発生します。

mousewheelイベントは、マウスのホイールを回したときのイベントを発生させます。

レガシーイベント:キーイベント

ブラウザに対してキーボードがアクティブになっている場合、キーボードの何かのキーを押下したり離した時にイベントが発生しています。

キーボードイベントは主に以下のものがあります。

keydown
keyup
keypress

keydownはキーボードを押下した時、keyupはキーボードを離した時、keypressはキーボードを押しっぱなしにした時に発生するイベントです。
イベントが発生した時に渡されるイベントオブジェクトは、keyCodeを持ち、どのキーを押したのかという情報が入っています。

イベントについて

Posted コメントするカテゴリー: javascript

イベントについて

イベントについてまとめていきます。
javascriptは非同期のイベントドリブン型のイベント処理を行います。
主にブラウザ上でユーザの何らかの操作がされた時、イベントが生成されます。
また、画面操作以外にもブラウザ上のドキュメントがロードされた時のイベントや、
ユーザのマウス操作、フォームの操作等にもイベントを生成しています。

webアプリケーションでは、イベントが発生した時の処理をまとめて関数として作成し、
それを登録して処理を行います。

イベントタイプについて

イベントタイプは発生したイベントの種類を示します。
Event.typeとして書き、Eventインタフェースの読み取り専用プロパティ「type」を示します。
発生したイベントの種別を表します。
例として

keydown    // キーが最初に押された時
keypress   // キーが押された時(Ctrl、Alt、Shiftを除く)
keyup      // キーを離した時

mousedown  // マウスを押下した時
mouseup    // マウスを離した時
click      // マウスをクリックした時

等があります。

イベントターゲット、イベントハンドラ、イベントオブジェクトについて

イベントが発生したオブジェクトや関連するオブジェクトをイベントターゲットといいます。
「何」に対して、「どのような」イベントが発生したのかを定義する為、イベントターゲットとイベントは組み合わせて考えます(実装します)

イベントハンドラはイベントを処理する関数です。
イベントタイプとイベントターゲットを指定してイベントハンドラ関数をブラウザに登録してプログラムします。
指定したターゲット上でイベントが発生するとイベントハンドラが呼び出されます。

イベントオブジェクトはイベントが発生し、イベントハンドラが呼び出された時に一番目の引数に発生したイベント情報が格納されたオブジェクトが渡ります。
このオブジェクトのことをイベントオブジェクトと呼びます。
例えば、キーボードを押したときに発生するイベントに対しては、イベントオブジェクトを継承したキーボードイベントオブジェクト(KeyboardEventオブジェクト)が渡されます。
このオブジェクトにはユーザが押下したキー情報があり、プロパティを指定して参照することができます。
例えば

KeyboardEvent.altKey
KeyboardEvent.code
KeyboardEvent.ctrlKey

等があり、altKeyはAltキーが押下されている場合はtrue(Boolean)を返し、
codeは押下されたキーのキーコードを返し、
ctrlKeyはCtrキーが押下されている場合はtrue(Boolean)を返す
という動きになります。

また、マウス操作の場合には、イベントオブジェクトを継承したマウスイベントオブジェクト情報が格納されたオブジェクトが渡ります。
このマウスオブジェクトにはマウスポインタの座標が含まれたり、マウスボタンの状態も含まれています。

イベント伝播

イベント伝播を理解するには、「バブリング」と「キャプチャリング」の2通りの考え方があります。

イベント伝播 : バブリングについて

まずバブリングと呼ばれる考え方は、「イベント伝播はイベントが発生した際、どの要素にイベントが発生するかを決定する処理」と言えます。
HTMLDOM構造の中で発生したイベントはその要素の親要素へイベントが伝播していきます。
単一のDOM構造では、ユーザ操作したオブジェクト(要素)に対してイベントが発生しますが、
深くネストされたDOM構造では、ユーザが操作したオブジェクト(要素)の親要素にもイベントが発生します。
またその親要素自体もネストされていれば、親要素の親要素にもイベントが発生します。
このようにして、イベントが上の階層へ伝わっていくことをイベントバブリング(単にバブリング)と呼びます。

また、意図的に子要素のみでイベントを処理し、バブリングをさせない方法もあります。
「Event.bubbles」プロパティがfalseになっているとバブリングをせず、その要素のみでイベントが発生します。

イベント伝播 : キャプチャリングについて

次にキャプチャリングについてですが、イベントのバブリングとは逆に、ネストされたDOM構造の子要素で発生したイベントに対して、
Documentオブジェクト、html要素、body要素、div要素…と、DOMツリー構造の子、孫、その孫…という順番にイベントが伝播していくことを言います。

実際にはイベントリスナーの登録の際に、その要素がキャプチャリングの対象とするかを指定して呼び出します(デフォルトはfalseになっています)。
もし、キャプチャリングの対象とした場合は、目的の孫要素にイベント伝播が到達する前に、その上の階層のDOM要素で登録したイベントを呼ぶことができます。

以降の投稿では、これらのイベントについて、深く掘り下げて試していきます。

javascriptの命令で画面をスクロールさせる

Posted コメントするカテゴリー: javascript

javascriptの命令で画面をスクロールさせる

スクロールの設定について、試します。
以前の投稿(https://propansystem.net/blog/2022/04/26/post-7753/)では、スクロール位置を取得しましたが、
今回は、スクロールをjavascript側から設定する方法です。

スクロールを設定するメソッドは次のものがあります。
scrollTo()
scrollBy()

scrollToメソッドはスクロールの初期位置を設定します。
また、scrollByメソッドは、現在のスクロール位置から加算して、スクロール位置を設定します。

実際に次のようなサンプルを書いて試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1" style="width: 5000px; height: 5000px;">
	<div style="position:fixed; top:10px;">
		<div>X座標:<span id="x1"></span></div>
		<div>Y座標:<span id="y1"></span></div>
	</div>
</div>

<script type="text/javascript">
// ウィンドウ取得
let window_test = window;

// スクロールされたらメソッド「output_log_scroll」を呼ぶ
window.addEventListener('scroll', output_log_scroll);

// スクロール位置を強制指定
scrollTo(100, 200);

// スクロールの現在位置から、座標を加算する(この場合は、500, 700の位置になる)
scrollBy(400, 500);

// スクロール位置(X座標、Y座標)を出力する
function output_log_scroll()
{
	// x座標出力用の要素を取得
	let x1 = document.getElementById("x1");
	let y1 = document.getElementById("y1"); 

	// 要素を更新する
	x1.innerHTML = window_test.pageXOffset;
	y1.innerHTML = window_test.pageYOffset;

	console.log("window pageXOffset -> " + window_test.pageXOffset);
	console.log("window pageYOffset -> " + window_test.pageYOffset);
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

上記のソースと、実際にブラウザでアクセスしてみるとわかりますが、
最初のscrollToメソッドで「横方向に100、縦方向に200」のスクロール位置を決定していますが、
次のscrollByメソッドで「横方向に400、縦方向に500」のスクロール位置を決定していので
結果的に「横方向に500、縦方向に700」のスクロール位置になっていることがわかります。

動的にスクロール位置を変更する

次に、スクロール位置を動的に変更してみます。
実際の開発では利用機会はほとんどありませんが、ここでは実験的な意味をこめてサンプルを書いてみます。

サンプルHTMLは次のように書きました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1" style="width: 5000px; height: 5000px;">
	<div style="position:fixed; top:10px;">
		<div>X座標:<span id="x1"></span></div>
		<div>Y座標:<span id="y1"></span></div>
	</div>
</div>

<script type="text/javascript">
// ウィンドウ取得
let window_test = window;

// スクロールされたらメソッド「output_log_scroll」を呼ぶ
window.addEventListener('scroll', output_log_scroll);

// 50ミリ秒ごとに、スクロール値を加算していく
setInterval(function() {scrollBy(10, 25)}, 50);

// スクロール位置(X座標、Y座標)を出力する
function output_log_scroll()
{
	// x座標出力用の要素を取得
	let x1 = document.getElementById("x1");
	let y1 = document.getElementById("y1");

	// 要素を更新する
	x1.innerHTML = window_test.pageXOffset;
	y1.innerHTML = window_test.pageYOffset;

	console.log("window pageXOffset -> " + window_test.pageXOffset);
	console.log("window pageYOffset -> " + window_test.pageYOffset);
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

画面にアクセスしてみると、50ミリ秒ごとに

scrollBy(10, 25)

を実行して、スクロール位置が変化することがわかります。

ただし、画面上にあるdivタグ

<div id="test1" style="width: 5000px; height: 5000px;">
	<div style="position:fixed; top:10px;">
		<div>X座標:<span id="x1"></span></div>
		<div>Y座標:<span id="y1"></span></div>
	</div>
</div>

は、もともと幅、高さが5000pxのサイズを指定している為、スクロール位置がそれ以上になると、見た目の変化は止まります。

では、スクロールの加算が止まったあとは、setIntervalの命令はどうなるか、ログを入れて試してみます。

setInterval(function() {scrollBy(10, 25)}, 50);

let scroll_counter = 0;
setInterval(function() {
	scroll_counter++;
	console.log('scroll_counter -> ' + scroll_counter);
	scrollBy(10, 25)
}, 50);

という形で書き換えて実行してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1" style="width: 5000px; height: 5000px;">
	<div style="position:fixed; top:10px;">
		<div>X座標:<span id="x1"></span></div>
		<div>Y座標:<span id="y1"></span></div>
	</div>
</div>

<script type="text/javascript">
// ウィンドウ取得
let window_test = window;
let scroll_counter = 0;

// スクロールされたらメソッド「output_log_scroll」を呼ぶ
window.addEventListener('scroll', output_log_scroll);

// 50ミリ秒ごとに、スクロール値を加算していく
setInterval(function() {
	scroll_counter++;
	console.log('scroll_counter -> ' + scroll_counter);
	scrollBy(10, 25)
}, 50);

// スクロール位置(X座標、Y座標)を出力する
function output_log_scroll()
{
	// x座標出力用の要素を取得
	let x1 = document.getElementById("x1");
	let y1 = document.getElementById("y1"); 

	// 要素を更新する
	x1.innerHTML = window_test.pageXOffset;
	y1.innerHTML = window_test.pageYOffset;

	console.log("window pageXOffset -> " + window_test.pageXOffset);
	console.log("window pageYOffset -> " + window_test.pageYOffset);
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

実際に画面アクセスして開発者ツールでログを確認すると、スクロールの動きが止まってもscrollByの命令は実行され続けています。
サンプルとして実験的に書いたプログラムなので、実際の開発ではこのようなことはしませんが、スクロールメソッドとその挙動を確認する為に試してみました。

要素の大きさ、位置の取得する

Posted コメントするカテゴリー: javascript

要素の大きさ、位置の取得

要素の大きさや、位置を取得する方法を試してみます。

ここでは、要素の大きさや位置を getBoundingClientRect() を使って取得します。
このメソッドで取得できる値は、ビューポート座標での要素位置になります。

実際に下記のサンプルを書いて動作させてみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div>
	<div>ウィンドウサイズ(getBoundingClientRect) :<span id="x1"></span></div>
</div>

<script type="text/javascript">
// ウィンドウ取得
let window_test = window;

//ロード時に一度呼ぶ
output_log_resize();

// リサイズされたらメソッド「output_log_resize」を呼ぶ
window.addEventListener('resize', output_log_resize);

// スクロール位置(X座標、Y座標)を出力する
function output_log_resize()
{
	let x1 = document.getElementById("x1");
	let cr = x1.getBoundingClientRect();
	console.log("cr -> " + cr);

	// オブジェクトの内容を確認する
	for(var key in cr){
		console.log(key + " : " + cr[key]);
	}

	// 要素を更新する
	x1.innerHTML = cr;

	console.log("window cr -> " + cr);
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

上記URLにアクセスして、ウィンドウの幅や高さを変えたタイミングで開発者ツールのログには、
以下のように出力されます。

cr -> [object DOMRect]
x : 8
y : 8
width : 884
height : 24
top : 8
right : 892
bottom : 32
left : 8
toJSON : function toJSON() { [native code] }
window cr -> [object DOMRect]

また、上記の出力を確認した後、ウィンドの幅や高さを変更してみました。
すると、以下の出力に変わりました。

cr -> [object DOMRect]
x : 8
y : 8
width : 1073
height : 24
top : 8
right : 1081
bottom : 32
left : 8
toJSON : function toJSON() { [native code] }
window cr -> [object DOMRect]

ブラウザの幅に応じて、divタグの幅も変わっていることがわかります。
ただし、ブラウザの幅と高さを変更しても、divタグの縦方向には広がらない為、
高さを合わらず数値は変化しないこともわかります。

また、この出力で特徴的なのが「DOMRect」というオブジェクトが返ることです。
DOMRectは矩形を管理する機能を備えたインターフェイスで、
単独で新規のDOMRectオブジェクトを生成する時は次のように記載します。

let domRect = new DOMRect(0, 0, 100, 100);

引数はそれぞれ、x座標、y座標、幅、高さ、を表し、
newで生成すると、引数で指定した大きさのDOMRectオブジェクトを生成されます。

今回のサンプルでは「getBoundingClientRect」メソッドを使って、

	let x1 = document.getElementById("x1");
	let cr = x1.getBoundingClientRect();

の箇所で、画面上のDOM要素「x1」のHTMLDOMを基準として、
要素の寸法と、ビューポートに対しての相対位置情報を取得していることが確認できます。

ウィンドウの幅と高さを取得する

Posted コメントするカテゴリー: javascript

ウィンドウサイズの取得

javascriptでウィンドウサイズを取得する方法を試してみます。

ウィンドウサイズの取得は、windowオブジェクトのinnerWidth、innerHeightプロパティを使います。

innerWidthはウィンドウの内部の幅を取得し、
innerHeightはウィンドウの内部の高さを取得します。

実際に下記のサンプルを書いて動作させてみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div>
	<div>ウィンドウサイズ X軸:<span id="x1"></span></div>
	<div>ウィンドウサイズ Y軸:<span id="y1"></span></div>
	</div>
</div>

<script type="text/javascript">
// ウィンドウ取得
let window_test = window;

// リサイズされたらメソッド「output_log_resize」を呼ぶ
window.addEventListener('resize', output_log_resize);

// スクロール位置(X座標、Y座標)を出力する
function output_log_resize()
{
	// x座標出力用の要素を取得
	let x1 = document.getElementById("x1");
	let y1 = document.getElementById("y1");

	// 要素を更新する
	x1.innerHTML = window_test.innerWidth;
	y1.innerHTML = window_test.innerHeight;

	console.log("window pageXOffset -> " + window_test.pageXOffset);
	console.log("window pageYOffset -> " + window_test.pageYOffset);
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

アクセスしてウィンドウの幅や高さを変えたタイミングでウィンドウ内部の幅と高さを取得できています。
ただし、このプロパティは読み取り専用なので、ウィンドウの幅を変更することはできないので、
ウィンドウサイズを変更するには別なメソッドを使う必要があります。

スクロール座標の取得

Posted コメントするカテゴリー: javascript

スクロール座標の取得

javascriptでの要素の位置を取得する方法を試してみます。

今回はスクロールバーの位置を取得してみます。

まず、要素の位置(座標)の取得の前に、座標の考え方として、
次の考えがあります。

横方向の X座標はウィンドウの左上を0とし、右方向に増える。
縦方向の Y座標はウィンドウの左上を0とし、下方向に増える。

また、javascriptではドキュメントを基に決定する方法と、
ウィンドウ(他のビューポート含む)を基に決定する方法があります。

css等ではビューポートを基準として座標を決定していますが、
javascriptではどちらの方法で取得するか、メソッドによって決まります。

上記を踏まえて、スクロールバーの座標位置を取得してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1" style="width: 5000px; height: 5000px;">
	<div style="position:fixed; top:10px;">
		<div>X座標:<span id="x1"></span></div>
		<div>Y座標:<span id="y1"></span></div>
	</div>
</div>

<script type="text/javascript">
// ウィンドウ取得
let window_test = window;

// スクロールされたらメソッド「output_log_scroll」を呼ぶ
window.addEventListener('scroll', output_log_scroll);

// スクロール位置(X座標、Y座標)を出力する
function output_log_scroll()
{
	// x座標出力用の要素を取得
	let x1 = document.getElementById("x1");
	let y1 = document.getElementById("y1");

	// 要素を更新する
	x1.innerHTML = window_test.pageXOffset;
	y1.innerHTML = window_test.pageYOffset;

	console.log("window pageXOffset -> " + window_test.pageXOffset);
	console.log("window pageYOffset -> " + window_test.pageYOffset);
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

上記のソースと、実際にブラウザでアクセスしてみるとわかりますが、
一つの大きなdiv要素を用意し、上下にスクロールを出しています。

また、自分自身のwindowオブジェクトを取得する為に、windowオブジェクトを定義しています。

そしてそこでwindowオブジェクトのプロパティ「pageXOffset」と「pageYOffset」を取得し、
画面とコンソールログに出力しています。

画面出力の為に各divタブにはcssを指定していますが、取得結果を表示する為に描画位置を固定しています。

DocumentFragment

Posted コメントするカテゴリー: javascript

DocumentFragment

DocumentFragmentは通常のノードとは少し違い、複数のノードをまとめて持つことができます。

また、単独で存在し、使用することができます。

DocumentFragmentのノードは、parentNodeを持つことがなくnullになります。
子ノードを持つことができるので、そのノードは操作することができます。

DocumentFragmentを使った実例を使って、どんな動きをするのかを確認してみます。

まずは、DocumentFragmentを作成してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="p1" name="p1name">
	<div id="c1" name="c1name">c1text</div>
	<div id="c2" name="c2name">c2text</div>
	<div id="c3" name="c3name">c3text</div>
</div>

<script type="text/javascript">
// DocumentFragmentを作成する
let frag = document.createDocumentFragment();

console.log(frag);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

出力結果ですが、コンソールログには以下のように出力されます。

DocumentFragment []
​
baseURI: "https://propansystem.net/blogsample/js/040/test1.html"
​
childElementCount: 0
​
childNodes: NodeList []
​
children: HTMLCollection { length: 0 }
​
firstChild: null
​
firstElementChild: null
​
isConnected: false
​
lastChild: null
​
lastElementChild: null
​
nextSibling: null
​
nodeName: "#document-fragment"
​
nodeType: 11
​
nodeValue: null
​
ownerDocument: HTMLDocument https://propansystem.net/blogsample/js/040/test1.html
​
parentElement: null
​
parentNode: null
​
previousSibling: null
​
textContent: ""
​
<prototype>: DocumentFragmentPrototype { getElementById: getElementById(), querySelector: querySelector(), querySelectorAll: querySelectorAll(), … }

これは内容が空のDocumentFragmentノードが生成されたことを表します。

また、DocumentFragmentノードを作成しただけでは、まだHTMLドキュメントには何も影響を及ぼさないです。

別な方法だと、オブジェクトのコンストラクタを使用してnewすることもできます。

let frag = new DocumentFragment();

上記のように書くと同じノード「frag」が生成されます。

DocumentFragmentを使ってノードを生成する

では次にDocumentFragmentを使ってHTMLドキュメントに対してノードを追加してみます。

先ほどのHTMLサンプルに次のコードを追記しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="p1" name="p1name">
	<div id="c1" name="c1name">c1text</div>
	<div id="c2" name="c2name">c2text</div>
	<div id="c3" name="c3name">c3text</div>
</div>

<script type="text/javascript">
// DocumentFragmentを作成する
let frag = document.createDocumentFragment();

// 子ノードを作成する
let c4 = document.createElement('c4');
let c5 = document.createElement('c5');
let c6 = document.createElement('c6');

frag.append(c4);
frag.append(c5);
frag.append(c6);

console.log(frag);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

出力結果は以下のようになります。

DocumentFragment(3) [ c4, c5, c6 ]
​
baseURI: "https://propansystem.net/blogsample/js/040/test2.html"
​
childElementCount: 3
​
childNodes: NodeList(3) [ c4, c5, c6 ]
​
children: HTMLCollection { 0: c4, 1: c5, length: 3, … }
​
firstChild: <c4>
​
firstElementChild: <c4>
​
isConnected: false
​
lastChild: <c6>
​
lastElementChild: <c6>
​
nextSibling: null
​
nodeName: "#document-fragment"
​
nodeType: 11
​
nodeValue: null
​
ownerDocument: HTMLDocument https://propansystem.net/blogsample/js/040/test2.html
​
parentElement: null
​
parentNode: null
​
previousSibling: null
​
textContent: ""
​
<prototype>: DocumentFragmentPrototype { getElementById: getElementById(), querySelector: querySelector(), querySelectorAll: querySelectorAll(), … }

ここでは、DocumentFragmentノードに対して、c4、c5、c6の子ノードが追加されたことがわかります。

では次にDocumentFragmentを他のノードに追加してみます。
先ほどの例のHTMLを使い、「p1」のノードに対して、fragを追加してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="p1" name="p1name">
	<div id="c1" name="c1name">c1text</div>
	<div id="c2" name="c2name">c2text</div>
	<div id="c3" name="c3name">c3text</div>
</div>

<script type="text/javascript">
// DocumentFragmentを作成する
let frag = document.createDocumentFragment();

// 子ノードを作成する
let c4 = document.createElement('div');
let c5 = document.createElement('div');
let c6 = document.createElement('div');

// 作成した子ノードに、HTMLタグとテキストをセットする
c4.appendChild(document.createTextNode('c4text'));
c5.appendChild(document.createTextNode('c5text'));
c6.appendChild(document.createTextNode('c6text'));

frag.append(c4);
frag.append(c5);
frag.append(c6);

console.log(frag);

// Elementノードを取得
let p1 = document.getElementById("p1");

// p1のノードにDocumentFragmentを追加する
p1.append(frag);

</script>

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

出力結果についてですが、まず、console.logは以下のようになります。

DocumentFragment(3) [ div, div, div ]
​
baseURI: "https://propansystem.net/blogsample/js/040/test3.html"
​
childElementCount: 0
​
childNodes: NodeList []
​
children: HTMLCollection { length: 0 }
​
firstChild: null
​
firstElementChild: null
​
isConnected: false
​
lastChild: null
​
lastElementChild: null
​
nextSibling: null
​
nodeName: "#document-fragment"
​
nodeType: 11
​
nodeValue: null
​
ownerDocument: HTMLDocument https://propansystem.net/blogsample/js/040/test3.html
​
parentElement: null
​
parentNode: null
​
previousSibling: null
​
textContent: ""
​
<prototype>: DocumentFragmentPrototype { getElementById: getElementById(), querySelector: querySelector(), querySelectorAll: querySelectorAll(), … }

次に、画面上のHTMLソースは以下のようになります。(bodyタグ内だけの抜粋です)

<div id="p1" name="p1name">
	<div id="c1" name="c1name">c1text</div>
	<div id="c2" name="c2name">c2text</div>
	<div id="c3" name="c3name">c3text</div>
	<div>c4text</div>
	<div>c5text</div>
	<div>c6text</div>
</div>

どうゆう動作になったかというと、上の結果からもわかるとおり、
DocumentFragmentノードそのものは、元のp1の親ノードには影響を及ぼさず、もともとp1が持っていた
子ノードにも影響を及ぼさずに、DocumentFragmentにappendした子ノード「c4」「c5」「c6」だけがp1のノードに追加されています。

上記のような動きをするので、DocumentFragmentは一時的なDOMツリーを生成し、
そこに子ノードを追加してひとまとめにして、他の親ノードに対してまとめた子ノードを追加する。
といった場面で使えると言えます。

ノードの置換について

Posted コメントするカテゴリー: javascript

ノードの置換について

ノードの置換について試してみます。
一つ前の投稿でノードの削除をやりましたが、今回は削除するノードを置き換える動きです。
こちらも削除と同様に親ノードからたどって子ノードを指定し、そのノードを置き換えます。

実際にサンプルを用意して、ノードの置換をしてみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">
		<div id="p2" name="p2name">p2text</div>
		<div id="p3" name="p3name">p3text</div>
		<div id="p4" name="p4name">p4text</div>
		<div id="p5" name="p5name">p5text</div>
		<div id="p6" name="p6name">p6text</div>
	</div>
</div>

<script type="text/javascript">
// Elementノードを取得
let p1 = document.getElementById("p1");
let p2 = document.getElementById("p2");
let p3 = document.getElementById("p3");
let p4 = document.getElementById("p4");
let p5 = document.getElementById("p5");
let p6 = document.getElementById("p6");

// テスト用のノードを生成
let tn1 = document.createTextNode(" _TextNode1_ ");

// 取得した親ノード「p1」の子ノード「p4」を「tn1」に置換する
p1.replaceChild(tn1, p4);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

出力結果は次のようになります。
 ※…アクセスした画面のHTMLソースを抜粋

<div id="p1" name="p1name">
	<div id="p2" name="p2name">p2text</div>
	<div id="p3" name="p3name">p3text</div>
	 _TextNode1_ 
	<div id="p5" name="p3name">p5text</div>
	<div id="p6" name="p3name">p6text</div>
</div>

親ノードからたどり、4つめのノードが、生成した「_TextNode1_」ノードに置き換わっています。

注意が必要なのは、置き換える内容は、ノードでなければ動作はしません。
例えば

p1.replaceChild('test', p4);

という書き方でノードの「p4」を置き換えようとしても、第一引数に単純な文字列を指定しているので、関数が動作しません。
第一引数には「createElement」や「createTextNode」で生成した要素を指定することで、ノードの置換が可能になります。

ノードの削除について

Posted コメントするカテゴリー: javascript

ノードの削除について

ノードの削除について試してみます。
削除時の注意としては、削除する命令は親ノードに対して行い、
削除したい子ノードの何番目を削除するのかを指定します。

実際にサンプルを用意して、ノードの削除を試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">
		<div id="p2" name="p2name">p2text</div>
		<div id="p3" name="p3name">p3text</div>
		<div id="p4" name="p3name">p4text</div>
	</div>
</div>

<script type="text/javascript">
// Elementノードを取得
let p1 = document.getElementById("p1");
let p2 = document.getElementById("p2");
let p3 = document.getElementById("p3");
let p4 = document.getElementById("p4");

// 取得した親ノード「p1」の子ノード「p3」を削除
p1.removeChild(p3);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

出力結果は次のようになります。
 ※…アクセスした画面のHTMLソースを抜粋

<div id="p1" name="p1name">
		<div id="p2" name="p2name">p2text</div>
		
		<div id="p4" name="p3name">p4text</div>
	</div>

上記のjavascriptを実行した結果、子ノードの

<div id="p3" name="p3name">p3text</div>

が削除されていることがわかります。

また、親ノードから辿って子ノードの指定ができれば削除ができるので、
次のような書き方も可能です。

p3.parentNode.removeChild(p3);

上記の場合は、p3の子ノードから辿り、いったんparentNodeメソッドを使って、p3の親ノードをみて、
その後にremoveChildメソッドを使っています。

少し変則的ですが、下記のような書き方もできます。

p3.parentNode.removeChild(p2);

この場合は、p3の子ノードからparentNodeでp1を参照し、その後に子ノードである「p2」を削除しています。

出力結果は、

<div id="p1" name="p1name">
		
		<div id="p3" name="p3name">p3text</div>
		<div id="p4" name="p3name">p4text</div>
	</div>

という結果になります。

ノードの挿入について

Posted コメントするカテゴリー: javascript

ノードの挿入について

前回の投稿ではノードを作成しましたが、今回は、作成したノードを
HTMLドキュメントに挿入する方法を試してみます。

実際にサンプルを用意して、ノードの挿入を試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">p1text</div>
</div>

<script type="text/javascript">
// ID「test1」のノードを作成
let tn1 = document.createTextNode(" _TextNode1_ ");
console.log("tn1 -> " + tn1);

// Elementノードを取得
let p1 = document.getElementById("p1");

// 取得したノード「p1」に「tn1」を挿入
p1.appendChild(tn1);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

出力結果は次のようになります。
 ※…HTMLソースを抜粋

<div id="test1">
	<div id="p1" name="p1name">p1text _TextNode1_ </div>
</div>

また、appendChild()メソッドの他、insertBefore()メソッドもあります。
insertBeforeメソッドは、appendChildとは違い、挿入する位置が「前」になります。

具体的に次のようなサンプルを書いて、試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">p1text</div>
	<div id="p2" name="p2name">p2text</div>
	<div id="p3" name="p3name">p3text</div>
	<div id="p4" name="p3name">p4text</div>
</div>

<script type="text/javascript">
// ID「test1」のノードを作成
let tn1 = document.createTextNode(" _TextNode1_ ");
console.log("tn1 -> " + tn1);

// Elementノードを取得
let p1 = document.getElementById("p1");
let p2 = document.getElementById("p2");
let p3 = document.getElementById("p3");
let p4 = document.getElementById("p4");

// 取得したノード「p2」の前に「tn1」を挿入
p1.insertBefore(tn1, p2);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

上記を実行し結果、「_TextNode1_」は画面上にどう出力されるか試してみました。

結果、予想とは違い、

console出力上では、以下のエラーメッセージが出ました。

tn1 -> [object Text]
test2.html:26 Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node.
    at https://propansystem.net/blogsample/js/037/test2.html:26:4

出力されたエラーメッセージを読むと、

「新しいノードが挿入される前のノードは、このノードの子ではありません。」

という意味になり、insertBeforeはノードの子要素に対しての命令であることがわかります。

エラー内容を踏まえて、上記のサンプルを書き変えて、次のようにしてみました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">
		<div id="p2" name="p2name">p2text</div>
		<div id="p3" name="p3name">p3text</div>
		<div id="p4" name="p3name">p4text</div>
	</div>
</div>

<script type="text/javascript">
// ID「test1」のノードを作成
let tn1 = document.createTextNode(" _TextNode1_ ");
console.log("tn1 -> " + tn1);

// Elementノードを取得
let p1 = document.getElementById("p1");
let p2 = document.getElementById("p2");
let p3 = document.getElementById("p3");
let p4 = document.getElementById("p4");

// 取得したノード「p2」の前に「tn1」を挿入
p1.insertBefore(tn1, p2);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

htmlにアクセスすると、画面には

_TextNode1_
p2text
p3text
p4text

という結果が出力されます。

「id=”p2″」のノードの前に、新規作成した「_TextNode1_」のノードが挿入されていることがわかります。

さらに、「ノード「p3」の前に「tn1」を挿入」したパターンも試してみます。

htmlは以下のようにしました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">
		<div id="p2" name="p2name">p2text</div>
		<div id="p3" name="p3name">p3text</div>
		<div id="p4" name="p3name">p4text</div>
	</div>
</div>

<script type="text/javascript">
// ID「test1」のノードを作成
let tn1 = document.createTextNode(" _TextNode1_ ");
console.log("tn1 -> " + tn1);

// Elementノードを取得
let p1 = document.getElementById("p1");
let p2 = document.getElementById("p2");
let p3 = document.getElementById("p3");
let p4 = document.getElementById("p4");

// 取得したノード「p3」の前に「tn1」を挿入
p1.insertBefore(tn1, p3);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test4.html)

出力結果は、以下のようになります。

p2text
_TextNode1_
p3text
p4text

insertBeforeメソッドの第2引数を「id=”p3″」にしたため、
新規作成した「_TextNode1_」のノードが

<div id="p3" name="p3name">p3text</div>

の前に挿入されていることがわかります。

ノードの作成について

Posted コメントするカテゴリー: javascript

ノードの作成について

HTMLに対して、javascriptで動的にノード(Node型)を作成する方法があります。

Node型はツリー構造にすることができ、そのツリー構造内で新たにノードを挿入したり削除することができます。
また、置き換えることもできます。

実際にサンプルを用意して、ノードの作成を試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">p1text</div>
</div>

<script type="text/javascript">
// ID「test1」のノードを作成
let tn1 = document.createTextNode("test1");

console.log("tn1 -> " + tn1);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

出力結果は次のようになります。

tn1 -> [object Text]

上記の結果、テキストオブジェクトが生成されていることがわかります。

では、このテキストオブジェクトの中身を展開して、一つづつ出力してみます。
上記のHTMLファイル内に次のように追記します。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">p1text</div>
</div>

<script type="text/javascript">
// ID「test1」のノードを作成
let tn1 = document.createTextNode("test1");

console.log("tn1 -> " + tn1);

// オブジェクトの内容を確認する
for(var key in tn1){
	console.log(key + " : " + tn1[key]);
}

</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

結果は次のようになります。

wholeText : test1
assignedSlot : null
splitText : function splitText() { [native code] }
data : test1
length : 5
previousElementSibling : null
nextElementSibling : null
after : function after() { [native code] }
appendData : function appendData() { [native code] }
before : function before() { [native code] }
deleteData : function deleteData() { [native code] }
insertData : function insertData() { [native code] }
remove : function remove() { [native code] }
replaceData : function replaceData() { [native code] }
replaceWith : function replaceWith() { [native code] }
substringData : function substringData() { [native code] }
nodeType : 3
nodeName : #text
baseURI : https://propansystem.net/blogsample/js/036/test1.html
isConnected : false
ownerDocument : [object HTMLDocument]
parentNode : null
parentElement : null
childNodes : [object NodeList]
firstChild : null
lastChild : null
previousSibling : null
nextSibling : null
nodeValue : test1
textContent : test1
ELEMENT_NODE : 1
ATTRIBUTE_NODE : 2
TEXT_NODE : 3
CDATA_SECTION_NODE : 4
ENTITY_REFERENCE_NODE : 5
ENTITY_NODE : 6
PROCESSING_INSTRUCTION_NODE : 7
COMMENT_NODE : 8
DOCUMENT_NODE : 9
DOCUMENT_TYPE_NODE : 10
DOCUMENT_FRAGMENT_NODE : 11
NOTATION_NODE : 12
DOCUMENT_POSITION_DISCONNECTED : 1
DOCUMENT_POSITION_PRECEDING : 2
DOCUMENT_POSITION_FOLLOWING : 4
DOCUMENT_POSITION_CONTAINS : 8
DOCUMENT_POSITION_CONTAINED_BY : 16
DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC : 32
appendChild : function appendChild() { [native code] }
cloneNode : function cloneNode() { [native code] }
compareDocumentPosition : function compareDocumentPosition() { [native code] }
contains : function contains() { [native code] }
getRootNode : function getRootNode() { [native code] }
hasChildNodes : function hasChildNodes() { [native code] }
insertBefore : function insertBefore() { [native code] }
isDefaultNamespace : function isDefaultNamespace() { [native code] }
isEqualNode : function isEqualNode() { [native code] }
isSameNode : function isSameNode() { [native code] }
lookupNamespaceURI : function lookupNamespaceURI() { [native code] }
lookupPrefix : function lookupPrefix() { [native code] }
normalize : function normalize() { [native code] }
removeChild : function removeChild() { [native code] }
replaceChild : function replaceChild() { [native code] }
addEventListener : function addEventListener() { [native code] }
dispatchEvent : function dispatchEvent() { [native code] }
removeEventListener : function removeEventListener() { [native code] }

createTextNode を実行することにより、HTMLドキュメント内に新規のテキストノードを一つ作成されたことがわかります。

この他、ノードにはcloneNode()メソッドがあり、引数のノードをコピーする機能があります。

書き方は以下のように書きます。

cloneNode(元ノード, true)

第2引数でtrueを指定すると、再帰的に元ノードの全てのノードがコピー
されます。
falseを指定すると、元ノードのみがコピーされます。

Textノードについて

Posted コメントするカテゴリー: javascript

Textノードについて

Textノードについて、調べてみます。

あるHTMLドキュメントに対して、要素のコンテンツを取得する際、Textノードとして取得し、処理する場面があります。
また、Textノード自身がさらにTextノード(子要素)を持ち、その子要素のTextノードがさらにTextノード(孫要素)をもつ場合があります。

ここでは要素のコンテンツを取得する方法としてtextContent()の動きを確かめてみます。

以下のサンプルを用意して、試してみました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">p1text</div>
</div>

<script type="text/javascript">
// ID「p1」のオブジェクトを取得
let p1 = document.getElementById("p1");

// 取得したオブジェクトのattributesを確認
console.log("p1 -> " + p1.textContent);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

出力結果は次のようになります。

p1 -> p1text

もう少し詳しく試してみると、
textContent()関数は、divタグ内のHTML要素を除く内容を取得します。

例えば、下記のように「p1text」に対してHTMLタグがあった場合でも、

<div id="test1">
	<div id="p1" name="p1name"><b>p1text</b></div>
</div>

出力結果は

p1 -> p1text

という形で出力されます。

では次に、元のHTMLファイルを以下のように変更します。
「p1text」の後にさらにdivタグを入れ子にして要素を作ります。
そして先ほどの例のようにtextContent関数を試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">p1text<div id="p2" name="p2name">p2text</div></div>
</div>

<script type="text/javascript">
// ID「p1」のオブジェクトを取得
let p1 = document.getElementById("p1");

// 取得したオブジェクトのattributesを確認
console.log("p1 -> " + p1.textContent);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

出力結果は

p1 -> p1textp2text

という形になります。
サンプルの文字列の例がよくないですが、p1タグの内容を取得すると、その入れ子にした中のp2タグの内容も取得していることがわかります。

innerHTMLプロパティについて

Posted コメントするカテゴリー: javascript

innerHTMLプロパティについて

innerHTMLプロパティについて調べてみます。

innerHTMLプロパティはHTML5から標準化されたプロパティで、Elementノード、Documentノードで定義されています。
innerHTMLプロパティを使うと、マークアップを含んだ文字列形式で要素のコンテンツが返ります。
また、outerHTMLプロパティも同様に標準化されています。
こちらはHTMLドキュメント内のタグ情報(コンテンツの開始タグ、終了タグ)も含んだ形でマークアップ文字列が返ります。
outerHTMLはElementノードのみ定義されています。

また、HTML5で標準化されたメソッドとしてinsertAdjacentHTML()があります。
こちらはinnerHTMLプロパティと使用する場面で切り分けて使用します。

先ほどinnerHMTLプロパティは値を取得(マークアップを含んだ文字列形式で要素のコンテンツが返る)と書きましたが
innerHTMLプロパティは値を代入することもできます。

ただ、innerHTMLプロパティで値を代入すると、HTMLドキュメントのイベントが破壊されます。
javascriptのイベントが破壊されては困る場面でinsertAdjacentHTML()メソッドを使って、
HTMLドキュメント内の要素にDOMを追加することが可能になります。

以下、実際にHTMLドキュメントを使って試してみます。

HTMLドキュメントの要素を、innerHTMLを使ってプレーンテキストで取得する

HTMLドキュメントの要素を、プレーンテキストで取得する方法を、
実際に試してみました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">p1text</div>
	<input class="form1" id="fp1" type="text" name="test1">
	<div id="p2">p2text
		<div id="c1">c1text</div>
		<div id="c2">c2text</div>
		<div id="c3">c3text</div>
	</div>
	<div id="p3" name=3>p3text
		<div id="d1">d1text</div>
		<div id="d2">d2text</div>
		<div id="d3">d3text</div>
		<div id="d4">d4text</div>
	</div>
</div>

<script type="text/javascript">
// ID「p1」のオブジェクトを取得
let p1 = document.getElementById("p1");

// 取得したオブジェクトのattributesを確認
console.log("p1 -> " + p1.textContent);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

出力結果は次のようになります。

p1 -> p1text

HTMLドキュメントの要素を、innerHTMLを使って内容を書き変える

では次に、innerHTMLを使って、

<div id="p1" name="p1name">p1text</div>

の内容を更新してみます。

先ほどのHTMLを以下のように変更しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">p1text</div>
	<input class="form1" id="fp1" type="text" name="test1">
	<div id="p2">p2text
		<div id="c1">c1text</div>
		<div id="c2">c2text</div>
		<div id="c3">c3text</div>
	</div>
	<div id="p3" name=3>p3text
		<div id="d1">d1text</div>
		<div id="d2">d2text</div>
		<div id="d3">d3text</div>
		<div id="d4">d4text</div>
	</div>
</div>

<script type="text/javascript">
// ID「p1」のオブジェクトを取得
let p1 = document.getElementById("p1");

// innerHTMLを使って内容を書き換える
p1.innerHTML = 'p1 text update !';

// 取得したオブジェクトのattributesを確認
console.log("p1 -> " + p1.textContent);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

出力結果は次のようになります。

p1 -> p1 text update !

HTMLドキュメントの内容が書き変わったので(厳密に言うと、代入したタイミングでイベントも破棄されています)、
ログ出力には最初の例(test1.html)とは異なった値が出力されました。

HTMLドキュメントの要素を、textContentを使って内容を書き変える

では、textContentを使って、HTMLドキュメントの内容を変更してみます。

先ほどのHTMLを以下のようにさらに変更しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">p1text</div>
	<input class="form1" id="fp1" type="text" name="test1">
	<div id="p2">p2text
		<div id="c1">c1text</div>
		<div id="c2">c2text</div>
		<div id="c3">c3text</div>
	</div>
	<div id="p3" name=3>p3text
		<div id="d1">d1text</div>
		<div id="d2">d2text</div>
		<div id="d3">d3text</div>
		<div id="d4">d4text</div>
	</div>
</div>

<script type="text/javascript">
// ID「p1」のオブジェクトを取得
let p1 = document.getElementById("p1");

// textContentを使って内容を書き換える
p1.textContent = 'p1 text update (textContent) !';

// 取得したオブジェクトのattributesを確認
console.log("p1 -> " + p1.textContent);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

出力結果は次のようになります。

p1 -> p1 text update (textContent) !

今回確かめたように、innerHTMLとtextContentを使って、HTMLドキュメントを書き変えましたが、
どちらもコンテンツの更新はできることがわかりました。

textContentとinnerHTMLの違いについて

上記のサンプルから、textContentとinnerHTMLはどちらもHTMLドキュメントを書き変える(代入する)ことができます。
動きだけを見ると両社には違いはありませんが、
textContentプロパティは指定した要素のTextノードを全て連携した値です。
innerTextプロパティはscript要素のコンテンツを返さず、空白は取り除かれ、table (テーブル要素)は読み出し専用プロパティになる。という点が異なります。

ElementオブジェクトのAttrオブジェクトについて

Posted コメントするカテゴリー: javascript

ElementオブジェクトのAttrオブジェクトについて

ElementオブジェクトのAttrについて調べてみます。

ElementオブジェクトはHTML/XMLを内包したオブジェクトです。
Elementオブジェクトは親のインターフェイスの「Node型」、その親インターフェイスである「EventTarget型」のプロパティを継承しています。

そのNode型にはattributesプロパティが定義されています。
このattributesプロパティは要素の属性を表すリスト(配列のようなもの)「NamedNodeMap」を参照します。
このattributesプロパティのNamedNodeMapに対しては数値(インデックス)を使ってアクセス可能です。
また、数値(インデックス)ではなく属性名でもアクセス可能です。

実際にどんな挙動になるのか、試してみました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<input class="form1" id="p1" type="text" name="test1">
</div>

<script type="text/javascript">
// ID「p1」のオブジェクトを取得
let test1 = document.getElementById("p1");

// 取得したオブジェクトのattributesを確認
console.log("test1 -> " + test1.attributes);

// 取得したオブジェクトのattributesの最初の要素を確認
console.log("test2 -> " + test1.attributes[0]);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

出力結果は次のようになります。

test1 -> [object NamedNodeMap]
test2 -> [object Attr]

取得したオブジェクトに対してattributesプロパティを調べると、
「NamedNodeMapオブジェクト」を参照していることがわかります。

また、attributesオブジェクトにインデックスを指定すると「Attrオブジェクト」を参照しています。

AttrオブジェクトはNode型の派生です。
Attrオブジェクトはnameプロパティとvalueプロパティがあり、属性の名前と値が格納されています。
試しに、先ほどのHTMLドキュメントに追記して、次のようなHTMLを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<input class="form1" id="p1" type="text" name="test1">
</div>

<script type="text/javascript">
// ID「p1」のオブジェクトを取得
let test1 = document.getElementById("p1");

// 取得したオブジェクトのattributesを確認
console.log("test1 -> " + test1.attributes);

// 取得したオブジェクトのattributesの最初の要素を確認
console.log("test2 -> " + test1.attributes[0]);

// 取得オブジェクトの最初の要素のname属性とvalue属性を出力
console.log("test3_name -> " + test1.attributes[0].name);
console.log("test3_value -> " + test1.attributes[0].value);

// 取得オブジェクトの2番目の要素のname属性とvalue属性を出力
console.log("test4_name -> " + test1.attributes[1].name);
console.log("test4_value -> " + test1.attributes[1].value);

// 取得オブジェクトの3番目の要素のname属性とvalue属性を出力
console.log("test5_name -> " + test1.attributes[2].name);
console.log("test5_value -> " + test1.attributes[2].value);

// 取得オブジェクトの4番目の要素のname属性とvalue属性を出力
console.log("test6_name -> " + test1.attributes[3].name);
console.log("test6_value -> " + test1.attributes[3].value);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

このHTMLドキュメントにアクセスすると、ログには次のように出力されます。

test1 -> [object NamedNodeMap]
test2 -> [object Attr]
test3_name -> class
test3_value -> form1
test4_name -> id
test4_value -> p1
test5_name -> type
test5_value -> text
test6_name -> name
test6_value -> test1

test3以降、ドキュメント内の

<input class="form1" id="p1" type="text" name="test1">

に対しての、
属性の名前と値を順に取得できていることがわかります。

では、さらにHTMLに追記して、下記のような記述を試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<input class="form1" id="p1" type="text" name="test1">
</div>

<script type="text/javascript">
// ID「p1」のオブジェクトを取得
let test1 = document.getElementById("p1");

// 取得したオブジェクトのattributesを確認
console.log("test1 -> " + test1.attributes);

// 取得したオブジェクトのattributesの最初の要素を確認
console.log("test2 -> " + test1.attributes[0]);

// 取得オブジェクトの最初の要素のname属性とvalue属性を出力
console.log("test3_name -> " + test1.attributes[0].name);
console.log("test3_value -> " + test1.attributes[0].value);

// 取得オブジェクトの2番目の要素のname属性とvalue属性を出力
console.log("test4_name -> " + test1.attributes[1].name);
console.log("test4_value -> " + test1.attributes[1].value);

// 取得オブジェクトの3番目の要素のname属性とvalue属性を出力
console.log("test5_name -> " + test1.attributes[2].name);
console.log("test5_value -> " + test1.attributes[2].value);

// 取得オブジェクトの4番目の要素のname属性とvalue属性を出力
console.log("test6_name -> " + test1.attributes[3].name);
console.log("test6_value -> " + test1.attributes[3].value);

// 取得オブジェクトの5番目の要素のname属性とvalue属性を出力
console.log("test7_name -> " + test1.attributes[4].name);
console.log("test7_value -> " + test1.attributes[4].value);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test3.html)

存在しないはずの5番目の要素にアクセスする書き方をしてみました。

出力結果は、次のようになります。

test1 -> [object NamedNodeMap]
test2 -> [object Attr]
test3_name -> class
test3_value -> form1
test4_name -> id
test4_value -> p1
test5_name -> type
test5_value -> text
test6_name -> name
test6_value -> test1
Uncaught TypeError: Cannot read properties of undefined (reading 'name')
    at test1.html:43

もともと、ドキュメント内の

<input class="form1" id="p1" type="text" name="test1">

には、
要素が4つしかない為、5つめの要素にアクセスした為に

Cannot read properties of undefined

というエラーになりました。
余談ですが、存在しない要素にアクセスするとどうなるかを試しました。
実際のプログラムでは起こりえない(実装しない)ことですが、内部構造を理解する意味でも色々と試してみるとよいかもしれません。

データセット属性について

Posted コメントするカテゴリー: javascript

データセット属性について

データセット属性について試してみます。

データセット属性はHTMLドキュメントに対して、標準外の属性を定義し、その定義した属性を
javascript側で取得し使用することができます。

実際にどんな挙動になるのか、以下かんたんなHTMLドキュメントのサンプルを書いて試してみました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name" data-testdata1="aaa" data-testdata2="bbb" data-testdata3="ccc">
		p1text
	</div>
</div>

<script type="text/javascript">
// ID「p1」の要素を取得
let test1 = document.getElementById("p1");

// 取得した内容を確認
console.log("test1 -> " + test1);

// 取得したデータセット属性の値を確認
console.log("testdata1 -> " + test1.dataset.testdata1);
console.log("testdata2 -> " + test1.dataset.testdata2);
console.log("testdata3 -> " + test1.dataset.testdata3);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

実行した結果は

test1 -> [object HTMLDivElement]
testdata1 -> aaa
testdata2 -> bbb
testdata3 -> ccc

という結果になります。

HTMLドキュメントとして定義した

<div id="p1" name="p1name" data-testdata1="aaa" data-testdata2="bbb" data-testdata3="ccc">
	p1text
</div>

という箇所の「data-testdata1=”aaa”」の部分がデータセット属性になります。

データセット属性を取得するときは

test1.dataset.testdata1

というように、「(取得した)DOMオブジェクト.dataset.定義名」という、データセットプロパティを使用し、値を取得します。

データセット属性に値をセットする

上記のサンプルではデータセット属性の値を取得しましたが、その反対に値をデータセット属性に対してセットすることもできます。
以下のサンプルで試してみます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name" data-testdata1="aaa" data-testdata2="bbb" data-testdata3="ccc">
		p1text
	</div>
</div>

<script type="text/javascript">
let test1 = document.getElementById("p1");
test1.dataset.testdata1 = "ddd";
</script>

</body>
</html>

サーバ上のHTMLはこちら(test2.html)

上記の画面にアクセスすると、データセット属性に値を代入された後のHTMLドキュメントになっていることがわかります。

具体的には、以下のHTMLソースになっていることを確認しました。(ブラウザの開発者ツールで確認)

<html lang="ja"><head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name" data-testdata1="ddd" data-testdata2="bbb" data-testdata3="ccc">
		p1text
	</div>
</div>

<script type="text/javascript">
// ID「p1」の要素を取得
let test1 = document.getElementById("p1");

// データセット属性のtestdata1に対して値を代入
test1.dataset.testdata1 = "ddd";
</script>

</body>
</html>

javascript実行前は「data-testdata1=”aaa”」でしたが、実行後に「data-testdata1=”ddd”」という値に書き変わっています。

データセット属性の注意点

データセット属性についての注意点をまとめます。

・属性名は小文字で記述する必要あり
・HTMLのDOM構造として使用し、見た目には影響を及ぼさない
・属性名にハイフンが含まれる場合は、アクセス時(マップ時)にキャメルケースを使用する

hasAttribute()、removeAttribute()について

Posted コメントするカテゴリー: javascript

hasAttribute()、removeAttribute()について

前回の投稿で使用したHTMLドキュメントをもとに、hasAttributeとremoveAttributeのメソッドを調べてみます。

HTMLドキュメントは前回投稿した以下のものを用意しました。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>テストHTML</title>
</head>
<body>

<div id="test1">
	<div id="p1" name="p1name">p1text</div>
	<div id="p2">p2text
		<div id="c1">c1text</div>
		<div id="c2">c2text</div>
		<div id="c3">c3text</div>
	</div>
	<div id="p3" name=3>p3text
		<div id="d1">d1text</div>
		<div id="d2">d2text</div>
		<div id="d3">d3text</div>
		<div id="d4">d4text</div>
	</div>
</div>

<script type="text/javascript">
// hasAttributeで「name属性」の有無を取得
let test1 = document.getElementById("p1").hasAttribute("name");
console.log("test1 -> " + test1);

// hasAttributeで「names属性」の有無を取得
let test2 = document.getElementById("p1").hasAttribute("names");
console.log("test2 -> " + test2);

// 一旦「name属性」を削除する
let test3 = document.getElementById("p1").removeAttribute("name");
console.log("test3 -> " + test3);

// 改めて「name属性」の有無を取得する
let test4 = document.getElementById("p1").hasAttribute("name");
console.log("test4 -> " + test4);
</script>

</body>
</html>

サーバ上のHTMLはこちら(test1.html)

上記の出力結果は

test1 -> true
test2 -> false
test3 -> undefined
test4 -> false

となります。

test1では、

<div id="p1" name="p1name">p1text</div>

というHTMLドキュメント構造から、trueが返りました。
test2では、「names」属性は元々定義されていないのでfalseが返りました。
test3では、removeAttributeメソッドが実行され、その結果、返値の出力としてundefinedになりました。(これは言語仕様としてundefinedが返る為です)
test4では、一度test3のremoveAttributeメソッドが実行された為、改めてname属性を取得しても、存在しない為、falseが返りました。

また、上記のjavascriptが実行された後、HTMLドキュメントからは該当のname属性のDOMが削除されています。

最初は

<div id="p1" name="p1name">p1text</div>

というDOM構造でしたが、実行後は

<div id="p1">p1text</div>

というDOM構造になっています。