<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: AJK-Essential</title>
    <description>The latest articles on Forem by AJK-Essential (@ajk-essential).</description>
    <link>https://forem.com/ajk-essential</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2694810%2F0fadbab7-edbf-42b8-b77b-5ed5e5224c7c.png</url>
      <title>Forem: AJK-Essential</title>
      <link>https://forem.com/ajk-essential</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/ajk-essential"/>
    <language>en</language>
    <item>
      <title>Observing Position Changes of HTML Elements Using IntersectionObserver</title>
      <dc:creator>AJK-Essential</dc:creator>
      <pubDate>Sun, 12 Jan 2025 09:30:21 +0000</pubDate>
      <link>https://forem.com/ajk-essential/observing-position-change-of-html-elements-using-intersection-observer-12de</link>
      <guid>https://forem.com/ajk-essential/observing-position-change-of-html-elements-using-intersection-observer-12de</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;An experimental exploration of detecting element movement using &lt;code&gt;IntersectionObserver&lt;/code&gt;, its limitations, and practical trade-offs.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h5&gt;
  
  
  Credits:
&lt;/h5&gt;

&lt;blockquote&gt;
&lt;p&gt;I want to clarify that while the code in this post was developed entirely by me, the idea behind this implementation originated from this StackOverflow discussion:&lt;br&gt;&lt;br&gt;
&lt;em&gt;“How to observe DOM element position changes”&lt;/em&gt; — &lt;a href="https://stackoverflow.com/questions/59792071/how-to-observe-dom-element-position-changes" rel="noopener noreferrer"&gt;https://stackoverflow.com/questions/59792071/how-to-observe-dom-element-position-changes&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;This Stackoverflow discussion presented the concept, but not an implementation. I explored the idea, built the implementation myself, and wrote this article to explain how it works and how to use it. Many thanks to the original author of the StackOverflow post for the inspiration.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h5&gt;
  
  
  TL;DR:
&lt;/h5&gt;

&lt;blockquote&gt;
&lt;p&gt;An experiment to see if we can use an &lt;code&gt;IntersectionObserver&lt;/code&gt; to observe &lt;strong&gt;position changes&lt;/strong&gt; of elements.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;Quick Navigation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Approach&lt;/li&gt;
&lt;li&gt;Tracking Modes&lt;/li&gt;
&lt;li&gt;Approach 1&lt;/li&gt;
&lt;li&gt;Approach 2&lt;/li&gt;
&lt;li&gt;Approach 3&lt;/li&gt;
&lt;li&gt;Demos &amp;amp; Source Code&lt;/li&gt;
&lt;li&gt;Conclusion&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  Important Disclaimer
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;This approach is &lt;strong&gt;not a foolproof way to detect position changes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;IntersectionObserver&lt;/code&gt; was not designed to be a general-purpose position-change detector, and all approaches discussed in this article, work within its constraints.&lt;/p&gt;

&lt;p&gt;Known limitations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Element &lt;strong&gt;resizes&lt;/strong&gt; may not be detected reliably&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Viewport resizes&lt;/strong&gt; can invalidate internal assumptions&lt;/li&gt;
&lt;li&gt;Some layout or transform changes may not trigger observer callbacks&lt;/li&gt;
&lt;li&gt;The browser may &lt;strong&gt;skip intersection updates&lt;/strong&gt; under heavy load&lt;/li&gt;
&lt;li&gt;Rapid observer teardown and reconfiguration can cause missed transitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These techniques should be viewed as &lt;strong&gt;best-effort motion detection strategies&lt;/strong&gt;, not as guaranteed replacements for explicit layout observation.&lt;/p&gt;

&lt;p&gt;The goal of this article is to explore &lt;em&gt;how far IntersectionObserver can be pushed&lt;/em&gt; — and where it breaks down.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Approach
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;IntersectionObserver&lt;/code&gt; is typically used to detect visibility changes, but it also&lt;br&gt;
reports &lt;strong&gt;partial intersections&lt;/strong&gt;, which makes it possible to infer &lt;strong&gt;relative motion&lt;/strong&gt;&lt;br&gt;
between a target and its observing root.&lt;/p&gt;

