← Back to Module 09

Module 09: Extensions & Plugins Test

Instructions:
  1. This page loads collector-v8.js with both the click tracker and scroll depth extensions
  2. Click the buttons, links, cards, and list items below — watch the click log update
  3. Scroll down through the page — watch the scroll depth percentage update in real time
  4. Open DevTools Console to see all tracked events logged by the collector
  5. Depth markers at 25%, 50%, 75%, and 100% show where threshold events fire

Extension status: Click Tracker Scroll Tracker

Live Status

Scroll Depth

0%

Max Depth

0%

Click Count

0

Thresholds Hit

0 / 4

Click Log

No clicks recorded yet. Click any element below.

Buttons

Click these buttons to generate click tracking events with different classes and text content:

Links

Click these links (they do not navigate away from the page):

View Documentation | Download Report | Contact Support | Privacy Policy

Cards

Click these cards to see how the click tracker captures nested element details:

Analytics

Track user behavior and page performance metrics

Web Vitals

Monitor LCP, CLS, and INP core web vitals

Error Tracking

Capture JavaScript errors and unhandled rejections

Navigation List

Click list items to see selector paths for list structures:

25% Scroll Depth Threshold

Understanding Click Analytics

Click analytics reveal how users actually interact with your interface. While designers might intend for users to follow a specific flow, click data shows the reality. Users often click on elements that are not clickable, ignore prominent call-to-action buttons, and discover navigation patterns the design team never anticipated.

The CSS selector path captured by the click tracker is particularly valuable. It lets you aggregate clicks by element type and location. For example, you might discover that 40% of clicks on your product page go to the product image (which is not a link), suggesting users expect to zoom in or see a gallery view.

Coordinate data (clientX, clientY) enables heatmap visualization. By plotting click positions on a screenshot of the page, you can see clusters of activity and dead zones. This is more intuitive than reading tables of selector paths, making it easier to communicate findings to stakeholders who are not technical.

Debouncing is essential for accurate click tracking. Without it, a user's accidental double-click on a "Buy Now" button would be recorded as two clicks, inflating interaction counts and potentially misleading your analysis. The 300ms debounce window covers most accidental double-clicks while still capturing intentional rapid interactions on different elements.

The Value of Scroll Depth

Scroll depth is one of the most undervalued metrics in web analytics. Page views tell you someone arrived; scroll depth tells you whether they engaged with your content. A blog post with 10,000 views but only 5% of users scrolling past the first paragraph has a content problem, not a traffic problem.

The threshold-based reporting approach (25%, 50%, 75%, 100%) strikes a balance between data volume and insight quality. Reporting every pixel of scroll position would generate enormous amounts of data with minimal additional insight. The four thresholds give you a clear picture: how many users start reading (25%), reach the middle (50%), read most of the content (75%), and complete it (100%).

For long-form content, scroll depth correlates strongly with engagement and conversion. Users who scroll past 75% on a product page are significantly more likely to purchase. Users who reach 50% of an article are more likely to share it. These correlations let you segment your audience into engagement tiers and optimize each one differently.

50% Scroll Depth Threshold

Performance Considerations

The requestAnimationFrame throttle in the scroll tracker is not just a nice optimization — it is essential for production use. Scroll events fire at the browser's input polling rate, which can be 60 to 120 times per second on modern devices. Without throttling, each scroll event triggers a DOM measurement (reading scrollHeight, scrollTop, innerHeight), which can cause layout thrashing if other code is also reading or writing layout properties.

Layout thrashing occurs when JavaScript alternates between reading and writing DOM layout properties. Each write invalidates the cached layout, and each subsequent read forces the browser to recalculate it. By batching our read operations into a single requestAnimationFrame callback, we ensure that our measurements happen during the browser's normal rendering cycle, not during user input processing.

The capture phase for click events has its own performance implications. Capture-phase handlers fire before bubble-phase handlers, which means our click tracker runs before any application code that might handle (and stop propagation of) the click. This is critical for completeness but means our handler must be as lightweight as possible — any slowness would delay the application's own click handling.

Extension-level performance is the responsibility of the extension author, not the core collector. The plugin interface is intentionally minimal: it does not enforce performance budgets or timeout limits. This is a trade-off. A poorly written extension could degrade page performance, but enforcing limits would add complexity and restrict the kinds of extensions that can be built.

Plugin Architecture Patterns

The collector's plugin system follows the "inversion of control" pattern. The core collector does not know what extensions exist or what they do. Instead, extensions register themselves with the core, and the core provides a controlled API for them to interact with the data pipeline. This inverts the traditional dependency: instead of the core depending on specific features, features depend on the core.

This pattern is widespread in software architecture. WordPress uses it with "hooks" (actions and filters). Express.js uses it with middleware. Webpack uses it with loaders and plugins. jQuery used it with $.fn extensions. The common thread is a stable core with a well-defined extension point that third parties can use without modifying the core.

The limited API passed to extensions (track, set, getConfig, getSessionId) is an example of the "principle of least privilege." Extensions get access to exactly the capabilities they need and nothing more. They cannot call send() directly, cannot modify the configuration, and cannot access other extensions. This reduces the blast radius of a buggy extension: it can send bad data through track(), but it cannot corrupt the core collector's state or bypass sampling.

75% Scroll Depth Threshold

Real-World Extension Ideas

Beyond click tracking and scroll depth, the plugin system supports many other analytics use cases. A form analytics extension could track field interactions: which fields users fill in, which they skip, how long they spend on each field, and where they abandon the form. This data is invaluable for optimizing conversion funnels.

A session recording extension could capture DOM snapshots at intervals, building a replay of the user's session. This is complex (requiring mutation observers and efficient serialization) but the plugin interface supports it. The extension would use track() to send snapshots and getSessionId() to associate them with the session.

A rage-click detector could monitor for rapid clicks in the same area, which indicate user frustration. If a user clicks the same element five times in two seconds, something is broken or confusing. The extension would build on the same click event listener pattern but add temporal and spatial analysis.

An A/B test extension could read experiment assignments from cookies or a configuration service and include the variant in every tracked event. This lets you analyze any metric (scroll depth, click patterns, errors) segmented by experiment group, without modifying the core collector.

Extension Lifecycle in Single-Page Applications

Single-page applications (SPAs) present unique challenges for analytics extensions. In a traditional multi-page site, each navigation destroys the current page and creates a new one, automatically cleaning up event listeners and state. In an SPA, the page never fully unloads — navigations are simulated by swapping DOM content and updating the URL with the History API.

This means extensions must be explicitly destroyed and re-initialized when the user navigates within the SPA. Without destroy(), event listeners accumulate: after ten navigations, you would have ten click handlers and ten scroll handlers all firing simultaneously, sending duplicate data and degrading performance.

The destroy() function in each extension handles this cleanup. When the SPA's router detects a navigation, it calls destroy() on each active extension, then re-initializes them for the new view. The collector's use() function handles the re-initialization, and the extension's destroy() handles the cleanup.

Some extensions may need to persist state across SPA navigations. The scroll tracker, for example, should probably reset its max depth and reported thresholds when navigating to a new view. The click tracker, on the other hand, is stateless — each click is independent. The extension author decides what to persist and what to reset in their init() and destroy() implementations.

100% Scroll Depth Threshold — You made it to the bottom!

Final Interaction Zone

One last set of clickable elements at the bottom of the page:

Back to top