+ The window itself does not scroll, the main element does. The content is exactly
+ 4000px tall.
+
+
+ Home
+
+
+ {Array.from({ length: 100 }, (_, i) => (
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut
+ labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco
+ laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in
+ voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
+ cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
+
+ ))}
+
+
+ Home
+
+
+
+ >
+ )
+}
diff --git a/playground/nextjs/styles/globals.css b/playground/nextjs/styles/globals.css
index 759e4997d..e29a0a67a 100644
--- a/playground/nextjs/styles/globals.css
+++ b/playground/nextjs/styles/globals.css
@@ -5,6 +5,10 @@ main {
font-family: helvetica, arial, sans-serif;
}
+html, body {
+ margin: 0
+}
+
.buttons {
display: flex;
gap: 0.5rem;
diff --git a/src/__tests__/page-view.test.ts b/src/__tests__/page-view.test.ts
index 2dbce5c11..3dcc30fe2 100644
--- a/src/__tests__/page-view.test.ts
+++ b/src/__tests__/page-view.test.ts
@@ -1,4 +1,5 @@
import { PageViewManager } from '../page-view'
+import { PostHog } from '../posthog-core'
const mockWindowGetter = jest.fn()
jest.mock('../utils/globals', () => ({
@@ -10,6 +11,9 @@ jest.mock('../utils/globals', () => ({
describe('PageView ID manager', () => {
describe('doPageView', () => {
+ const instance: PostHog = {
+ config: {},
+ } as any
beforeEach(() => {
mockWindowGetter.mockReturnValue({
location: {
@@ -41,7 +45,7 @@ describe('PageView ID manager', () => {
},
})
- const pageViewIdManager = new PageViewManager()
+ const pageViewIdManager = new PageViewManager(instance)
pageViewIdManager.doPageView()
// force the manager to update the scroll data by calling an internal method
@@ -72,7 +76,7 @@ describe('PageView ID manager', () => {
},
})
- const pageViewIdManager = new PageViewManager()
+ const pageViewIdManager = new PageViewManager(instance)
pageViewIdManager.doPageView()
// force the manager to update the scroll data by calling an internal method
@@ -90,7 +94,7 @@ describe('PageView ID manager', () => {
})
it('can handle scroll updates before doPageView is called', () => {
- const pageViewIdManager = new PageViewManager()
+ const pageViewIdManager = new PageViewManager(instance)
pageViewIdManager._updateScrollData()
const firstPageView = pageViewIdManager.doPageView()
@@ -101,7 +105,7 @@ describe('PageView ID manager', () => {
})
it('should include the pathname', () => {
- const pageViewIdManager = new PageViewManager()
+ const pageViewIdManager = new PageViewManager(instance)
const firstPageView = pageViewIdManager.doPageView()
expect(firstPageView.$prev_pageview_pathname).toBeUndefined()
diff --git a/src/page-view.ts b/src/page-view.ts
index 6abb7f1a2..be9b71b71 100644
--- a/src/page-view.ts
+++ b/src/page-view.ts
@@ -1,4 +1,6 @@
import { window } from './utils/globals'
+import { PostHog } from './posthog-core'
+import { _isArray } from './utils/type-utils'
interface PageViewData {
pathname: string
@@ -32,6 +34,11 @@ interface PageViewEventProperties extends ScrollProperties {
export class PageViewManager {
_pageViewData: PageViewData | undefined
_hasSeenPageView = false
+ _instance: PostHog
+
+ constructor(instance: PostHog) {
+ this._instance = instance
+ }
_createPageViewData(): PageViewData {
return {
@@ -134,8 +141,11 @@ export class PageViewManager {
}
startMeasuringScrollPosition() {
- window?.addEventListener('scroll', this._updateScrollData)
- window?.addEventListener('scrollend', this._updateScrollData)
+ // setting the third argument to `true` means that we will receive scroll events for other scrollable elements
+ // on the page, not just the window
+ // see https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#usecapture
+ window?.addEventListener('scroll', this._updateScrollData, true)
+ window?.addEventListener('scrollend', this._updateScrollData, true)
window?.addEventListener('resize', this._updateScrollData)
}
@@ -145,22 +155,45 @@ export class PageViewManager {
window?.removeEventListener('resize', this._updateScrollData)
}
+ _scrollElement(): Element | null | undefined {
+ if (this._instance.config.scroll_root_selector) {
+ const selectors = _isArray(this._instance.config.scroll_root_selector)
+ ? this._instance.config.scroll_root_selector
+ : [this._instance.config.scroll_root_selector]
+ for (const selector of selectors) {
+ const element = window?.document.querySelector(selector)
+ if (element) {
+ return element
+ }
+ }
+ return undefined
+ } else {
+ return window?.document.documentElement
+ }
+ }
+
_scrollHeight(): number {
- return window
- ? Math.max(0, window.document.documentElement.scrollHeight - window.document.documentElement.clientHeight)
- : 0
+ const element = this._scrollElement()
+ return element ? Math.max(0, element.scrollHeight - element.clientHeight) : 0
}
_scrollY(): number {
- return window ? window.scrollY || window.pageYOffset || window.document.documentElement.scrollTop || 0 : 0
+ if (this._instance.config.scroll_root_selector) {
+ const element = this._scrollElement()
+ return (element && element.scrollTop) || 0
+ } else {
+ return window ? window.scrollY || window.pageYOffset || window.document.documentElement.scrollTop || 0 : 0
+ }
}
_contentHeight(): number {
- return window?.document.documentElement.scrollHeight || 0
+ const element = this._scrollElement()
+ return element?.scrollHeight || 0
}
_contentY(): number {
- const clientHeight = window?.document.documentElement.clientHeight || 0
+ const element = this._scrollElement()
+ const clientHeight = element?.clientHeight || 0
return this._scrollY() + clientHeight
}
}
diff --git a/src/posthog-core.ts b/src/posthog-core.ts
index 07efdd15f..948be0c46 100644
--- a/src/posthog-core.ts
+++ b/src/posthog-core.ts
@@ -326,7 +326,7 @@ export class PostHog {
this.featureFlags = new PostHogFeatureFlags(this)
this.toolbar = new Toolbar(this)
- this.pageViewManager = new PageViewManager()
+ this.pageViewManager = new PageViewManager(this)
this.surveys = new PostHogSurveys(this)
this.rateLimiter = new RateLimiter()
diff --git a/src/types.ts b/src/types.ts
index 4f9efc479..8292bf171 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -128,6 +128,7 @@ export interface PostHogConfig {
segment?: any
__preview_measure_pageview_stats?: boolean
__preview_send_client_session_params?: boolean
+ scroll_root_selector?: string | string[]
}
export interface OptInOutCapturingOptions {
From 7b4ab5e73a83210c820fa00cdee84791412b09b7 Mon Sep 17 00:00:00 2001
From: Robbie Coomber
Date: Mon, 15 Jan 2024 14:31:07 +0000
Subject: [PATCH 2/2] Add a comment for scroll_root_selector
---
src/types.ts | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/types.ts b/src/types.ts
index 8292bf171..3dde979d4 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -128,6 +128,7 @@ export interface PostHogConfig {
segment?: any
__preview_measure_pageview_stats?: boolean
__preview_send_client_session_params?: boolean
+ // Let the pageview scroll stats use a custom css selector for the root element, e.g. `main`
scroll_root_selector?: string | string[]
}