Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ The `Watcher` constructor can be passed 3 different options:

* `time` - The time threshold
* `ratio` - The ratio threshold
* `rootMargin` - The [rootMargin](https://wicg.github.io/IntersectionObserver/#dom-intersectionobserverinit-rootmargin) in object form.
* `rootMargin` - The [rootMargin](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Intersection_observer_options) in object form.
* `root` - The [root](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Intersection_observer_options) element with respect to which we want to watch the target. By default it is window.

## Utility API

Expand Down
6 changes: 5 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,11 @@ import {
Frame
} from './metal/index';


import w from './metal/window-proxy';

import { invalidate } from './metal/window-proxy';

export {
on,
off,
Expand All @@ -58,7 +61,8 @@ export {
SpanielTrackedElement,
setGlobalEngine,
getGlobalEngine,
w as __w__
w as __w__,
invalidate
};

export function queryElement(el: Element, callback: (clientRect: ClientRect, frame: Frame) => void) {
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface SpanielObserverInit {
root?: SpanielTrackedElement;
rootMargin?: DOMString | DOMMargin; // default: 0px
threshold?: SpanielThreshold[]; // default: 0
ALLOW_CACHED_SCHEDULER?: boolean;
}

export interface SpanielRecord {
Expand Down Expand Up @@ -83,4 +84,5 @@ export interface IntersectionObserverInit {
root?: SpanielTrackedElement;
rootMargin?: DOMString; // default: 0px
threshold?: number | number[]; // default: 0
ALLOW_CACHED_SCHEDULER?: boolean;
}
8 changes: 4 additions & 4 deletions src/intersection-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,14 @@ export class SpanielIntersectionObserver implements IntersectionObserver {
this.id = generateToken();
options.threshold = options.threshold || 0;
this.rootMarginObj = rootMarginToDOMMargin(options.rootMargin || '0px');

this.root = options.root;
if (Array.isArray(options.threshold)) {
this.thresholds = <Array<number>>options.threshold;
} else {
this.thresholds = [<number>options.threshold];
}

this.scheduler = new ElementScheduler();
this.scheduler = new ElementScheduler(null, this.root, options.ALLOW_CACHED_SCHEDULER);
}
};

Expand Down Expand Up @@ -182,8 +182,8 @@ export class IntersectionObserverEntry implements IntersectionObserverEntryInit
export function generateEntry(frame: Frame, clientRect: DOMRectReadOnly, el: Element, rootMargin: DOMMargin): IntersectionObserverEntry {
let { top, bottom, left, right } = clientRect;
let rootBounds: ClientRect = {
left: rootMargin.left,
top: rootMargin.top,
left: frame.left + rootMargin.left,
top: frame.top + rootMargin.top,
bottom: rootMargin.bottom,
right: rootMargin.right,
width: frame.width - (rootMargin.right + rootMargin.left),
Expand Down
25 changes: 19 additions & 6 deletions src/metal/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,17 +52,30 @@ export interface FrameInterface {
scrollLeft: number;
width: number;
height: number;
x: number;
y: number;
top: number;
left: number;
}

export interface MetaInterface {
scrollTop: number;
scrollLeft: number;
width: number;
height: number;
scrollLeft: number;
scrollTop: number;
x: number;
y: number;
top: number;
left: number;
}

export interface OnWindowIsDirtyInterface {
fn: any;
scope: any;
id: string;
export interface SpanielClientRectInterface {
width: number;
height: number;
x: number;
y: number;
bottom: number;
top: number;
left: number;
right: number;
}
108 changes: 76 additions & 32 deletions src/metal/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import {
SchedulerInterface,
ElementSchedulerInterface,
FrameInterface,
QueueInterface
QueueInterface,
MetaInterface,
SpanielClientRectInterface
} from './interfaces';
import W from './window-proxy';

Expand All @@ -29,45 +31,88 @@ const TOKEN_SEED = 'xxxx'.replace(/[xy]/g, function(c) {
});
let tokenCounter = 0;

function generateRandomToken() {
return Math.floor(Math.random() * (9999999 - 0o0)).toString(16);
}

export class Frame implements FrameInterface {
constructor(
public timestamp: number,
public scrollTop: number,
public scrollLeft: number,
public width: number,
public height: number
public height: number,
public x: number,
public y: number,
public top: number,
public left: number
) {}
static generate(): Frame {
static generate(root: Element | Window = window): Frame {
const rootMeta = this.revalidateRootMeta(root);
return new Frame(
Date.now(),
W.meta.scrollTop,
W.meta.scrollLeft,
W.meta.width,
W.meta.height
rootMeta.scrollTop,
rootMeta.scrollLeft,
rootMeta.width,
rootMeta.height,
rootMeta.x,
rootMeta.y,
rootMeta.top,
rootMeta.left
);
}
static revalidateRootMeta(root: any = window): MetaInterface {
let _rootMeta: MetaInterface = {
width: 0,
height: 0,
scrollTop: 0,
scrollLeft: 0,
x: 0,
y: 0,
top: 0,
left: 0
};

// if root is dirty update the cached values
if (W.isDirty) { W.updateMeta(); }

if (root === window) {
_rootMeta.height = W.meta.height;
_rootMeta.width = W.meta.width;
_rootMeta.scrollLeft = W.meta.scrollLeft;
_rootMeta.scrollTop = W.meta.scrollTop;
}else if (root) {
let _clientRect = getBoundingClientRect(root);
_rootMeta.scrollTop = root.scrollTop;
_rootMeta.scrollLeft = root.scrollLeft;
_rootMeta.width = _clientRect.width;
_rootMeta.height = _clientRect.height;
_rootMeta.x = _clientRect.x;
_rootMeta.y = _clientRect.y;
_rootMeta.top = _clientRect.top;
_rootMeta.left = _clientRect.left;
}

return _rootMeta;
}
}

export function generateToken() {
return tokenCounter++ + TOKEN_SEED;
}

export abstract class BaseScheduler {
protected root: Element | Window;
protected engine: EngineInterface;
protected queue: QueueInterface;
protected isTicking: Boolean = false;
protected toRemove: Array<string| Element | Function> = [];
protected id?: string;

constructor(customEngine?: EngineInterface) {
constructor(customEngine?: EngineInterface, root: Element | Window = window) {
if (customEngine) {
this.engine = customEngine;
} else {
this.engine = getGlobalEngine();
}

this.root = root;
}
protected abstract applyQueue(frame: Frame): void;

Expand All @@ -81,8 +126,7 @@ export abstract class BaseScheduler {
}
this.toRemove = [];
}

this.applyQueue(Frame.generate());
this.applyQueue(Frame.generate(this.root));
this.engine.scheduleRead(this.tick.bind(this));
}
}
Expand All @@ -97,7 +141,7 @@ export abstract class BaseScheduler {
let frame: Frame = null;
this.engine.scheduleRead(() => {
clientRect = getBoundingClientRect(el);
frame = Frame.generate();
frame = Frame.generate(this.root);
});
this.engine.scheduleWork(() => {
callback(clientRect, frame);
Expand All @@ -108,7 +152,6 @@ export abstract class BaseScheduler {
}
unwatchAll() {
this.queue.clear();
W.__destroy__();
}
startTicking() {
if (!this.isTicking) {
Expand Down Expand Up @@ -140,7 +183,7 @@ export class Scheduler extends BaseScheduler implements SchedulerInterface {
export class PredicatedScheduler extends Scheduler implements SchedulerInterface {
predicate: (frame: Frame) => Boolean;
constructor(predicate: (frame: Frame) => Boolean) {
super(null);
super(null, window);
this.predicate = predicate;
}
applyQueue(frame: Frame) {
Expand All @@ -152,13 +195,17 @@ export class PredicatedScheduler extends Scheduler implements SchedulerInterface

export class ElementScheduler extends BaseScheduler implements ElementSchedulerInterface {
protected queue: DOMQueue;
protected isDirty: boolean = false;
protected id: string = '';
protected lastVersion: number = W.version;
protected ALLOW_CACHED_SCHEDULER: boolean;

constructor(customEngine?: EngineInterface) {
super(customEngine);
constructor(customEngine?: EngineInterface, root?: Element | Window, ALLOW_CACHED_SCHEDULER: boolean = false) {
super(customEngine, root);
this.queue = new DOMQueue();
this.id = generateRandomToken();
this.ALLOW_CACHED_SCHEDULER = ALLOW_CACHED_SCHEDULER;
}

get isDirty(): boolean {
return W.version !== this.lastVersion;
}

applyQueue(frame: Frame) {
Expand All @@ -169,14 +216,19 @@ export class ElementScheduler extends BaseScheduler implements ElementSchedulerI
clientRect = this.queue.items[i].clientRect = getBoundingClientRect(el);
}

// FLAG WHICH WILL EVENTUALLY BE REMOVED
// THE EXPERIMENTAL FLAG DEFAULTS TO OFF
if (!this.ALLOW_CACHED_SCHEDULER) {
clientRect = this.queue.items[i].clientRect = getBoundingClientRect(el);
}

callback(frame, id, clientRect);
}

this.isDirty = false;
this.lastVersion = W.version;
}

watch(el: Element, callback: (frame: FrameInterface, id: string, clientRect?: ClientRect | null) => void, id?: string): string {
this.initWindowIsDirtyListeners();
this.startTicking();
id = id || generateToken();
let clientRect = null;
Expand All @@ -189,14 +241,6 @@ export class ElementScheduler extends BaseScheduler implements ElementSchedulerI
});
return id;
}

initWindowIsDirtyListeners() {
W.onWindowIsDirtyListeners.push({ fn: this.windowIsDirtyHandler, scope: this, id: this.id });
}

windowIsDirtyHandler() {
this.isDirty = true;
}
}

let globalScheduler: Scheduler = null;
Expand Down
Loading