diff --git a/package.json b/package.json index cd0139c..00a4d80 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "tsc && yarn run build && testem ci && node test/headless/run", "serve": "yarn run build && node test/headless/server/app", - "test:headless": "mocha --require @babel/register test/headless/specs/**/*.js --exit", + "test:headless": "mocha --require @babel/register test/headless/specs/**/*.js test/headless/specs/*.js --exit --timeout 5000", "watch": "broccoli-timepiece exports", "build": "./scripts/build.sh", "stats": "node scripts/size-calc", diff --git a/src/interfaces.ts b/src/interfaces.ts index dffd7a3..b4a97c7 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -28,36 +28,81 @@ export interface SpanielObserverInit { USE_NATIVE_IO?: boolean; } +export interface TimeCompat { + highResTime: number; + unixTime: number; +} + export interface SpanielRecord { target: SpanielTrackedElement; payload: any; thresholdStates: SpanielThresholdState[]; - lastSeenEntry: IntersectionObserverEntry | null; + lastSeenEntry: MaybeInternalIntersectionObserverEntry | null; } export interface SpanielThresholdState { lastSatisfied: Boolean; - lastEntry: IntersectionObserverEntry | null; + lastEntry: MaybeInternalIntersectionObserverEntry | null; threshold: SpanielThreshold; - lastVisible: number; + lastVisible: TimeCompat; visible: boolean; timeoutId?: number; } export interface SpanielIntersectionObserverEntryInit { - time: DOMHighResTimeStamp; - rootBounds: ClientRect; - boundingClientRect: ClientRect; - intersectionRect: ClientRect; + highResTime: DOMHighResTimeStamp; + unixTime: number; + rootBounds: SpanielRect; + boundingClientRect: SpanielRect; + intersectionRect: SpanielRect & ClientRect; target: SpanielTrackedElement; } -export interface SpanielObserverEntry extends IntersectionObserverEntryInit { +export interface SpanielRect extends Partial { + readonly height: number; + readonly width: number; + readonly x: number; + readonly y: number; +} + +export interface SpanielObserverEntry { + isIntersecting: boolean; duration: number; + visibleTime: number; intersectionRatio: number; entering: boolean; label?: string; payload?: any; + unixTime: number; + highResTime: number; + time: number; + target: Element; + boundingClientRect: SpanielRect; + intersectionRect: SpanielRect; + rootBounds: SpanielRect | null; +} + +export interface InternalIntersectionObserverEntry { + time: number; + highResTime: DOMHighResTimeStamp; + target: Element; + boundingClientRect: SpanielRect; + intersectionRect: SpanielRect; + rootBounds: SpanielRect | null; + intersectionRatio: number; + isIntersecting: boolean; +} + +export interface MaybeInternalIntersectionObserverEntry { + time: number; + unixTime?: number; + highResTime?: DOMHighResTimeStamp; + target: Element; + boundingClientRect: SpanielRect; + intersectionRect: SpanielRect & ClientRect; + rootBounds: SpanielRect | null; + intersectionRatio: number; + isIntersecting: boolean; } export interface IntersectionObserverClass { diff --git a/src/intersection-observer.ts b/src/intersection-observer.ts index 5d5f0b6..86a6c95 100644 --- a/src/intersection-observer.ts +++ b/src/intersection-observer.ts @@ -19,15 +19,17 @@ import { DOMRectReadOnly, IntersectionObserverInit, DOMMargin, - SpanielIntersectionObserverEntryInit + SpanielIntersectionObserverEntryInit, + InternalIntersectionObserverEntry, + SpanielRect } from './interfaces'; interface EntryEvent { - entry: IntersectionObserverEntry; + entry: InternalIntersectionObserverEntry; numSatisfiedThresholds: number; } -function marginToRect(margin: DOMMargin): ClientRect { +function marginToRect(margin: DOMMargin): ClientRect & SpanielRect { let { left, right, top, bottom } = margin; return { left, @@ -35,7 +37,9 @@ function marginToRect(margin: DOMMargin): ClientRect { bottom, right, width: right - left, - height: bottom - top + height: bottom - top, + x: left, + y: top }; } @@ -140,13 +144,14 @@ export class SpanielIntersectionObserver implements IntersectionObserver { } } -function addRatio(entryInit: SpanielIntersectionObserverEntryInit): IntersectionObserverEntry { - const { time, rootBounds, boundingClientRect, intersectionRect, target } = entryInit; +function addRatio(entryInit: SpanielIntersectionObserverEntryInit): InternalIntersectionObserverEntry { + const { unixTime, highResTime, rootBounds, boundingClientRect, intersectionRect, target } = entryInit; const boundingArea = boundingClientRect.height * boundingClientRect.width; const intersectionRatio = boundingArea > 0 ? (intersectionRect.width * intersectionRect.height) / boundingArea : 0; return { - time, + time: unixTime, + highResTime, rootBounds, boundingClientRect, intersectionRect, @@ -156,7 +161,7 @@ function addRatio(entryInit: SpanielIntersectionObserverEntryInit): Intersection }; } -function emptyRect(): ClientRect | DOMRect { +function emptyRect(): ClientRect & SpanielRect { return { bottom: 0, height: 0, @@ -174,26 +179,31 @@ export function generateEntry( clientRect: DOMRectReadOnly, el: HTMLElement, rootMargin: DOMMargin -): IntersectionObserverEntry { +): InternalIntersectionObserverEntry { if (el.style.display === 'none') { return { + time: frame.dateNow, + highResTime: frame.highResTime, boundingClientRect: emptyRect(), intersectionRatio: 0, intersectionRect: emptyRect(), isIntersecting: false, rootBounds: emptyRect(), - target: el, - time: frame.timestamp + target: el }; } let { bottom, right } = clientRect; - let rootBounds: ClientRect = { - left: frame.left + rootMargin.left, - top: frame.top + rootMargin.top, + const left = frame.left + rootMargin.left; + const top = frame.top + rootMargin.top; + let rootBounds: SpanielRect & ClientRect = { + left, + top, bottom: rootMargin.bottom, right: rootMargin.right, width: frame.width - (rootMargin.right + rootMargin.left), - height: frame.height - (rootMargin.bottom + rootMargin.top) + height: frame.height - (rootMargin.bottom + rootMargin.top), + y: top, + x: left }; let intersectX = Math.max(rootBounds.left, clientRect.left); @@ -202,9 +212,13 @@ export function generateEntry( let width = Math.min(rootBounds.left + rootBounds.width, clientRect.right) - intersectX; let height = Math.min(rootBounds.top + rootBounds.height, clientRect.bottom) - intersectY; - let intersectionRect: ClientRect = { - left: width >= 0 ? intersectX : 0, - top: intersectY >= 0 ? intersectY : 0, + const interLeft = width >= 0 ? intersectX : 0; + const interTop = intersectY >= 0 ? intersectY : 0; + let intersectionRect: ClientRect & SpanielRect = { + left: interLeft, + top: interTop, + x: interLeft, + y: interTop, width, height, right, @@ -212,7 +226,8 @@ export function generateEntry( }; return addRatio({ - time: frame.timestamp, + unixTime: frame.dateNow, + highResTime: frame.highResTime, rootBounds, target: el, boundingClientRect: marginToRect(clientRect), diff --git a/src/metal/interfaces.ts b/src/metal/interfaces.ts index 0eda640..f8c462a 100644 --- a/src/metal/interfaces.ts +++ b/src/metal/interfaces.ts @@ -51,7 +51,8 @@ export interface ElementSchedulerInterface extends BaseSchedulerInterface { } export interface FrameInterface { - timestamp: number; + dateNow: number; + highResTime: DOMHighResTimeStamp; scrollTop: number; scrollLeft: number; width: number; diff --git a/src/metal/scheduler.ts b/src/metal/scheduler.ts index bac7601..f6ec9f8 100644 --- a/src/metal/scheduler.ts +++ b/src/metal/scheduler.ts @@ -33,7 +33,8 @@ let tokenCounter = 0; export class Frame implements FrameInterface { constructor( - public timestamp: number, + public dateNow: number, + public highResTime: number, public scrollTop: number, public scrollLeft: number, public width: number, @@ -47,6 +48,7 @@ export class Frame implements FrameInterface { const rootMeta = this.revalidateRootMeta(root); return new Frame( Date.now(), + performance.now(), rootMeta.scrollTop, rootMeta.scrollLeft, rootMeta.width, diff --git a/src/native-spaniel-observer.ts b/src/native-spaniel-observer.ts deleted file mode 100644 index 613db8c..0000000 --- a/src/native-spaniel-observer.ts +++ /dev/null @@ -1,259 +0,0 @@ -/* -Copyright 2017 LinkedIn Corp. Licensed under the Apache License, -Version 2.0 (the "License"); you may not use this file except in -compliance with the License. You may obtain a copy of the License -at http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -*/ - -import { - DOMMargin, - DOMString, - IntersectionObserverClass, - IntersectionObserverInit, - SpanielObserverEntry, - SpanielObserverInit, - SpanielObserverInterface, - SpanielRecord, - SpanielThreshold, - SpanielThresholdState, - SpanielTrackedElement -} from './interfaces'; -import { generateToken, on, scheduleWork } from './metal/index'; -import w from './metal/window-proxy'; -import { calculateIsIntersecting } from './utils'; - -let emptyRect = { x: 0, y: 0, width: 0, height: 0 }; - -export function DOMMarginToRootMargin(d: DOMMargin): DOMString { - return `${d.top}px ${d.right}px ${d.bottom}px ${d.left}px`; -} - -export class SpanielObserver implements SpanielObserverInterface { - callback: (entries: SpanielObserverEntry[]) => void; - observer: IntersectionObserver; - thresholds: SpanielThreshold[]; - recordStore: { [key: string]: SpanielRecord }; - queuedEntries: SpanielObserverEntry[]; - private paused: boolean; - constructor( - ObserverClass: IntersectionObserverClass, - callback: (entries: SpanielObserverEntry[]) => void, - options?: SpanielObserverInit - ) { - this.paused = false; - this.queuedEntries = []; - this.recordStore = {}; - this.callback = callback; - let { root, rootMargin, threshold } = - options || - ({ - threshold: [] - } as SpanielObserverInit); - rootMargin = rootMargin || '0px'; - let convertedRootMargin: DOMString = - typeof rootMargin !== 'string' ? DOMMarginToRootMargin(rootMargin) : rootMargin; - this.thresholds = threshold.sort((t: SpanielThreshold) => t.ratio); - - let o: IntersectionObserverInit = { - root, - rootMargin: convertedRootMargin, - threshold: this.thresholds.map((t: SpanielThreshold) => t.ratio) - }; - this.observer = new ObserverClass((records: IntersectionObserverEntry[]) => this.internalCallback(records), o); - - if (w.hasDOM) { - on('beforeunload', this.onWindowClosed.bind(this)); - on('hide', this.onTabHidden.bind(this)); - on('show', this.onTabShown.bind(this)); - } - } - private onWindowClosed() { - this.onTabHidden(); - } - private setAllHidden() { - let ids = Object.keys(this.recordStore); - let time = Date.now(); - for (let i = 0; i < ids.length; i++) { - this.handleRecordExiting(this.recordStore[ids[i]], time); - } - this.flushQueuedEntries(); - } - private onTabHidden() { - this.paused = true; - this.setAllHidden(); - } - private onTabShown() { - this.paused = false; - - let ids = Object.keys(this.recordStore); - let time = Date.now(); - for (let i = 0; i < ids.length; i++) { - let entry = this.recordStore[ids[i]].lastSeenEntry; - if (entry) { - let { intersectionRatio, boundingClientRect, rootBounds, intersectionRect, target } = entry; - this.handleObserverEntry({ - intersectionRatio, - boundingClientRect, - time, - rootBounds, - intersectionRect, - target, - isIntersecting: intersectionRatio > 0 - }); - } - } - } - private internalCallback(records: IntersectionObserverEntry[]) { - records.forEach(this.handleObserverEntry.bind(this)); - } - private flushQueuedEntries() { - if (this.queuedEntries.length > 0) { - this.callback(this.queuedEntries); - this.queuedEntries = []; - } - } - private generateSpanielEntry(entry: IntersectionObserverEntry, state: SpanielThresholdState): SpanielObserverEntry { - let { intersectionRatio, time, rootBounds, boundingClientRect, intersectionRect, target, isIntersecting } = entry; - let record = this.recordStore[(target).__spanielId]; - - return { - isIntersecting, - intersectionRatio, - time, - rootBounds, - boundingClientRect, - intersectionRect, - target: target, - duration: 0, - entering: false, - payload: record.payload, - label: state.threshold.label - }; - } - private handleRecordExiting(record: SpanielRecord, time: number = Date.now()) { - record.thresholdStates.forEach((state: SpanielThresholdState) => { - const boundingClientRect = record.lastSeenEntry && record.lastSeenEntry.boundingClientRect; - this.handleThresholdExiting( - { - intersectionRatio: -1, - time, - isIntersecting: false, - payload: record.payload, - label: state.threshold.label, - entering: false, - rootBounds: emptyRect, - boundingClientRect: boundingClientRect || emptyRect, - intersectionRect: emptyRect, - duration: time - state.lastVisible, - target: record.target - }, - state - ); - state.lastSatisfied = false; - state.visible = false; - state.lastEntry = null; - }); - } - private handleThresholdExiting(spanielEntry: SpanielObserverEntry, state: SpanielThresholdState) { - let { time } = spanielEntry; - let hasTimeThreshold = !!state.threshold.time; - if (state.lastSatisfied && (!hasTimeThreshold || (hasTimeThreshold && state.visible))) { - // Make into function - spanielEntry.duration = time - state.lastVisible; - spanielEntry.entering = false; - state.visible = false; - this.queuedEntries.push(spanielEntry); - } - - clearTimeout(state.timeoutId); - } - private handleObserverEntry(entry: IntersectionObserverEntry) { - let { time } = entry; - let target = entry.target; - let record = this.recordStore[target.__spanielId]; - - if (!record) { - return; - } - - record.lastSeenEntry = entry; - - if (!this.paused) { - record.thresholdStates.forEach((state: SpanielThresholdState) => { - // Find the thresholds that were crossed. Since you can have multiple thresholds - // for the same ratio, could be multiple thresholds - let hasTimeThreshold = !!state.threshold.time; - let spanielEntry: SpanielObserverEntry = this.generateSpanielEntry(entry, state); - - const ratioSatisfied = entry.intersectionRatio >= state.threshold.ratio; - const isIntersecting = calculateIsIntersecting(entry); - const isSatisfied = ratioSatisfied && isIntersecting; - - if (isSatisfied != state.lastSatisfied) { - if (isSatisfied) { - spanielEntry.entering = true; - if (hasTimeThreshold) { - state.lastVisible = time; - const timerId: number = Number( - setTimeout(() => { - state.visible = true; - spanielEntry.duration = Date.now() - state.lastVisible; - this.callback([spanielEntry]); - }, state.threshold.time) - ); - state.timeoutId = timerId; - } else { - state.visible = true; - this.queuedEntries.push(spanielEntry); - } - } else { - this.handleThresholdExiting(spanielEntry, state); - } - - state.lastEntry = entry; - state.lastSatisfied = isSatisfied; - } - }); - this.flushQueuedEntries(); - } - } - disconnect() { - this.setAllHidden(); - this.observer.disconnect(); - this.recordStore = {}; - } - unobserve(element: SpanielTrackedElement) { - let record = this.recordStore[element.__spanielId]; - if (record) { - delete this.recordStore[element.__spanielId]; - this.observer.unobserve(element); - scheduleWork(() => { - this.handleRecordExiting(record); - this.flushQueuedEntries(); - }); - } - } - observe(target: Element, payload: any = null) { - let trackedTarget = target as SpanielTrackedElement; - let id = (trackedTarget.__spanielId = trackedTarget.__spanielId || generateToken()); - - this.recordStore[id] = { - target: trackedTarget, - payload, - lastSeenEntry: null, - thresholdStates: this.thresholds.map((threshold: SpanielThreshold) => ({ - lastSatisfied: false, - lastEntry: null, - threshold, - visible: false, - lastVisible: 0 - })) - }; - this.observer.observe(trackedTarget); - return id; - } -} diff --git a/src/native-watcher.ts b/src/native-watcher.ts deleted file mode 100644 index 21ddbfa..0000000 --- a/src/native-watcher.ts +++ /dev/null @@ -1,107 +0,0 @@ -/* -Copyright 2016 LinkedIn Corp. Licensed under the Apache License, -Version 2.0 (the "License"); you may not use this file except in -compliance with the License. You may obtain a copy of the License -at http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -*/ - -import { SpanielObserver } from './native-spaniel-observer'; - -import { - SpanielObserverEntry, - DOMString, - DOMMargin, - SpanielTrackedElement, - IntersectionObserverClass -} from './interfaces'; - -export interface WatcherConfig { - ratio?: number; - time?: number; - rootMargin?: DOMString | DOMMargin; - root?: SpanielTrackedElement; -} - -export type EventName = 'impressed' | 'exposed' | 'visible' | 'impression-complete'; - -export type WatcherCallback = (eventName: EventName, callback: WatcherCallbackOptions) => void; - -export interface Threshold { - label: EventName; - time: number; - ratio: number; -} - -export interface WatcherCallbackOptions { - duration: number; - visibleTime?: number; - boundingClientRect: DOMRectInit; -} - -function onEntry(entries: SpanielObserverEntry[]) { - entries.forEach((entry: SpanielObserverEntry) => { - const { label, duration, boundingClientRect } = entry; - const opts: WatcherCallbackOptions = { - duration, - boundingClientRect - }; - if (entry.entering) { - entry.payload.callback(label, opts); - } else if (entry.label === 'impressed') { - opts.visibleTime = entry.time - entry.duration; - entry.payload.callback('impression-complete', opts); - } - }); -} - -export class Watcher { - observer: SpanielObserver; - constructor(ObserverClass: IntersectionObserverClass, config: WatcherConfig = {}) { - let { time, ratio, rootMargin, root } = config; - - let threshold: Threshold[] = [ - { - label: 'exposed', - time: 0, - ratio: 0 - } - ]; - - if (time) { - threshold.push({ - label: 'impressed', - time, - ratio: ratio || 0 - }); - } - - if (ratio) { - threshold.push({ - label: 'visible', - time: 0, - ratio - }); - } - - this.observer = new SpanielObserver(ObserverClass, onEntry, { - rootMargin, - threshold, - root - }); - } - watch(el: Element, callback: WatcherCallback) { - this.observer.observe(el, { - callback - }); - } - unwatch(el: Element) { - this.observer.unobserve(el as SpanielTrackedElement); - } - disconnect() { - this.observer.disconnect(); - } -} diff --git a/src/spaniel-observer.ts b/src/spaniel-observer.ts index 1c50b49..76f1e80 100644 --- a/src/spaniel-observer.ts +++ b/src/spaniel-observer.ts @@ -13,6 +13,7 @@ import { DOMMargin, DOMString, IntersectionObserverInit, + MaybeInternalIntersectionObserverEntry, SpanielObserverEntry, SpanielObserverInit, SpanielObserverInterface, @@ -98,17 +99,12 @@ export class SpanielObserver implements SpanielObserverInterface { this.paused = true; this.setAllHidden(); } - // Generate a timestamp using the same relative origin time as the backing intersection observer - // native IO timestamps are relative to navigation start, whereas the spaniel polyfill uses unix - // timestamps, relative to 00:00:00 UTC on 1 January 1970 - private generateObserverTimestamp() { - return this.usingNativeIo ? Math.floor(performance.now()) : Date.now(); - } private _onTabShown() { this.paused = false; let ids = Object.keys(this.recordStore); - let time = this.generateObserverTimestamp(); + const highResTime = performance.now(); + const unixTime = Date.now(); for (let i = 0; i < ids.length; i++) { let entry = this.recordStore[ids[i]].lastSeenEntry; if (entry) { @@ -116,7 +112,9 @@ export class SpanielObserver implements SpanielObserverInterface { this.handleObserverEntry({ intersectionRatio, boundingClientRect, - time, + time: unixTime, + highResTime, + unixTime, isIntersecting, rootBounds, intersectionRect, @@ -134,20 +132,31 @@ export class SpanielObserver implements SpanielObserverInterface { this.queuedEntries = []; } } - private generateSpanielEntry(entry: IntersectionObserverEntry, state: SpanielThresholdState): SpanielObserverEntry { + private generateSpanielEntry( + entry: MaybeInternalIntersectionObserverEntry, + state: SpanielThresholdState + ): SpanielObserverEntry { let { intersectionRatio, rootBounds, boundingClientRect, intersectionRect, isIntersecting, time, target } = entry; let record = this.recordStore[(target).__spanielId]; - const timeOrigin = w.performance.timeOrigin || w.performance.timing.navigationStart; - const unixTime = this.usingNativeIo ? Math.floor(timeOrigin + time) : time; + const unixTime = this.usingNativeIo + ? Math.floor(w.performance.timeOrigin || w.performance.timing.navigationStart + time) + : time; + const highResTime = this.usingNativeIo ? time : entry.highResTime; + if (!highResTime) { + throw new Error('Missing intersection entry timestamp'); + } return { intersectionRatio, isIntersecting, + unixTime, time: unixTime, + highResTime, rootBounds, boundingClientRect, intersectionRect, target: target, duration: 0, + visibleTime: isIntersecting ? time : -1, entering: false, payload: record.payload, label: state.threshold.label @@ -155,20 +164,24 @@ export class SpanielObserver implements SpanielObserverInterface { } private handleRecordExiting(record: SpanielRecord) { const time = Date.now(); + const perfTime = performance.now(); record.thresholdStates.forEach((state: SpanielThresholdState) => { const boundingClientRect = record.lastSeenEntry && record.lastSeenEntry.boundingClientRect; this.handleThresholdExiting( { intersectionRatio: -1, isIntersecting: false, + unixTime: time, time, + highResTime: perfTime, payload: record.payload, label: state.threshold.label, entering: false, rootBounds: emptyRect, boundingClientRect: boundingClientRect || emptyRect, intersectionRect: emptyRect, - duration: time - state.lastVisible, + visibleTime: state.lastVisible.unixTime, + duration: perfTime - state.lastVisible.highResTime, target: record.target }, state @@ -179,11 +192,12 @@ export class SpanielObserver implements SpanielObserverInterface { }); } private handleThresholdExiting(spanielEntry: SpanielObserverEntry, state: SpanielThresholdState) { - let { time } = spanielEntry; + let { highResTime } = spanielEntry; let hasTimeThreshold = !!state.threshold.time; if (state.lastSatisfied && (!hasTimeThreshold || (hasTimeThreshold && state.visible))) { // Make into function - spanielEntry.duration = time - state.lastVisible; + spanielEntry.duration = highResTime - state.lastVisible.highResTime; + spanielEntry.visibleTime = state.lastVisible.unixTime; spanielEntry.entering = false; state.visible = false; this.queuedEntries.push(spanielEntry); @@ -191,7 +205,7 @@ export class SpanielObserver implements SpanielObserverInterface { clearTimeout(state.timeoutId); } - private handleObserverEntry(entry: IntersectionObserverEntry) { + private handleObserverEntry(entry: MaybeInternalIntersectionObserverEntry) { let target = entry.target; let record = this.recordStore[target.__spanielId]; @@ -218,11 +232,15 @@ export class SpanielObserver implements SpanielObserverInterface { if (isSatisfied) { spanielEntry.entering = true; if (hasTimeThreshold) { - state.lastVisible = spanielEntry.time; + state.lastVisible = { + highResTime: spanielEntry.highResTime, + unixTime: spanielEntry.unixTime + }; const timerId: number = Number( setTimeout(() => { state.visible = true; - spanielEntry.duration = Date.now() - state.lastVisible; + spanielEntry.duration = performance.now() - state.lastVisible.highResTime; + spanielEntry.visibleTime = state.lastVisible.unixTime; this.callback([spanielEntry]); }, state.threshold.time) ); @@ -285,7 +303,10 @@ export class SpanielObserver implements SpanielObserverInterface { lastEntry: null, threshold, visible: false, - lastVisible: 0 + lastVisible: { + unixTime: 0, + highResTime: -1 + } })) }; this.observer.observe(trackedTarget); diff --git a/src/watcher.ts b/src/watcher.ts index 03af208..53d6418 100644 --- a/src/watcher.ts +++ b/src/watcher.ts @@ -45,13 +45,12 @@ function onEntry(entries: SpanielObserverEntry[]) { const opts: WatcherCallbackOptions = { duration, boundingClientRect, - visibleTime: entry.time, + visibleTime: entry.visibleTime, intersectionRect }; if (entry.entering) { entry.payload.callback(label, opts); } else if (entry.label === 'impressed') { - opts.visibleTime = entry.time - entry.duration; entry.payload.callback('impression-complete', opts); } });