&lt;p&gt;Another important feature is the ability to dynamically resize the observer’s root&lt;br&gt;
using &lt;code&gt;rootMargin&lt;/code&gt;. This effectively lets us control the size of the “capturing window”&lt;br&gt;
used for observation.&lt;/p&gt;

&lt;p&gt;By combining high-resolution thresholds with dynamic root resizing, we can approximate&lt;br&gt;
fine-grained position change detection without continuously reading layout.&lt;/p&gt;




&lt;h5&gt;
  
  
  Terminology note:
&lt;/h5&gt;

&lt;blockquote&gt;
&lt;p&gt;Throughout this article, the terms &lt;strong&gt;viewport window&lt;/strong&gt; and &lt;strong&gt;wide window&lt;/strong&gt; are used interchangeably to refer to the viewport-sized &lt;code&gt;IntersectionObserver&lt;/code&gt; root.&lt;br&gt;&lt;br&gt;
The term &lt;strong&gt;fine window&lt;/strong&gt; refers to the dynamically resized observer root that tightly wraps the visible intersection rectangle of the target.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Tracking Modes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Wide Tracking (Visibility Detection)
&lt;/h3&gt;

&lt;p&gt;We use a wide capturing window that covers the entire visual area (the viewport).&lt;/p&gt;

&lt;p&gt;This mode is used to determine whether the target is inside or outside the visual area.&lt;/p&gt;

&lt;p&gt;Once the observer reports that the target intersects the viewport (even at its boundary), we know the target is visible.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Fine Tracking (Precise Movement)
&lt;/h3&gt;

&lt;p&gt;When the target is within the visual area, we switch to a small capturing window that tightly wraps around the target element.&lt;/p&gt;

&lt;p&gt;With the high-resolution threshold array, even very small movements of the target produce detectable changes in intersection ratio.&lt;/p&gt;

&lt;p&gt;This allows us to track motion with high precision.&lt;/p&gt;




&lt;h2&gt;
  
  
  Determining When the Target Has Moved
&lt;/h2&gt;

&lt;p&gt;Before diving into the detection logic, here are a few important observations (assuming the fine window is used only when the target is either at the boundary of the viewport or entirely within it):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Window Size&lt;/th&gt;
&lt;th&gt;Intersection Ratio&lt;/th&gt;
&lt;th&gt;Position Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;gt; 0 and &amp;lt; 1&lt;/td&gt;
&lt;td&gt;Touching the boundary of the viewport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;gt; 0 and &amp;lt; 1&lt;/td&gt;
&lt;td&gt;Touching the boundary of the fine window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Outside the fine window but inside the viewport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Outside the viewport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Completely inside the fine window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wide&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&amp;gt; 0 and &amp;lt; 1&lt;/td&gt;
&lt;td&gt;Touching the boundary of the viewport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wide&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Outside the viewport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Wide&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Completely inside the viewport&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Determining the Movement Strategy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Approach 1
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Movement Flow (Text-Only)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;START -&amp;gt; STATE: WIDE  (Visibility Detection)
  - Observe using a viewport-sized window
  - intersectionRatio &amp;gt; 0  -&amp;gt; switch to FINE
  - intersectionRatio == 0 -&amp;gt; remain in WIDE

STATE: FINE  (Precise Tracking)
  - Observe using a tightly wrapped window
  - intersectionRatio == 1        -&amp;gt; stationary
  - 0 &amp;lt; intersectionRatio &amp;lt; 1     -&amp;gt; moving
  - intersectionRatio == 0        -&amp;gt; switch to WIDE

Notes: If the `intersection-ratio` while in the fine
window state is less than 1, movement has started.

Particulary if the intersection-ratio is 0, we switch
back to wide window detection to start the process
again.

