diff --git a/src/record/index.ts b/src/record/index.ts index abc627cd..b5a625a1 100644 --- a/src/record/index.ts +++ b/src/record/index.ts @@ -1,6 +1,6 @@ import { snapshot } from 'rrweb-snapshot'; import initObservers from './observer'; -import { mirror } from '../utils'; +import { mirror, on } from '../utils'; import { EventType, event, @@ -9,14 +9,6 @@ import { IncrementalSource, } from '../types'; -function on( - type: string, - fn: EventListenerOrEventListenerObject, - target = document, -) { - target.addEventListener(type, fn, { capture: true, passive: true }); -} - function wrapEvent(e: event): eventWithTime { return { ...e, @@ -80,6 +72,26 @@ function record(options: recordOptions) { }, }), ), + scrollCb: p => + emit( + wrapEvent({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.Scroll, + ...p, + }, + }), + ), + viewportResizeCb: d => + emit( + wrapEvent({ + type: EventType.IncrementalSnapshot, + data: { + source: IncrementalSource.ViewportResize, + ...d, + }, + }), + ), }); }); } catch (error) { diff --git a/src/record/observer.ts b/src/record/observer.ts index 4f08b59c..02f64028 100644 --- a/src/record/observer.ts +++ b/src/record/observer.ts @@ -1,5 +1,5 @@ import { INode } from 'rrweb-snapshot'; -import { mirror, throttle } from '../utils'; +import { mirror, throttle, on } from '../utils'; import { mutationCallBack, textMutation, @@ -9,9 +9,11 @@ import { observerParam, mousemoveCallBack, mousePosition, - handlerMap, mouseInteractionCallBack, MouseInteractions, + listenerHandler, + scrollCallback, + viewportResizeCallback, } from '../types'; function initMutationObserver(cb: mutationCallBack): MutationObserver { @@ -68,6 +70,7 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver { id: mirror.getId(n as INode), }); }); + // TODO: init new nodes to be INode addedNodes.forEach(n => { adds.push({ parentId: id, @@ -104,7 +107,7 @@ function initMutationObserver(cb: mutationCallBack): MutationObserver { return observer; } -function initMousemoveObserver(cb: mousemoveCallBack): () => void { +function initMousemoveObserver(cb: mousemoveCallBack): listenerHandler { let positions: mousePosition[] = []; let timeBaseline: number | null; const wrappedCb = throttle(() => { @@ -136,16 +139,13 @@ function initMousemoveObserver(cb: mousemoveCallBack): () => void { trailing: false, }, ); - document.addEventListener('mousemove', updatePosition); - return () => { - document.removeEventListener('mousemove', updatePosition); - }; + return on('mousemove', updatePosition); } function initMouseInteractionObserver( cb: mouseInteractionCallBack, -): () => void { - const handlers: handlerMap = {}; +): listenerHandler { + const handlers: listenerHandler[] = []; const getHandler = (eventKey: keyof typeof MouseInteractions) => { return (event: MouseEvent) => { const id = mirror.getId(event.target as INode); @@ -163,25 +163,69 @@ function initMouseInteractionObserver( .forEach((eventKey: keyof typeof MouseInteractions) => { const eventName = eventKey.toLowerCase(); const handler = getHandler(eventKey); - handlers[eventName] = handler; - document.addEventListener(eventName, handler); + handlers.push(on(eventName, handler)); }); return () => { - Object.keys(handlers).forEach(eventName => { - document.removeEventListener(eventName, handlers[eventName]); - }); + handlers.forEach(h => h()); }; } +function initScrollObserver(cb: scrollCallback): listenerHandler { + const updatePosition = throttle(evt => { + if (!evt.target) { + return; + } + const id = mirror.getId(evt.target as INode); + if (evt.target === document) { + cb({ + id, + x: document.documentElement.scrollTop, + y: document.documentElement.scrollLeft, + }); + } else { + cb({ + id, + x: (evt.target as HTMLElement).scrollTop, + y: (evt.target as HTMLElement).scrollLeft, + }); + } + }, 100); + return on('scroll', updatePosition); +} + +function initViewportResizeObserver( + cb: viewportResizeCallback, +): listenerHandler { + const updateDimension = throttle(() => { + const height = + window.innerHeight || + (document.documentElement && document.documentElement.clientHeight) || + (document.body && document.body.clientHeight); + const width = + window.innerWidth || + (document.documentElement && document.documentElement.clientWidth) || + (document.body && document.body.clientWidth); + cb({ + width: Number(width), + height: Number(height), + }); + }, 200); + return on('resize', updateDimension, window); +} + export default function initObservers(o: observerParam) { const mutationObserver = initMutationObserver(o.mutationCb); const mousemoveHandler = initMousemoveObserver(o.mousemoveCb); const mouseInteractionHandler = initMouseInteractionObserver( o.mouseInteractionCb, ); + const scrollHandler = initScrollObserver(o.scrollCb); + const viewportResizeHandler = initViewportResizeObserver(o.viewportResizeCb); return { mutationObserver, mousemoveHandler, mouseInteractionHandler, + scrollHandler, + viewportResizeHandler, }; } diff --git a/src/types.ts b/src/types.ts index a2b8920e..86da426c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -35,6 +35,8 @@ export enum IncrementalSource { Mutation, MouseMove, MouseInteraction, + Scroll, + ViewportResize, } export type mutationData = { @@ -50,10 +52,20 @@ export type mouseInteractionData = { source: IncrementalSource.MouseInteraction; } & mouseInteractionParam; +export type scrollData = { + source: IncrementalSource.Scroll; +} & scrollPosition; + +export type viewportResizeData = { + source: IncrementalSource.ViewportResize; +} & viewportResizeDimention; + export type incrementalData = | mutationData | mousemoveData - | mouseInteractionData; + | mouseInteractionData + | scrollData + | viewportResizeData; export type event = | domContentLoadedEvent @@ -73,6 +85,8 @@ export type observerParam = { mutationCb: mutationCallBack; mousemoveCb: mousemoveCallBack; mouseInteractionCb: mouseInteractionCallBack; + scrollCb: scrollCallback; + viewportResizeCb: viewportResizeCallback; }; export type textMutation = { @@ -116,10 +130,6 @@ export type mousePosition = { timeOffset: number; }; -export type handlerMap = { - [key: string]: EventListener; -}; - export enum MouseInteractions { MouseUp, MouseDown, @@ -142,6 +152,21 @@ type mouseInteractionParam = { export type mouseInteractionCallBack = (d: mouseInteractionParam) => void; +export type scrollPosition = { + id: number; + x: number; + y: number; +}; + +export type scrollCallback = (p: scrollPosition) => void; + +export type viewportResizeDimention = { + width: number; + height: number; +}; + +export type viewportResizeCallback = (d: viewportResizeDimention) => void; + export type Mirror = { map: idNodeMap; getId: (n: INode) => number; @@ -152,3 +177,5 @@ export type throttleOptions = { leading?: boolean; trailing?: boolean; }; + +export type listenerHandler = () => void; diff --git a/src/utils.ts b/src/utils.ts index 487fd087..fa8262b9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,4 +1,13 @@ -import { Mirror, throttleOptions } from './types'; +import { Mirror, throttleOptions, listenerHandler } from './types'; + +export function on( + type: string, + fn: EventListenerOrEventListenerObject, + target: Document | Window = document, +): listenerHandler { + target.addEventListener(type, fn, { capture: true, passive: true }); + return () => target.removeEventListener(type, fn); +} export const mirror: Mirror = { map: {},