Skip to content

Commit 80e1864

Browse files
committed
Handle zero-area and display: none cases
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
1 parent b754bab commit 80e1864

10 files changed

+656
-522
lines changed

src/index.ts

+2-4
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1111

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

14-
import { entrySatisfiesRatio } from './utils';
15-
1614
import { SpanielTrackedElement, DOMMargin, IntersectionObserverClass } from './interfaces';
1715

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

4846
export function elementSatisfiesRatio(
49-
el: Element,
47+
el: HTMLElement,
5048
ratio: number = 0,
5149
callback: (result: Boolean) => void,
5250
rootMargin: DOMMargin = { top: 0, bottom: 0, left: 0, right: 0 }
5351
) {
5452
queryElement(el, (bcr: ClientRect, frame: Frame) => {
5553
let entry = generateEntry(frame, bcr, el, rootMargin);
56-
callback(entrySatisfiesRatio(entry, ratio));
54+
callback(entry.isIntersecting && entry.intersectionRatio >= ratio);
5755
});
5856
}

src/interfaces.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
99
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1010
*/
1111

12-
export interface SpanielTrackedElement extends Element {
12+
export interface SpanielTrackedElement extends HTMLElement {
1313
__spanielId: string;
1414
}
1515

src/intersection-observer.ts

+36-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
99
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1010
*/
1111

12-
import { entrySatisfiesRatio } from './utils';
12+
import { calculateIsIntersecting } from './utils';
1313

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

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

66-
observe(target: Element) {
66+
observe(target: HTMLElement) {
6767
let trackedTarget = target as SpanielTrackedElement;
6868

6969
let id = (trackedTarget.__spanielId = trackedTarget.__spanielId || generateToken());
@@ -77,7 +77,7 @@ export class SpanielIntersectionObserver implements IntersectionObserver {
7777
);
7878
return id;
7979
}
80-
private onTick(frame: Frame, id: string, bcr: DOMRectReadOnly, el: Element) {
80+
private onTick(frame: Frame, id: string, bcr: DOMRectReadOnly, el: SpanielTrackedElement) {
8181
let { numSatisfiedThresholds, entry } = this.generateEntryEvent(frame, bcr, el);
8282
let record: EntryEvent =
8383
this.records[id] ||
@@ -86,8 +86,12 @@ export class SpanielIntersectionObserver implements IntersectionObserver {
8686
numSatisfiedThresholds: 0
8787
});
8888

89-
if (numSatisfiedThresholds !== record.numSatisfiedThresholds) {
89+
if (
90+
numSatisfiedThresholds !== record.numSatisfiedThresholds ||
91+
entry.isIntersecting !== record.entry.isIntersecting
92+
) {
9093
record.numSatisfiedThresholds = numSatisfiedThresholds;
94+
record.entry = entry;
9195
this.scheduler.scheduleWork(() => {
9296
this.callback([entry]);
9397
});
@@ -104,13 +108,13 @@ export class SpanielIntersectionObserver implements IntersectionObserver {
104108
takeRecords(): IntersectionObserverEntry[] {
105109
return [];
106110
}
107-
private generateEntryEvent(frame: Frame, bcr: DOMRectReadOnly, el: Element): EntryEvent {
111+
private generateEntryEvent(frame: Frame, bcr: DOMRectReadOnly, el: HTMLElement): EntryEvent {
108112
let count: number = 0;
109113
let entry = generateEntry(frame, bcr, el, this.rootMarginObj);
110114

111115
for (let i = 0; i < this.thresholds.length; i++) {
112116
let threshold = this.thresholds[i];
113-
if (entrySatisfiesRatio(entry, threshold)) {
117+
if (entry.intersectionRatio >= threshold) {
114118
count++;
115119
}
116120
}
@@ -149,7 +153,7 @@ function addRatio(entryInit: SpanielIntersectionObserverEntryInit): Intersection
149153
intersectionRect,
150154
target,
151155
intersectionRatio,
152-
isIntersecting: null
156+
isIntersecting: calculateIsIntersecting({ intersectionRect })
153157
};
154158
}
155159

@@ -179,12 +183,36 @@ export class IntersectionObserverEntry implements IntersectionObserverEntryInit
179183
};
180184
*/
181185

186+
function emptyRect(): ClientRect | DOMRect {
187+
return {
188+
bottom: 0,
189+
height: 0,
190+
left: 0,
191+
right: 0,
192+
top: 0,
193+
width: 0,
194+
x: 0,
195+
y: 0
196+
};
197+
}
198+
182199
export function generateEntry(
183200
frame: Frame,
184201
bcr: DOMRectReadOnly,
185-
el: Element,
202+
el: HTMLElement,
186203
rootMargin: DOMMargin
187204
): IntersectionObserverEntry {
205+
if (el.style.display === 'none') {
206+
return {
207+
boundingClientRect: emptyRect(),
208+
intersectionRatio: 0,
209+
intersectionRect: emptyRect(),
210+
isIntersecting: false,
211+
rootBounds: emptyRect(),
212+
target: el,
213+
time: frame.timestamp
214+
};
215+
}
188216
let { bottom, right } = bcr;
189217
let rootBounds: ClientRect = {
190218
left: frame.x - rootMargin.left,

src/native-spaniel-observer.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
99
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1010
*/
1111

12-
import { entrySatisfiesRatio } from './utils';
12+
import { calculateIsIntersecting } from './utils';
1313

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

190-
const ratioSatisfied = entrySatisfiesRatio(entry, state.threshold.ratio);
190+
const ratioSatisfied = entry.intersectionRatio >= state.threshold.ratio;
191+
const isIntersecting = calculateIsIntersecting(entry);
191192

192-
if (ratioSatisfied && !state.lastSatisfied) {
193+
if (ratioSatisfied && !state.lastSatisfied && isIntersecting) {
193194
spanielEntry.entering = true;
194195
if (hasTimeThreshold) {
195196
state.lastVisible = time;

src/spaniel-observer.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ distributed under the License is distributed on an "AS IS" BASIS,
99
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1010
*/
1111

12-
import { entrySatisfiesRatio } from './utils';
12+
import { calculateIsIntersecting } from './utils';
1313

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

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

195-
const ratioSatisfied = entrySatisfiesRatio(entry, state.threshold.ratio);
195+
const ratioSatisfied = entry.intersectionRatio >= state.threshold.ratio;
196+
const isIntersecting = calculateIsIntersecting(entry);
196197

197-
if (ratioSatisfied && !state.lastSatisfied) {
198+
if (ratioSatisfied && !state.lastSatisfied && isIntersecting) {
198199
spanielEntry.entering = true;
199200
if (hasTimeThreshold) {
200201
state.lastVisible = time;

src/utils.ts

+2-15
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,3 @@
1-
export function entrySatisfiesRatio(entry: IntersectionObserverEntry, threshold: number) {
2-
let { boundingClientRect, intersectionRatio } = entry;
3-
4-
// Edge case where item has no actual area
5-
if (boundingClientRect.width === 0 || boundingClientRect.height === 0) {
6-
let { boundingClientRect, intersectionRect } = entry;
7-
return (
8-
boundingClientRect.left === intersectionRect.left &&
9-
boundingClientRect.top === intersectionRect.top &&
10-
intersectionRect.width >= 0 &&
11-
intersectionRect.height >= 0
12-
);
13-
} else {
14-
return intersectionRatio > threshold || (intersectionRatio === 1 && threshold === 1);
15-
}
1+
export function calculateIsIntersecting({ intersectionRect }: { intersectionRect: ClientRect }) {
2+
return intersectionRect.width > 0 || intersectionRect.height > 0;
163
}

test/headless/run.js

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ server.stdout.on('data', data => {
1616
'--require',
1717
'@babel/register',
1818
'test/headless/specs/**/*.js',
19+
'test/headless/specs/*.js',
1920
'--exit',
2021
'--timeout',
2122
'5000'

0 commit comments

Comments
 (0)