(We are relying on the large threshold of the fine
window for fine-grained movement detection (0&amp;lt;IR&amp;lt;1))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;This approach works well in most cases, but it exposes an important limitation.&lt;/p&gt;

&lt;p&gt;When we rely entirely on the fine window for all cases where 0 &amp;lt; intersectionRatio &amp;lt; 1, tracking can become unreliable as the intersection ratio approaches 0. This is especially noticeable when the target moves along the viewport boundary rather than directly out of it.&lt;/p&gt;

&lt;p&gt;In such cases, the observer may miss movement updates entirely, and once those updates are missed, the subsequent motion is no longer captured correctly. As a result, the tracking logic can fall out of sync with the actual position of the target.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;Approach 2 — Observer + RAF Hybrid Tracking
&lt;/h2&gt;

&lt;p&gt;Approach 2 builds on the limitations observed in Approach 1 by combining&lt;br&gt;
&lt;code&gt;IntersectionObserver&lt;/code&gt; with &lt;code&gt;requestAnimationFrame&lt;/code&gt; (RAF).&lt;/p&gt;

&lt;p&gt;Instead of relying entirely on &lt;code&gt;intersectionRatio&lt;/code&gt; to infer motion, this&lt;br&gt;
approach uses the observer mainly for &lt;strong&gt;visibility detection and state&lt;br&gt;
transitions&lt;/strong&gt;, while &lt;strong&gt;actual movement is detected by comparing bounding box&lt;br&gt;
changes over time&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This makes motion tracking reliable even when the target moves close to or&lt;br&gt;
along the viewport boundaries.&lt;/p&gt;




&lt;h3&gt;
  
  
  Key Idea
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;IntersectionObserver&lt;/code&gt; is used to:

&lt;ul&gt;
&lt;li&gt;Detect whether the target is inside or outside the viewport&lt;/li&gt;
&lt;li&gt;Switch between wide and fine capturing windows&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;requestAnimationFrame&lt;/code&gt; is used to:

&lt;ul&gt;
&lt;li&gt;Track real movement by comparing &lt;code&gt;getBoundingClientRect()&lt;/code&gt; values&lt;/li&gt;
&lt;li&gt;Confirm when movement has stopped&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This removes the dependency on unstable intersection ratios when they approach&lt;br&gt;
zero.&lt;/p&gt;




&lt;h3&gt;
  
  
  Movement Flow (Text-Only)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
START -&amp;gt; STATE: WIDE (Viewport Detection)

Observe using a viewport-sized window

intersectionRatio &amp;gt; 0 -&amp;gt; switch to FINE

intersectionRatio == 0 -&amp;gt; remain in WIDE

STATE: FINE (Boundary Awareness)

Observe using a tightly wrapped window

If bounding box changes:
- Report position
- Enter RAF-based tracking

If intersectionRatio == 0:
- Switch back to WIDE

STATE: RAF TRACKING (True Motion Detection)

Compare boundingClientRect on every frame

If position changes:
- Report movement
- Reset stop timer

If position remains stable for N frames:
- Consider motion stopped
- Switch back to WIDE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Why This Works Better Than Approach 1
&lt;/h3&gt;

&lt;p&gt;In Approach 1, tracking could fail when the &lt;code&gt;intersectionRatio&lt;/code&gt; became very small,&lt;br&gt;
especially when the target moved along the viewport boundary instead of directly&lt;br&gt;
out of it.&lt;/p&gt;

&lt;p&gt;In this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Once movement is detected, RAF takes over&lt;/li&gt;
&lt;li&gt;Motion is detected using actual geometry, not intersection ratios&lt;/li&gt;
&lt;li&gt;Boundary conditions no longer cause missed updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures continuous and reliable tracking even in edge cases.&lt;/p&gt;




&lt;h4&gt;
  
  
  Limitation
&lt;/h4&gt;

&lt;p&gt;This approach relies on calling &lt;code&gt;getBoundingClientRect()&lt;/code&gt; on every animation frame during RAF-based tracking.&lt;/p&gt;

