HTML5 API

javascript

HTML5 API

HTML5のAPIについて、いくつか試してみます。
HTML5APIは非常に多く用意されているので、簡単にいくつかピックアップしてみます。

注意が必要な点としては、使用するAPIがブラウザの種類やブラウザのバージョンによって
対応/非対応があるという点です。
また、バージョンによっても対応状況が異なります。

APIの使用を検討する際に十分検討して実装する必要があります。

以下、いくつかのAPIで簡単なサンプルを試してみます。

Geolocation API

(許可済の場合)ユーザの現在の緯度経度を取得します。

以下は「navigator.geolocation.getCurrentPosition」の例です。

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

<div>lat:<span id='lat'></span></div>
<div>lon:<span id='lon'></span></div>

<script>
navigator.geolocation.getCurrentPosition(
	(position) => {
		// 現在地が取得できた場合の処理
		let lat = document.querySelector("#lat");
		let lon = document.querySelector("#lon");
		
		lat.innerHTML = position.coords.latitude;
		lon.innerHTML = position.coords.longitude;
	},
	(error) => {
		// エラーが発生した場合の処理(オプション)
		console.error(error.message);
	}
);
</script>
</body>
</html>

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

クロスドメインメッセージ

異なるドメインで開かれているウィンドウ(ブラウザ)に対し、メッセージを送るAPIです。

送信元では、postMessage()メソッドを使います。
postMessage()メソッドは、1つめの引数にメッセージ、2つめの引数に送信先ウィンドウの出身を表す文字列を指定し、

送信先(受信側)では、windowオブジェクトでmessageイベントが発生します。
messageイベントでは、以下のプロパティを持ちます。

data
送信元から送られたメッセージのコピー

source
送信元のwindowオブジェクト

origin
送信元の出身(URL形式)を表した文字列

送信先(受信側)の処理では、処理の先頭でoriginプロパティを確認すると安全です。
これは異なる出身からのメッセージは処理せず、送信元の出身が明確な場合に処理をするという考えです。

以下、簡単なサンプルを用意しました。

これはサンプルでは親ウィンドウ、子ウィンドウで書いていますが、これは異なるドメインのiframeを表示する場合でも同じです。

クロスドメインメッセージ (親ウィンドウから子ウィンドウへメッセージを渡す例)

送信側(親ウィンドウ)

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

<div id="bottom">
    <input type="button" id="openwindow"  value="ウィンドウオープン">
    <input type="button" id="postmessage" value="子ウィンドウへメッセージの送信">
</div>

<script>
// ウィンドウの定義
let popup_window;

// ウィンドウオープンボタン
let openwindow_dom = document.querySelector("#openwindow");
openwindow_dom.addEventListener('click', (e) => {
    // 新規ウィンドウを開く
    popup_window = window.open(
        "https://propanlab.net/demo/blogsample/js/195/test2_1.html",
        "test",
        "width=600, height=500"
    );
}, false);

// 子ウィンドウへのメッセージの送信
let postmessage_dom = document.querySelector("#postmessage");
postmessage_dom.addEventListener('click', (e) => {
    // 開いたウィンドウに対し、postMessage()メソッドを使い、メッセージを送る
    popup_window.postMessage(
        "サンプル文字列です。postMessageのテスト",
        "https://propanlab.net/"
    );
}, false);

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

受信側(子ウィンドウ)

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

メッセージ受信側画面

<script>
addEventListener("message", (event) => {
    // 送信元のorigin情報をチェックし、想定と異なる場合は以下の処理を止める
    if (event.origin != "https://propansystem.net") {
        console.log("origin err !");
        return;
    }
    
    // 以下、なんらかの処理

    // 例:受信したevent.dataをポップアップ表示する
    alert("受信したデータ:" + event.data);

}, false);
</script>
</body>
</html>

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

画面にアクセスし、一旦「ウィンドウオープン」ボタンを押下して子ウィンドウを開き、
その後に「子ウィンドウへメッセージの送信」ボタンを押下すると、
子ウィンドウに対してpostMessage()メソッドを使ってデータが送信されていることが確認できます。

クロスドメインメッセージ (子ウィンドウから親ウィンドウへメッセージを渡す例)

受信側(親ウィンドウ)
便宜上、一旦親ウィンドウから子ウィンドウを開き、その後に子ウィンドウから親ウィンドウへメッセージを送るサンプルになっています。

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

<div id="bottom">
    <input type="button" id="openwindow"  value="ウィンドウオープン">
</div>

<script>
// ウィンドウの定義
let popup_window;

// ウィンドウオープンボタン
let openwindow_dom = document.querySelector("#openwindow");
openwindow_dom.addEventListener('click', (e) => {

    // 新規ウィンドウを開く
    popup_window = window.open(
    	"https://propanlab.net/demo/blogsample/js/195/test3_1.html",
    	"test",
    	"width=600, height=500"
    );
}, false);

