1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クリックイベントをストリーム化して DOM アニメーションを非同期に制御する — readable-stream-with-safe-resolvers 活用例

Posted at

今回は、私がリリースした 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 安全性)

この例のメリット

  1. イベントを非同期ストリーム化

    • クリックが多くても順次処理され、DOM の操作が重ならない
  2. アニメーション完了を非同期で待機

    • Promise withResolvers を使うことで、イベントごとに独立した完了待ちが可能
  3. 安全性

    • ストリームを閉じたり、途中でキャンセルしてもエラーが出ない

まとめ

  • readable-stream-with-safe-resolvers外部から安全に ReadableStream を制御 できるユーティリティ
  • DOM イベントや WebSocket 等の 非同期イベント処理に最適
  • 今回の例では、クリックイベントをストリーム化して DOM アニメーションを安全に処理する方法を紹介
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?