Skip to content

Commit

Permalink
backporting the root implementation with memoize feature
Browse files Browse the repository at this point in the history
  • Loading branch information
lynchbomb committed Apr 26, 2018
1 parent b10891c commit 1387e54
Show file tree
Hide file tree
Showing 19 changed files with 301 additions and 168 deletions.
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 { validateState } from './metal/window-proxy';

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

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

0 comments on commit 1387e54

Please sign in to comment.