&lt;p&gt;Because &lt;code&gt;getBoundingClientRect()&lt;/code&gt; is a layout-dependent API, frequent access can increase CPU usage and may trigger layout thrashing if the browser is forced to recalculate layout repeatedly.&lt;/p&gt;

&lt;p&gt;This overhead is especially noticeable when monitoring performance using Chrome DevTools (for example, via the Performance panel), where increased layout and scripting activity can be observed during continuous tracking.&lt;/p&gt;

&lt;p&gt;So far, this is the primary limitation identified with this approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;Approach 3 — Intersection-Ratio–Driven Motion Detection
&lt;/h2&gt;




&lt;h5&gt;
  
  
  TL;DR:
&lt;/h5&gt;

&lt;blockquote&gt;
&lt;p&gt;Movement is detected when the intersection ratio reported by the fine window differs from the ratio previously reported by the viewport window.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;Approach 3 takes a different direction compared to the previous approaches.&lt;/p&gt;

&lt;p&gt;Instead of using layout reads (&lt;code&gt;getBoundingClientRect()&lt;/code&gt;) or per-frame checks, this approach relies entirely on &lt;strong&gt;intersection ratio changes&lt;/strong&gt; to infer motion. &lt;/p&gt;

&lt;p&gt;The key idea is that motion is inferred &lt;strong&gt;by comparing the intersection ratio reported by the fine window with the intersection ratio previously reported by the viewport (wide) window&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;When the target first intersects the viewport, the intersection ratio reported by the viewport-sized observer is stored. After switching to the fine window, motion is assumed to have occurred &lt;strong&gt;only if the intersection ratio reported by the fine window differs from that previously recorded viewport intersection ratio&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Readers are encouraged to refer back to the &lt;em&gt;“Determining When the Target Has Moved”&lt;/em&gt; table above, which shows how intersection ratios map to boundary contact, full containment, and exit conditions for both wide and fine windows.&lt;/p&gt;

&lt;p&gt;The fine window tightly wraps the visible intersection rectangle of the target with a ~1px margin (implemented via &lt;code&gt;rootMargin&lt;/code&gt;). Because this window closely tracks the visible portion of the target, even lateral or boundary-parallel movement usually causes the fine-window intersection ratio to diverge from the viewport ratio, triggering motion detection.&lt;/p&gt;




&lt;h3&gt;
  
  
  Core Idea
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Start with a wide (viewport-sized) capturing window.&lt;/li&gt;
&lt;li&gt;When the target becomes visible, switch to a fine window around the visible portion of the target.&lt;/li&gt;
&lt;li&gt;Store the intersection ratio reported by the wide window.&lt;/li&gt;
&lt;li&gt;If the fine window later reports a &lt;strong&gt;different intersection ratio&lt;/strong&gt;, motion is assumed to have started.&lt;/li&gt;
&lt;li&gt;If the ratio remains the same, the element is assumed to be stationary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows motion detection without per-frame geometry checks.&lt;/p&gt;




&lt;h3&gt;
  
  
  Movement Flow (Text-Only)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;START -&amp;gt; STATE: WIDE

Observe with viewport-sized window

Store intersectionRatio as viewportIR

If intersectionRatio &amp;gt; 0 -&amp;gt; switch to FINE

STATE: FINE

Observe with tightly wrapped window

If intersectionRatio !== viewportIR:
-&amp;gt; motion detected
-&amp;gt; switch back to WIDE

Else:
-&amp;gt; assume stationary
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Variants
&lt;/h2&gt;

&lt;p&gt;Approach 3 can be implemented in two variants depending on the desired trade-off between &lt;strong&gt;smoothness&lt;/strong&gt; and &lt;strong&gt;robustness&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Variant A — Single Observer (Lightweight)
&lt;/h3&gt;

