openreplay/tracker/tracker/src/main/modules/scroll.ts
2022-08-17 18:21:25 +02:00

76 lines
2.1 KiB
TypeScript

import type App from '../app/index.js'
import { SetViewportScroll, SetNodeScroll } from '../app/messages.gen.js'
import { isElementNode, isRootNode } from '../app/guards.js'
export default function (app: App): void {
let documentScroll = false
const nodeScroll: Map<Node, [number, number]> = new Map()
function setNodeScroll(target: EventTarget | null) {
if (target instanceof Element) {
nodeScroll.set(target, [target.scrollLeft, target.scrollTop])
}
}
const sendSetViewportScroll = app.safe((): void =>
app.send(
SetViewportScroll(
window.pageXOffset ||
(document.documentElement && document.documentElement.scrollLeft) ||
(document.body && document.body.scrollLeft) ||
0,
window.pageYOffset ||
(document.documentElement && document.documentElement.scrollTop) ||
(document.body && document.body.scrollTop) ||
0,
),
),
)
const sendSetNodeScroll = app.safe((s: [number, number], node: Node): void => {
const id = app.nodes.getID(node)
if (id !== undefined) {
app.send(SetNodeScroll(id, s[0], s[1]))
}
})
app.attachStartCallback(sendSetViewportScroll)
app.attachStopCallback(() => {
documentScroll = false
nodeScroll.clear()
})
app.nodes.attachNodeCallback((node, isStart) => {
if (isStart && isElementNode(node) && node.scrollLeft + node.scrollTop > 0) {
nodeScroll.set(node, [node.scrollLeft, node.scrollTop])
} else if (isRootNode(node)) {
// scroll is not-composed event (https://javascript.info/shadow-dom-events)
app.attachEventListener(node, 'scroll', (e: Event): void => {
setNodeScroll(e.target)
})
}
})
app.attachEventListener(document, 'scroll', (e: Event): void => {
const target = e.target
if (target === document) {
documentScroll = true
return
}
setNodeScroll(target)
})
app.ticker.attach(
(): void => {
if (documentScroll) {
sendSetViewportScroll()
documentScroll = false
}
nodeScroll.forEach(sendSetNodeScroll)
nodeScroll.clear()
},
5,
false,
)
}