今回は、私がリリースした readable-stream-with-safe-resolvers を使って、クリックイベントをストリーム化して DOM に描画・アニメーションを非同期で処理する例 を紹介します。
See the Pen readable-stream-with-safe-resolvers sample by juner clarinet (@juner) on CodePen.
概要
withSafeResolvers() は ReadableStream を外部から安全に操作するためのラッパーです。
特徴:
-
enqueue(chunk)で値をストリームに追加 -
close()でストリームを終了 -
error(reason)でストリームをエラー終了 - ストリームが finalized(閉じる・エラー・キャンセル)後はすべての操作が無視される
この特性を利用して、クリックイベントを 非同期ストリーム に変換し、順次 DOM に描画してアニメーションを管理します。
HTML & CSS
<body>
<template id="template"><div class="hi">hi</div></template>
</body>
<style>
body {
min-height: 100vh;
min-width: 100vw;
overflow: hidden;
}
.hi {
pointer-events: none;
position: absolute;
top: calc(var(--offset-y) * 1px);
left: calc(var(--offset-x) * 1px);
animation: fadeOut 2s;
animation-fill-mode: both;
}
@keyframes fadeOut {
0% { opacity: 1; }
50% { opacity: 1; }
100% { opacity: 0; }
}
</style>
-
<template>を使って、クリック毎に DOM 要素を生成 - CSS でフェードアウトアニメーションを付与
TypeScript / JavaScript
import { withSafeResolvers } from "readable-stream-with-safe-resolvers";
const { enqueue, close, stream } = withSafeResolvers<MouseEvent>();
const $body = document.body;
const template = document.getElementById("template")!;
// クリックイベントをストリームに追加
$body.addEventListener("click", enqueue);
// ストリームから逐次 DOM を描画
(async () => {
for await (const event of stream) {
const $template = template.content.cloneNode(true) as DocumentFragment;
const $hi = $template.querySelector(".hi")!;
$hi.style.setProperty("--offset-x", String(event.offsetX));
$hi.style.setProperty("--offset-y", String(event.offsetY));
$body.appendChild($template);
// アニメーション完了後に要素を削除(非同期)
(async () => {
await animationToEnd($hi);
$hi.remove();
})();
}
})();
async function animationToEnd($elm: HTMLElement) {
const { resolve, promise } = Promise.withResolvers<void>();
$elm.addEventListener("animationend", resolve, { once: true });
await promise;
}
ポイント:
-
withSafeResolvers()でイベントストリームを作成 -
for awaitでストリームを逐次処理 - DOM 要素生成とアニメーション完了待ちを非同期で並列処理
- クリック連打しても安全(
withSafeResolversの finalized 安全性)
この例のメリット
-
イベントを非同期ストリーム化
- クリックが多くても順次処理され、DOM の操作が重ならない
-
アニメーション完了を非同期で待機
- Promise withResolvers を使うことで、イベントごとに独立した完了待ちが可能
-
安全性
- ストリームを閉じたり、途中でキャンセルしてもエラーが出ない
まとめ
-
readable-stream-with-safe-resolversは 外部から安全に ReadableStream を制御 できるユーティリティ - DOM イベントや WebSocket 等の 非同期イベント処理に最適
- 今回の例では、クリックイベントをストリーム化して DOM アニメーションを安全に処理する方法を紹介