// (子ウィンドウからの)メッセージの受信
window.addEventListener("message", (event) => {

    // 送信元のorigin情報をチェックし、想定と異なる場合は以下の処理を止める
    if (event.origin != "https://propanlab.net") {
        console.log("origin err !");
        return;
    }
    
    // 以下、なんらかの処理

    // 例:受信したevent.dataをポップアップ表示する
    alert("受信したデータ:" + event.data);

}, false);

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

送信側(子ウィンドウ)

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

メッセージ受信側画面

<div id="bottom">
    <input type="button" id="postmessage" value="親ウィンドウへのメッセージの送信">
</div>

<script>
// 親ウィドウへのメッセージの送信
let postmessage_dom = document.querySelector("#postmessage");
postmessage_dom.addEventListener('click', (e) => {
    // 親ウィンドウに対し、postMessage()メソッドを使い、メッセージを送る
    window.opener.postMessage(
        "親ウィンドウへのテスト文字列送信",
        "https://propansystem.net/"
    );
}, false);
</script>
</body>
</html>

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

画面にアクセスし、子ウィンドウを開きます。
その後、子ウィンドウにある「親ウィンドウへメッセージの送信」ボタンを押下すると、
親ウィンドウ側でメッセージを受信しポップアップ表示がされます。

上記の2つの例で、別ドメインにまたがるポップアップ(またはiframe)ウィンドウに対し、データの送受信ができることがわかります。

Web Worker(ウェブワーカー)

メイン処理とは別にバックグラウンドで別な処理を実行するAPIです。
重い計算処理や非同期タスクをメインスレッドとは別に実行する際に使います。

Web Workerは処理を別スレッドにする際、メイン処理とサブ処理で
それぞれjavascriptのファイルを分けて書くと処理の見通しが良くなります。
(便宜上Worker側で処理することをサブ処理と同じ意味で書いています)

またその他の留意点として以下があります。
・javascriptで長時間の処理が発生する場合、メインの処理が止まるリスクが無くなる
・(サブ処理から)windowオブジェクトやDocumentオブジェクトにアクセスすることはできない
・postMessage()メソッドを使いサブ処理へ値を渡すことができる

Web Worker(ウェブワーカー) 具体例

ここからはWeb Workerの具体例をあげます。

サブ処理側で処理を開始したい場合、Workerオブジェクトをnewしてインスタンスを生成して使用します。

// Web Workerを作成
const worker = new Worker('外部javascriptファイル');

Workerを作成したら、Wokerに対してデータを送信できます。

worker.postMessage('なんらかのデータ');

Worker側で重い処理(時間のかかる処理)を開始し、処理が完了したタイミングでmessageを受け取ります。

// Web Workerからのメッセージを受け取る
worker.onmessage = function (event) {
    
    // 重い処理が終わった後のなんらかの処理
    
};

メイン処理

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

メイン処理

<script>

alert("メイン処理:開始");

// Web Workerを作成
const worker = new Worker('test4_sub.js');

// Web Workerからのメッセージを受け取る
worker.onmessage = function (event) {
	// Workerから受け取った結果を表示
    alert("Web Worker側のサブ処理終了 : 計算結果:" + event.data);
};

// Web Workerにデータを送信
// 1から10000000000までの全合計値を計算
worker.postMessage(10000000000);

alert("メイン処理:終了 (サブ処理はバックグラウンドで実行中)");

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

サブ処理

self.onmessage = function (event) {

    console.log("sub start");

    const num = event.data; // メインスレッドから受け取ったデータ
    let sum = 0;

    // 重い処理(1~numまでの合計を計算)
    for (let i = 1; i <= num; i++) {
        sum += i;
    }

    console.log("sub end");

    // 計算結果をメインスレッドに送信
    self.postMessage(sum);
};

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

画面にアクセスすると、まず「メイン処理開始」のダイアログが表示されます。
画面にしたがってOKボタンを押下すると、サブ処理がWorkerで実行されます。
その後、サブ処理は時間がかかる処理を進めていますが、メイン処理側ではサブ処理の状態とは無関係に処理が完了します。
サブ処理が(長時間経過したあと)完了するとメイン処理側にメッセージを渡し、メイン処理側でダイアログを表示して終了します。

Blob

BlobはBinary Large Objectの略で、バイナリーデータを扱う際のAPIです。
以下の特徴があります。
・Blobはバイナリーデータを扱います
・小きいサイズのデータでも扱えます
・Blobに対しては、バイト単位で大きさを判定、MIMEタイプを調べる、小さいBlobに分割すること、が可能です
・APIは非同期で処理されます
・webブラウザに保存できます(メモリ、ディスク使用)
・ローカルファイルシステム、ローカルデータベース、他のウィンドウ、ほかのWokerからBlobを読み書き可能です。

