-
Notifications
You must be signed in to change notification settings - Fork 131
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
feat: Support custom scroll selector #961
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,14 @@ | ||
import { Html, Head, Main, NextScript } from 'next/document' | ||
import React from 'react' | ||
|
||
export default function Document() { | ||
return ( | ||
<Html lang="en"> | ||
<Head /> | ||
<body> | ||
<Main /> | ||
<NextScript /> | ||
</body> | ||
</Html> | ||
) | ||
return ( | ||
<Html lang="en"> | ||
<Head /> | ||
<body> | ||
<Main /> | ||
<NextScript /> | ||
</body> | ||
</Html> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import Head from 'next/head' | ||
import Link from 'next/link' | ||
import React, { useEffect } from 'react' | ||
|
||
export default function Home() { | ||
useEffect(() => { | ||
const html = document.querySelector('html') | ||
const body = document.querySelector('body') | ||
const nextRoot = document.querySelector<HTMLDivElement>('div#__next') | ||
if (!html || !body || !nextRoot) return | ||
html.style.height = '100%' | ||
html.style.overflow = 'hidden' | ||
body.style.height = '100%' | ||
nextRoot.style.height = '100%' | ||
return () => { | ||
html.style.height = '' | ||
html.style.overflow = '' | ||
body.style.height = '' | ||
nextRoot.style.height = '' | ||
} | ||
}, []) | ||
|
||
return ( | ||
<> | ||
<Head> | ||
<title>PostHog</title> | ||
<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
</Head> | ||
<main | ||
id="scroll_element" | ||
style={{ | ||
height: '100%', | ||
overflow: 'scroll', | ||
margin: 0, | ||
padding: 0, | ||
}} | ||
> | ||
<div | ||
style={{ | ||
height: '4000px', | ||
overflow: 'hidden', | ||
}} | ||
> | ||
<h1>A long page</h1> | ||
<p> | ||
The window itself does not scroll, the <code>main</code> element does. The content is exactly | ||
4000px tall. | ||
</p> | ||
<div className="buttons"> | ||
<Link href="/">Home</Link> | ||
</div> | ||
|
||
{Array.from({ length: 100 }, (_, i) => ( | ||
<p key={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. | ||
</p> | ||
))} | ||
|
||
<div className="buttons"> | ||
<Link href="/">Home</Link> | ||
</div> | ||
</div> | ||
</main> | ||
</> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
Comment on lines
+144
to
149
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. trying to think defensively (so feel free to shoot this down) is it worth debouncing here? the processing looks cheap but we'll get a lot of updates processed that we don't need 🤷 |
||
} | ||
|
||
|
@@ -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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. similar nit on these helpers, we could lookup the element once and pass it into each of these helpers... (may be so fast it doesn't matter :)) |
||
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 | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -128,6 +128,8 @@ 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[] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: could add a doc comment here so it is clear what it does (given the fact that documentation might come much later and this is an edge case option) |
||
} | ||
|
||
export interface OptInOutCapturingOptions { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quick note that you can't use the css comma here, i.e.
"#scroll_element,html"
as it would always return the html root element, even if#scroll_element
is present on the page :)