&lt;p&gt;This variant uses a single &lt;code&gt;IntersectionObserver&lt;/code&gt; and relies solely on fine-window intersection ratio deltas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Characteristics&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lowest runtime overhead&lt;/li&gt;
&lt;li&gt;Smoothest behavior during continuous motion&lt;/li&gt;
&lt;li&gt;No layout reads&lt;/li&gt;
&lt;li&gt;No secondary observers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Trade-off&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rare edge cases (such as missed callbacks during observer reconfiguration) may go undetected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recommended when&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Performance and smoothness are critical&lt;/li&gt;
&lt;li&gt;Best-effort motion detection is acceptable&lt;/li&gt;
&lt;li&gt;Occasional missed transitions are tolerable&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Variant B — Dual Observer (Robust)
&lt;/h3&gt;

&lt;p&gt;This variant introduces a &lt;strong&gt;backup viewport observer&lt;/strong&gt; that watches for intersection ratio changes at all times.&lt;/p&gt;

&lt;p&gt;If the primary observer misses a transition, the backup observer forces a reset back to the wide-window detection state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Characteristics&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More resilient to missed callbacks&lt;/li&gt;
&lt;li&gt;Better recovery during observer teardown and reconfiguration&lt;/li&gt;
&lt;li&gt;Slightly higher CPU and callback overhead&lt;/li&gt;
&lt;li&gt;Can feel less smooth under heavy performance monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Trade-off&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Additional observer increases runtime work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recommended when&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reliability is more important than minimal overhead&lt;/li&gt;
&lt;li&gt;Edge cases must be minimized&lt;/li&gt;
&lt;li&gt;Public demos or production scenarios require extra safety&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;Even with a tightly constructed fine window, this approach fundamentally relies on &lt;strong&gt;intersection area changes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Motion that preserves the intersection area exactly may not be detected. In practice, these cases are rare and typically insignificant for real-world UI interactions, making this trade-off acceptable for many use cases.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The demo and source code for Approach 3 use the dual-observer (robust) variant for improved reliability.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  &lt;a&gt;&lt;/a&gt;Demos &amp;amp; Source Code
&lt;/h2&gt;

&lt;p&gt;Each approach is fully implemented and demonstrated below.&lt;br&gt;&lt;br&gt;
The demos allow you to visually inspect behavior near viewport boundaries and during fine-grained motion.&lt;/p&gt;




&lt;h3&gt;
  
  
  Approach 1 — IntersectionObserver Only
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Demo&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://ajk-essential.github.io/Position-Observer/one-io-only/index.html" rel="noopener noreferrer"&gt;https://ajk-essential.github.io/Position-Observer/one-io-only/index.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Code&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/AJK-Essential/Position-Observer/blob/main/src/one-io-only/position-observer.ts" rel="noopener noreferrer"&gt;https://github.com/AJK-Essential/Position-Observer/blob/main/src/one-io-only/position-observer.ts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Pure &lt;code&gt;IntersectionObserver&lt;/code&gt;–based tracking using wide and fine windows.&lt;br&gt;&lt;br&gt;
Susceptible to missed updates when the intersection ratio approaches zero near boundaries.&lt;/p&gt;




