Skip to content

Commit

Permalink
Handle zero-area and display: none cases
Browse files Browse the repository at this point in the history
Attempts to satsify the spec: https://www.w3.org/TR/intersection-observer/#update-intersection-observations-algo

isIntersecting, non-zero area, and display:nonen are all related, so fixing in one swoop.

Fixes:
#93
#73

Related issues:
w3c/IntersectionObserver#69
w3c/IntersectionObserver#222
  • Loading branch information
asakusuma committed Jan 6, 2019
1 parent b754bab commit 80e1864
Show file tree
Hide file tree
Showing 10 changed files with 656 additions and 522 deletions.
6 changes: 2 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

import { SpanielIntersectionObserver, generateEntry } from './intersection-observer';

import { entrySatisfiesRatio } from './utils';

import { SpanielTrackedElement, DOMMargin, IntersectionObserverClass } from './interfaces';

export { Watcher, WatcherConfig } from './watcher';
Expand Down Expand Up @@ -46,13 +44,13 @@ export function queryElement(el: Element, callback: (bcr: ClientRect, frame: Fra
}

export function elementSatisfiesRatio(
el: Element,
el: HTMLElement,
ratio: number = 0,
callback: (result: Boolean) => void,
rootMargin: DOMMargin = { top: 0, bottom: 0, left: 0, right: 0 }
) {
queryElement(el, (bcr: ClientRect, frame: Frame) => {
let entry = generateEntry(frame, bcr, el, rootMargin);
callback(entrySatisfiesRatio(entry, ratio));
callback(entry.isIntersecting && entry.intersectionRatio >= ratio);
});
}
2 changes: 1 addition & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/

export interface SpanielTrackedElement extends Element {
export interface SpanielTrackedElement extends HTMLElement {
__spanielId: string;
}

Expand Down
44 changes: 36 additions & 8 deletions src/intersection-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/

import { entrySatisfiesRatio } from './utils';
import { calculateIsIntersecting } from './utils';

import { Frame, ElementScheduler, generateToken } from './metal/index';

Expand Down Expand Up @@ -63,7 +63,7 @@ export class SpanielIntersectionObserver implements IntersectionObserver {
public thresholds: number[];
private records: { [index: string]: EntryEvent };

observe(target: Element) {
observe(target: HTMLElement) {
let trackedTarget = target as SpanielTrackedElement;

let id = (trackedTarget.__spanielId = trackedTarget.__spanielId || generateToken());
Expand All @@ -77,7 +77,7 @@ export class SpanielIntersectionObserver implements IntersectionObserver {
);
return id;
}
private onTick(frame: Frame, id: string, bcr: DOMRectReadOnly, el: Element) {
private onTick(frame: Frame, id: string, bcr: DOMRectReadOnly, el: SpanielTrackedElement) {
let { numSatisfiedThresholds, entry } = this.generateEntryEvent(frame, bcr, el);
let record: EntryEvent =
this.records[id] ||
Expand All @@ -86,8 +86,12 @@ export class SpanielIntersectionObserver implements IntersectionObserver {
numSatisfiedThresholds: 0
});

if (numSatisfiedThresholds !== record.numSatisfiedThresholds) {
if (
numSatisfiedThresholds !== record.numSatisfiedThresholds ||
entry.isIntersecting !== record.entry.isIntersecting
) {
record.numSatisfiedThresholds = numSatisfiedThresholds;
record.entry = entry;
this.scheduler.scheduleWork(() => {
this.callback([entry]);
});
Expand All @@ -104,13 +108,13 @@ export class SpanielIntersectionObserver implements IntersectionObserver {
takeRecords(): IntersectionObserverEntry[] {
return [];
}
private generateEntryEvent(frame: Frame, bcr: DOMRectReadOnly, el: Element): EntryEvent {
private generateEntryEvent(frame: Frame, bcr: DOMRectReadOnly, el: HTMLElement): EntryEvent {
let count: number = 0;
let entry = generateEntry(frame, bcr, el, this.rootMarginObj);

for (let i = 0; i < this.thresholds.length; i++) {
let threshold = this.thresholds[i];
if (entrySatisfiesRatio(entry, threshold)) {
if (entry.intersectionRatio >= threshold) {
count++;
}
}
Expand Down Expand Up @@ -149,7 +153,7 @@ function addRatio(entryInit: SpanielIntersectionObserverEntryInit): Intersection
intersectionRect,
target,
intersectionRatio,
isIntersecting: null
isIntersecting: calculateIsIntersecting({ intersectionRect })
};
}

Expand Down Expand Up @@ -179,12 +183,36 @@ export class IntersectionObserverEntry implements IntersectionObserverEntryInit
};
*/

function emptyRect(): ClientRect | DOMRect {
return {
bottom: 0,
height: 0,
left: 0,
right: 0,
top: 0,
width: 0,
x: 0,
y: 0
};
}

export function generateEntry(
frame: Frame,
bcr: DOMRectReadOnly,
el: Element,
el: HTMLElement,
rootMargin: DOMMargin
): IntersectionObserverEntry {
if (el.style.display === 'none') {
return {
boundingClientRect: emptyRect(),
intersectionRatio: 0,
intersectionRect: emptyRect(),
isIntersecting: false,
rootBounds: emptyRect(),
target: el,
time: frame.timestamp
};
}
let { bottom, right } = bcr;
let rootBounds: ClientRect = {
left: frame.x - rootMargin.left,
Expand Down
7 changes: 4 additions & 3 deletions src/native-spaniel-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/

import { entrySatisfiesRatio } from './utils';
import { calculateIsIntersecting } from './utils';

import {
IntersectionObserverInit,
Expand Down Expand Up @@ -187,9 +187,10 @@ export class SpanielObserver implements SpanielObserverInterface {
let hasTimeThreshold = !!state.threshold.time;
let spanielEntry: SpanielObserverEntry = this.generateSpanielEntry(entry, state);

const ratioSatisfied = entrySatisfiesRatio(entry, state.threshold.ratio);
const ratioSatisfied = entry.intersectionRatio >= state.threshold.ratio;
const isIntersecting = calculateIsIntersecting(entry);

if (ratioSatisfied && !state.lastSatisfied) {
if (ratioSatisfied && !state.lastSatisfied && isIntersecting) {
spanielEntry.entering = true;
if (hasTimeThreshold) {
state.lastVisible = time;
Expand Down
7 changes: 4 additions & 3 deletions src/spaniel-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*/

import { entrySatisfiesRatio } from './utils';
import { calculateIsIntersecting } from './utils';

import { SpanielIntersectionObserver } from './intersection-observer';

Expand Down Expand Up @@ -192,9 +192,10 @@ export class SpanielObserver implements SpanielObserverInterface {
let hasTimeThreshold = !!state.threshold.time;
let spanielEntry: SpanielObserverEntry = this.generateSpanielEntry(entry, state);

const ratioSatisfied = entrySatisfiesRatio(entry, state.threshold.ratio);
const ratioSatisfied = entry.intersectionRatio >= state.threshold.ratio;
const isIntersecting = calculateIsIntersecting(entry);

if (ratioSatisfied && !state.lastSatisfied) {
if (ratioSatisfied && !state.lastSatisfied && isIntersecting) {
spanielEntry.entering = true;
if (hasTimeThreshold) {
state.lastVisible = time;
Expand Down
17 changes: 2 additions & 15 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
export function entrySatisfiesRatio(entry: IntersectionObserverEntry, threshold: number) {
let { boundingClientRect, intersectionRatio } = entry;

// Edge case where item has no actual area
if (boundingClientRect.width === 0 || boundingClientRect.height === 0) {
let { boundingClientRect, intersectionRect } = entry;
return (
boundingClientRect.left === intersectionRect.left &&
boundingClientRect.top === intersectionRect.top &&
intersectionRect.width >= 0 &&
intersectionRect.height >= 0
);
} else {
return intersectionRatio > threshold || (intersectionRatio === 1 && threshold === 1);
}
export function calculateIsIntersecting({ intersectionRect }: { intersectionRect: ClientRect }) {
return intersectionRect.width > 0 || intersectionRect.height > 0;
}
1 change: 1 addition & 0 deletions test/headless/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ server.stdout.on('data', data => {
'--require',
'@babel/register',
'test/headless/specs/**/*.js',
'test/headless/specs/*.js',
'--exit',
'--timeout',
'5000'
Expand Down
Loading

0 comments on commit 80e1864

Please sign in to comment.