What this lets you do
Count how many visitors actually reached your pricing table, your testimonials, or the bottom of a long post, and then use that count as a funnel step. Add one attribute to the element:scroll_to_pricing goal fires the moment any part of that section enters the viewport. No JavaScript, no deploy beyond the HTML edit.
Before you start
The Traceten snippet has to be installed on the page. See the install overview. The name follows the goal name rules: lowercase letters, digits and underscores, starting with a letter. An invalid or reserved name is ignored silently.Properties
data-traceten-goal-* attributes work here too, on the same element:
scroll_to_pricing with { "variant": "b", "page_type": "landing" }. The same property rules and limits apply, including the fact that property keys and values are both stored and both read back (detail).
Once per element
A scroll goal fires at most once per element, per page load. Scroll past the section, scroll back up, scroll down to it again: one goal. That guarantee is per element, and an element is a DOM node. Navigating away and back within a single-page app usually rebuilds the section as a new node, and a new node counts again: two pageviews, two completions, which is the right answer. What cannot happen is the same node counting twice. This is deliberate and it is not just tidiness. Scroll goals are the highest-volume kind of goal, and every goal is a billable event on your plan. A goal that re-fired every time a section re-entered the viewport would charge you several times for one visitor reading one page, and would make the number meaningless as a funnel step.Single-page apps
A scroll goal fires at most once per element, for as long as the page is open. Not once per URL: once per element. Traceten remembers which elements have fired and never arms one of them a second time, so a visitor who reaches your pricing section, navigates elsewhere and comes back is one completion, not two. What a route change does is arm every goal element that has not fired yet, including the ones the new route just rendered. In practice that is the normal case: most frameworks build fresh DOM nodes for a new route, so ascroll_to_pricing section on /pricing is a different element from the one on /product and counts on its own.
The one case where this is visible: if your app keeps the very same DOM node mounted across routes (a shared layout, a persistent sidebar), that node fires once and then not again, however many times the URL changes around it.
A goal counts whether the visitor scrolled to the section or the router put it in front of them. Navigating to a route whose section is above the fold records the completion on arrival, with no scrolling, exactly as a fresh page load of that same URL would. The two entry paths agree by design.
A pageview for the same URL changes nothing at all. If your app calls traceten("pageview") by hand on a route that did not actually change, no goal fires a second time.
Known limitations
Elements added after the page loads are picked up on the next route change, not immediately. Traceten scans fordata-traceten-scroll elements when the page loads and again on each single-page-app route change. It does not watch the DOM continuously, because a document-wide mutation observer on every customer page is a cost we are not willing to put on your site for this feature.
This includes the case where a route changes, paints a skeleton, and fills in the real content a second later: the scan runs against the skeleton and finds nothing, so that route’s new section is armed on the next route change rather than on this one. Anything already armed keeps working throughout, including sections that were waiting to be scrolled to.
For a scroll goal injected into an already-loaded page without a route change (a lazy-loaded section, a modal, content revealed by a “load more” button), call traceten.track("your_goal_name") directly at the moment the content appears.
Any visible sliver counts as “in view.” The goal fires as soon as any part of the element intersects the viewport, not when it is fully visible or centred. If you want “reached the bottom of the article,” put the attribute on a small element at the bottom rather than on the article itself.
At most 200 scroll elements are armed, in document order. The cut is taken before anything else, on every scan, so an element that sits past the 200th in the document is never armed at all, for the life of the page load. It is not “skipped this time and picked up later”. A page with more than 200 scroll goals is almost always a selector mistake.
Browser support
Scroll goals use the browser’sIntersectionObserver API, which every current browser has had for years.
Where it is missing, the feature does nothing: no goal, no error, no fallback. Traceten does not ship a polyfill for it, because the snippet is capped at 10KB and a polyfill would cost a meaningful share of that budget to serve a fraction of a percent of traffic. Everything else on the page, including click goals and pageviews, is unaffected.
Verify it worked
- Open the page with devtools, Network tab, filtered to
conversions. - Scroll the section into view.
- One
POSTtohttps://ingest.traceten.com/v1/conversionsshould appear, with your goal name in the body.
display: none never intersects), then work through when a goal is not recorded. The observer is installed only once a consent tool on the page has answered, so a scroll before that point records nothing.