&lt;h3&gt;
  
  
  Approach 2 — IntersectionObserver + RAF Hybrid
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Demo&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://ajk-essential.github.io/Position-Observer/one-io-with-continuous-poll-raf/index.html" rel="noopener noreferrer"&gt;https://ajk-essential.github.io/Position-Observer/one-io-with-continuous-poll-raf/index.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Code&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/AJK-Essential/Position-Observer/blob/main/src/one-io-with-continuous-poll-raf/position-observer.ts" rel="noopener noreferrer"&gt;https://github.com/AJK-Essential/Position-Observer/blob/main/src/one-io-with-continuous-poll-raf/position-observer.ts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Uses &lt;code&gt;IntersectionObserver&lt;/code&gt; for state transitions and &lt;code&gt;requestAnimationFrame&lt;/code&gt; with&lt;br&gt;
&lt;code&gt;getBoundingClientRect()&lt;/code&gt; for reliable motion detection.&lt;br&gt;&lt;br&gt;
Most robust approach, with higher CPU and layout cost during active tracking.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This implementation uses two requestAnimationFrame loops:&lt;br&gt;
One RAF continuously tracks movement while the element is in motion.&lt;br&gt;
A second RAF confirms that motion has fully stopped by waiting for a stable position over a short duration.&lt;br&gt;
These two loops can be merged into a single RAF in simpler implementations, at the cost of slightly weaker stop detection (for example, increased sensitivity to brief pauses or compositor jitter).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Approach 3 — Intersection-Ratio–Driven Detection
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Demo&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://ajk-essential.github.io/Position-Observer/two-io-differing-ir/index.html" rel="noopener noreferrer"&gt;https://ajk-essential.github.io/Position-Observer/two-io-differing-ir/index.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Source Code&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/AJK-Essential/Position-Observer/blob/main/src/two-io-differing-ir/position-observer.ts" rel="noopener noreferrer"&gt;https://github.com/AJK-Essential/Position-Observer/blob/main/src/two-io-differing-ir/position-observer.ts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Infers motion by comparing intersection ratios between wide and fine windows.&lt;br&gt;&lt;br&gt;
Avoids layout reads entirely and offers a balance between performance and accuracy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The demo and source code for Approach 3 use the &lt;strong&gt;dual-observer (robust) variant&lt;/strong&gt;&lt;br&gt;
to minimize missed transitions during observer reconfiguration.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Choosing the Right Approach
&lt;/h2&gt;

&lt;p&gt;Each approach explores a different trade-off space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach 1&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimal logic&lt;/li&gt;
&lt;li&gt;Observer-only&lt;/li&gt;
&lt;li&gt;Can miss updates near boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Approach 2&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most reliable&lt;/li&gt;
&lt;li&gt;Uses real geometry&lt;/li&gt;
&lt;li&gt;Higher CPU and layout cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Approach 3&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Observer-only&lt;/li&gt;
&lt;li&gt;No layout reads&lt;/li&gt;
&lt;li&gt;Relies on intersection-area changes&lt;/li&gt;
&lt;li&gt;Best balance for many real-world cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If correctness under all conditions is critical, polling layout remains the only&lt;br&gt;
guaranteed solution. If performance and simplicity matter more, observer-based&lt;br&gt;
approaches can be surprisingly effective.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;IntersectionObserver&lt;/code&gt; is not a position observer — but with careful window&lt;br&gt;
construction, threshold tuning, and state management, it can be used to &lt;strong&gt;approximate&lt;/strong&gt;&lt;br&gt;
position changes surprisingly well.&lt;/p&gt;

&lt;p&gt;Each approach in this article demonstrates a different way of working around the&lt;br&gt;
observer’s limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By refining intersection windows&lt;/li&gt;
&lt;li&gt;By detecting ratio deltas instead of absolute values&lt;/li&gt;
&lt;li&gt;By combining observers with animation-frame polling when necessary&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these techniques are perfect, and none should be treated as universally correct.&lt;br&gt;
However, they can be extremely useful in cases where &lt;strong&gt;continuous layout reads are too&lt;br&gt;
expensive&lt;/strong&gt; and &lt;strong&gt;best-effort motion detection is acceptable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;As with most performance-sensitive UI problems, the right solution depends on context.&lt;/p&gt;




&lt;p&gt;Feel free to explore, test, and modify the code in this GitHub repo:&lt;br&gt;&lt;br&gt;
🔗 &lt;a href="https://github.com/AJK-Essential/Position-Observer" rel="noopener noreferrer"&gt;https://github.com/AJK-Essential/Position-Observer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find improvements or bugs, I’d love to hear about them!&lt;/p&gt;

&lt;p&gt;Thanks for reading — hope this was helpful ✨&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>typescript</category>
      <category>intersectionobserver</category>
    </item>
  </channel>
</rss>