・structored cloneアルゴリズムのサポート
・メッセージイベントを通じて取得
・BlobBuiderオブジェクトで作成可能

Blob取得後の処理例は
・postMessage()メソッドで他のウィンドウやWokerに送信可能
・BlobをAJAXを用いてサーバにアップロード可能
・createObjectURL()メソッドで特種なblobを取得可能

以下、簡単なサンプルをあげます。

Blob – Blobを生成するサンプル

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

<script>
let blob = new Blob(["サンプルテキスト"], { type: "text/plain" });

// 開発者ツールで確認
console.log("blob -> " + blob);

for (let key in blob) {
    console.log(key + " : " + blob[key]);
}
</script>
</body>
</html>

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

画面にアクセスし、開発者ツールでconsole.logの値を確認すると

blob -> [object Blob]

という出力がされます。

Blobというオブジェクトが生成されていることが確認できます。

また、そのオブジェクトの内容を以下の方法で調べてみると、

for (let key in blob) {
    console.log(key + " : " + blob[key]);
}

次のような出力結果になります。

size : 24
type : text/plain
arrayBuffer : function arrayBuffer() { [native code] }
slice : function slice() { [native code] }
stream : function stream() { [native code] }
text : function text() { [native code] }

Blob – URLを生成するサンプル

次にURLを生成するサンプルです。

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

<script>

let blob = new Blob(["サンプルテキスト"], { type: "text/plain" });

let blobUrl = URL.createObjectURL(blob);

// 開発者ツールで確認
console.log("blobUrl -> " + blobUrl);

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

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

画面にアクセスし、開発者ツールのconsole.loを確認すると以下の出力が確認できます。

blobUrl -> blob:https://propansystem.net/2a498274-bd25-47ef-b798-bdc6aa0c4d0d

URLに「blob:」を含む点が注意です。

Blob – 生成したURLをもとに、blogをダウンロードするサンプル

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

<script>

let blob = new Blob(["サンプルテキスト"], { type: "text/plain" });

let blobUrl = URL.createObjectURL(blob);

let bloblink = document.createElement("a");

bloblink.href = blobUrl;

bloblink.download = "sample.txt";

bloblink.click();

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

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

画面にアクセスすると、sample.txtだダウンロードされます。
txtファイルをダウンロードし、内容を確認すると「サンプルテキスト」の文字列が出力されていることが確認できます。

FilesystemAPI

FilesystemAPIはブラウザからローカルのファイルにアクセスすることができるAPIです。
ただし、全てのブラウザ、全てバージョンで動作するわけではないので注意が必要です。
以下のサンプルはchromeブラウザで動作するものです。

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

<div>
	<button id="openFile">ファイルを開く</button>
	<button id="saveFile">ファイルを保存</button>
</div>

<textarea id="textArea" cols="30" rows="10" placeholder="ファイル内容"></textarea>

<script>

const openFileButton = document.getElementById("openFile");
const saveFileButton = document.getElementById("saveFile");
const textArea = document.getElementById("textArea");

let fileHandle;

// ファイルを開く
openFileButton.addEventListener("click", async () => {
    try {
        // ユーザーにファイル選択ダイアログを表示
        [fileHandle] = await window.showOpenFilePicker({
            types: [
                {
                    description: "テキストファイル",
                    accept: { "text/plain": [".txt"] },
                },
            ],
        });

        // ファイルを読み取る
        const file = await fileHandle.getFile();
        const contents = await file.text();
        textArea.value = contents;
    } catch (error) {
        console.error("ファイルを開けませんでした:", error);
    }
});

// ファイルを保存する
saveFileButton.addEventListener("click", async () => {
    try {
        if (!fileHandle) {
            // 新しいファイルの保存
            fileHandle = await window.showSaveFilePicker({
                suggestedName: "newFile.txt",
                types: [
                    {
                        description: "テキストファイル",
                        accept: { "text/plain": [".txt"] },
                    },
                ],
            });
        }

        // ファイルに書き込む
        const writable = await fileHandle.createWritable();
        await writable.write(textArea.value);
        await writable.close();
        alert("ファイルを保存しました!");
    } catch (error) {
        console.error("ファイルを保存できませんでした:", error);
    }
});

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

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

画面にアクセスし、「ファイル内容」に欄に任意の文字列を入力後、「ファイルを保存」ボタンを押下すると
ローカル環境への保存確認ダイアログが表示されます。
保存時に任意のファイル名として保存すると、ローカル環境に任意のファイル名として保存されます。
また、ファイルの内容はブラウザで記載した内容になっていることが確認できます。

