Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin: Element Tracking #1400

Open
wants to merge 30 commits into
base: master
Choose a base branch
from

Conversation

jethron
Copy link
Contributor

@jethron jethron commented Dec 11, 2024

This adds a new plugin for the Browser and JavaScript trackers: Element Tracking

The Element Tracking plugin aims to provide a generic mechanism for tracking specific DOM elements on web pages; in particular element lifetimes and view-ability.

It achieves this via the Intersection Observer and Mutation Observer APIs. This is similar to mechanisms like GTM's Visibility Triggers, but Snowplow-specific. Like those triggers, the elements are defined via CSS selectors. This aims to be more flexible in that it can fire more than once per page load if desired.

Configuration can vary from simple (just provide a CSS selector to track visibility events) to quite complex (track existence of elements from my shadow host custom components once per page, track them as visible after they have been on screen for 2500 milliseconds and more than 50% of the area is in-view, collect the heading text from that element and the data-product-id attributes from every div inside it). See the README for various in-depth examples & explanations of the various options.

Four events are supported:

  • create_element: When an element matching a CSS selector is added to the document (or is already in the document when the plugin is enabled; or when an existing DOM element is mutated to match a given selector)
  • expose_element: When an element matching a CSS selector enters the viewport and becomes visible to the user
  • obscure_element: When an element matching a CSS selector leaves the viewport and becomes no-longer-visible to the user
  • destroy_element: When an element that matched a CSS selector is removed from the document or modified to no longer match that selector

And there are 4 companion entities:

  • element: Contains details about elements that are the subject of the above events; since the data is otherwise identical the events all share this one entity
  • element_content: The plugin allows declarative extraction of information about elements and their child elements; this entity allows a flattened tree of children to be described; e.g. so you can track viewability of a Recommendations component, and extract information about the individual items that were recommended at the same time
  • element_statistics: This is an auxiliary entity intended for attaching state tracked by this plugin to other events; e.g. you can have the visibility state, number of times visible, and time-in-view of a given element attached to page ping events to get a rolling summary over time. This also allows element-specific depth tracking, similar to that provided by page pings for the overall document
  • component_parents: You can name selector and mark them as "components"; events triggered for specific elements will have a list of their containing components included in this entity. This works for the above events, and the plugin also provides a mechanism for use as a context generator with the Link and Form tracking plugins.

Proposed schemas for these events & entities: snowplow/iglu-central#1421

The events/entities are quite generic, but the plugin aims to enable uses cases such as:

  • Simple funnels (e.g. form created, form in view, (form events by Form Tracking plugin), form destroyed)
  • Ad unit impression tracking
  • Widget performance measurement (e.g. Recommendations, Over-The-Page/Modal popups, Newsletter signup boxes)
  • Content engagement (e.g. article-specific scroll depth)
  • Product analytics (by naming components you can better identify feature usage for cases where there are multiple ways to perform an action; e.g. link click in body content vs navigation)

Should resolve #98

I haven't tested with a large number of configurations on complex pages; many expose configurations may have performance impacts from calculating total time in view very often, but I've tried to keep everything as async-friendly as possible to manage perf. Community feedback welcome in that regard!

Copy link

bundlemon bot commented Dec 11, 2024

BundleMon

Files added (6)
Status Path Size Limits
trackers/javascript-tracker/dist/sp.js
+24.49KB 30KB / +10%
libraries/browser-tracker-core/dist/index.mod
ule.js
+23.59KB 25KB / +10%
libraries/tracker-core/dist/index.module.js
+19.42KB 20KB / +10%
trackers/browser-tracker/dist/index.umd.min.j
s
+17.39KB 20KB / +10%
trackers/javascript-tracker/dist/sp.lite.js
+17.33KB 20KB / +10%
trackers/browser-tracker/dist/index.module.js
+3.49KB 5KB / +10%

Total files change +105.7KB 0%

Final result: ✅

View report in BundleMon website ➡️


Current branch size history

@jethron jethron force-pushed the plugin-element-tracking branch from 94041da to 3f57ff8 Compare December 11, 2024 06:27
Copy link
Contributor

@matus-tomlein matus-tomlein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is surprisingly comprehensive and well engineered! 👏👏👏

Just some initial feedback from testing this in a React app:

  1. I see the obscure and destroy events firing after the next page view (after useEffect callback in React is triggered on new page elements). This makes sense and is likely very hard to avoid, but is a bit confusing. I was wondering if there is a way to add a firstPageViewId information to these events so that one can understand if they refer to elements created on the previous page? Some elements might outlive one page intentionally, so probably we can't force the previous page view id in the web_page entity. But there might be a better solution I haven't thought of.
  2. Related to the previous one, the obscure and destroy events always have all values in the element entity (width, height, position) set to 0. Not sure if we can use the last non-zero value?
  3. The element_age_ms property in the stats entity seems to be too large – 3468018102679.6997 (see screenshot attached). But this is only happening for some elements, on others it's closer to total_time_visible_ms. Also we probably don't need the floating point precision when the unit is ms.
  4. This is a small suggestion, but I was wondering if it makes sense to add the stats entity for the current element on the obscure/destroy events? Could give more context to the event and potentially a more accurate measurement than the ping events with N second granularity.

Screenshot 2024-12-12 at 14 34 18

@jethron
Copy link
Contributor Author

jethron commented Jan 7, 2025

This is surprisingly comprehensive and well engineered! 👏👏👏

Not sure if compliment? 😅 But I'll take it as one. Thanks!

  1. I see the obscure and destroy events firing after the next page view (after useEffect callback in React is triggered on new page elements). This makes sense and is likely very hard to avoid, but is a bit confusing. I was wondering if there is a way to add a firstPageViewId information to these events so that one can understand if they refer to elements created on the previous page? Some elements might outlive one page intentionally, so probably we can't force the previous page view id in the web_page entity. But there might be a better solution I haven't thought of.

Hm, yes I can see this being problematic. Especially with everything sending async there's a good chance it'll be part of the next pageview rather than the previous one. Good call!

  1. Related to the previous one, the obscure and destroy events always have all values in the element entity (width, height, position) set to 0. Not sure if we can use the last non-zero value?

Last-non-zero might get confusing if it actually shrinks to that before obscured/removed, but last-known-if-zero for those events hopefully helps? Added.

  1. The element_age_ms property in the stats entity seems to be too large – 3468018102679.6997 (see screenshot attached). But this is only happening for some elements, on others it's closer to total_time_visible_ms. Also we probably don't need the floating point precision when the unit is ms.

Oops, relative vs absolute timestamp mixup. Fixed!

  1. This is a small suggestion, but I was wondering if it makes sense to add the stats entity for the current element on the obscure/destroy events? Could give more context to the event and potentially a more accurate measurement than the ping events with N second granularity.

You should already be able to do this with the includeStats setting with e.g. ["obscure_element", "destroy_element"] or similar. Do you mean this would be better as a default?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add DOM entity in-view and scrollability events
2 participants