Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

0.10.0: destroy() can leave an IntersectionObserver on a detached element (idle callback re-arms observePosition)

オープン 初心者向け
#246 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

@AndreasOhlsson がすでに取り組んでいます。

2026年10月8日 から。

  • #247 @AndreasOhlsson による — オープン

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
75/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
javascript, typescript
領域
frontend

調査の方向性

index.mjsを読み、特に102行目付近のobservePosition()と、issueで説明されているdestroy()およびupdatePos()の処理を確認してください。既存のテストでobserverのクリーンアップがどのようにカバーされているかを確認し、キューに入ったidle callbackのケースを再現して、破棄後に切り離された要素が再び監視対象にならないことを検証してください。

索引モデルが issue の本文から書いたものです。

説明

Summary

In 0.10.0, an element's subtree can stay alive after destroy(). This also happens when useAutoAnimate unmounts. A low-priority position update that was queued before destroy() runs after it, and observePosition() then creates a new IntersectionObserver on the now-detached element. Its root is document.documentElement, so the observer, and the detached subtree its callback closes over, live for the rest of the page session. Nothing disconnects it again.

This is related to #180 (timers in older versions), but it is a separate path that destroy() doesn't cover.

How it happens (0.10.0, index.mjs)
  1. poll(el) (around line 178) runs setInterval(() => lowPriority(updatePos.bind(null, el)), 2000). lowPriority() (around line 187) queues a requestIdleCallback.
  2. The component unmounts while the main thread is busy, e.g. a route change. destroy() (around line 776) clears intervals, debounces, intersections and coords, but an idle callback that is already queued still runs.
  3. That callback calls updatePos(el). After its debounce, updatePos calls observePosition(el) (around line 102), which disconnects the old observer (already gone) and creates a new one on the detached element.
Repro
  • Scenario: in Chromium, a parent with 10 animated children; unmount it during a ~600 ms busy main-thread window; force GC.
  • 0.10.0 as published: the detached subtree survived GC in 11 of 12 trials. All leaked observers were created 200–600 ms after an idle callback that was queued before destroy() and ran after it.
  • In a real app: we saw a steady leak of one timeline subtree every few navigations. 0.8.2 leaked on every navigation.
Suggested fix

In observePosition(), right after the old observer is disconnected:

if (!el.isConnected) {
  intersections.delete(el)
  return
}

Every path that re-creates an observer goes through this function (idle callbacks, the root resize update, observer and animation callbacks), so one guard covers all of them.

  • 0.10.0 with the guard: 0 of 12 trials leaked, even though idle callbacks still ran after destroy().
  • Over 60 navigations in our app: DOM nodes and event listeners stayed flat.

Observing a disconnected element measures nothing, so no animation behaviour is lost. Happy to open a PR if that's useful.

主要言語
TypeScript
スター
13.9k
フォーク
266
PR マージ指標
30日以内にマージされた PR はありません

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

formkit/auto-animate のほかの issue

formkit/auto-animate の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。