「ファイルを開く」ボタンを押下すると、ローカル環境のファイルを開く操作をし、
ファイル内容を下のテキストエリアに展開します。
さらに展開したテキストエリアに対して内容を編集して「ファイルを保存」ボタンを押下して変更した内容を保存することができます。

Indexed Database API

Indexed Database APIはブラウザからデータベースを使用するAPIです。
以下の特徴があります。

ブラウザ内データベース:
クライアント側で構造化データを保存できるAPIです。

NoSQL型:
キーと値のペアでデータを保存し、テーブルではなくオブジェクトストアを使用します。

非同期処理:
操作は非同期的に行われ、PromiseやEventで結果を管理します。

キーの柔軟性:
主キーやインデックスを設定し、効率的なデータ検索が可能です。

永続性:
ブラウザが閉じられてもデータが保持されます。
容量が大きい: localStorage(約5MB)よりも多くのデータを保存できます(数百MB~GB)。

また、メリットは以下になります。

大容量データの保存:
ローカルストレージよりもはるかに多くのデータを保存できます。

高速なデータアクセス:
主キーやインデックスを活用することで効率的にデータを検索できます。

構造化データのサポート:
JSONのような複雑なデータ構造をそのまま保存可能です。

非同期処理対応:
メインスレッドをブロックせず、アプリの応答性を維持できます。
オフラインサポート: サーバーに依存せずローカルにデータを保存できるため、
オフラインアプリの実装に適しています。

デメリットは以下になります。

APIの複雑さ:
他のストレージAPI(例: localStorage)と比べて、初期設定や操作が複雑です。

ブラウザ依存性:
一部の古いブラウザや軽量ブラウザでは対応していない場合があります。

容量制限:
ブラウザごとに保存可能なデータ容量が異なります。

デバッグの難しさ:
開発ツールでのデバッグがやや煩雑で、テストがしにくいことがあります。

非同期処理の学習コスト:
イベント駆動のモデルに慣れていないと、扱いが難しい場合があります。

以下、簡単なサンプルを用意しました。

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

<div>
    <button id="addData"   >データを追加</button>
    <button id="readData"  >データを取得</button>
    <button id="deleteData">データを削除</button>
</div>
<div id="output"></div>

<script>
// IndexedDBの設定
let dbName    = "TestDB";
let storeName = "Users";
let db;

// データベースを開く
let openRequest = indexedDB.open(dbName, 1);

// データベース初期化
openRequest.onupgradeneeded = (event) => {
    db = event.target.result;

    // オブジェクトストアが存在しなければ作成
    if (!db.objectStoreNames.contains(storeName)) {
        db.createObjectStore(storeName, { keyPath: "id" });
    }
};

// データベース接続成功
openRequest.onsuccess = (event) => {
    db = event.target.result;
    console.log("データベース接続成功");
};

// エラー処理
openRequest.onerror = (event) => {
    console.error("データベース接続エラー:", event.target.error);
};

// データを追加
document.getElementById("addData").addEventListener("click", () => {
    let transaction = db.transaction(storeName, "readwrite");
    let store       = transaction.objectStore(storeName);
    let userData = { id: 1, name: "テスト名前001", age: 50 };
    let request = store.add(userData);

    request.onsuccess = () => {
        console.log("データ追加成功:", userData);
        document.getElementById("output").textContent = "データを追加しました。";
    };

    request.onerror = (event) => {
        console.error("データ追加エラー:", event.target.error);
    };
});

// データを取得
document.getElementById("readData").addEventListener("click", () => {
    let transaction = db.transaction(storeName, "readonly");
    let store       = transaction.objectStore(storeName);

    let request = store.get(1); // idが1のデータを取得

    request.onsuccess = () => {
        console.log("データ取得成功:", request.result);
        document.getElementById("output").textContent =
            "取得したデータ: " + JSON.stringify(request.result);
    };

    request.onerror = (event) => {
        console.error("データ取得エラー:", event.target.error);
    };
});

// データを削除
document.getElementById("deleteData").addEventListener("click", () => {
    let transaction = db.transaction(storeName, "readwrite");
    let store       = transaction.objectStore(storeName);

    let request = store.delete(1); // idが1のデータを削除

    request.onsuccess = () => {
        console.log("データ削除成功");
        document.getElementById("output").textContent = "データを削除しました。";
    };

    request.onerror = (event) => {
        console.error("データ削除エラー:", event.target.error);
    };
});

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

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

画面にアクセスし「データ追加」ボタンを押下すると、予め決めておいたテストデータがIndexed Databaseに登録されます。
また、同じボタンを押下しても、1レコードだけになります。(IDが同じ為)

次に「データを取得」ボタンを押下すると、DBに登録した値を取得し画面に出力します。
同様に「データを削除」ボタンを押下すると、登録した値が削除されます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です