Skip to content

Commit f8f750b

Browse files
committed
WIP: 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 f8f750b

7 files changed

+403
-290
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

+35-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,7 +86,10 @@ 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;
9194
this.scheduler.scheduleWork(() => {
9295
this.callback([entry]);
@@ -104,13 +107,13 @@ export class SpanielIntersectionObserver implements IntersectionObserver {
104107
takeRecords(): IntersectionObserverEntry[] {
105108
return [];
106109
}
107-
private generateEntryEvent(frame: Frame, bcr: DOMRectReadOnly, el: Element): EntryEvent {
110+
private generateEntryEvent(frame: Frame, bcr: DOMRectReadOnly, el: HTMLElement): EntryEvent {
108111
let count: number = 0;
109112
let entry = generateEntry(frame, bcr, el, this.rootMarginObj);
110113

111114
for (let i = 0; i < this.thresholds.length; i++) {
112115
let threshold = this.thresholds[i];
113-
if (entrySatisfiesRatio(entry, threshold)) {
116+
if (entry.intersectionRatio >= threshold) {
114117
count++;
115118
}
116119
}
@@ -149,7 +152,7 @@ function addRatio(entryInit: SpanielIntersectionObserverEntryInit): Intersection
149152
intersectionRect,
150153
target,
151154
intersectionRatio,
152-
isIntersecting: null
155+
isIntersecting: calculateIsIntersecting({ intersectionRect })
153156
};
154157
}
155158

@@ -179,12 +182,36 @@ export class IntersectionObserverEntry implements IntersectionObserverEntryInit
179182
};
180183
*/
181184

185+
function emptyRect(): ClientRect | DOMRect {
186+
return {
187+
bottom: 0,
188+
height: 0,
189+
left: 0,
190+
right: 0,
191+
top: 0,
192+
width: 0,
193+
x: 0,
194+
y: 0
195+
};
196+
}
197+
182198
export function generateEntry(
183199
frame: Frame,
184200
bcr: DOMRectReadOnly,
185-
el: Element,
201+
el: HTMLElement,
186202
rootMargin: DOMMargin
187203
): IntersectionObserverEntry {
204+
if (el.style.display === 'none') {
205+
return {
206+
boundingClientRect: emptyRect(),
207+
intersectionRatio: 0,
208+
intersectionRect: emptyRect(),
209+
isIntersecting: false,
210+
rootBounds: emptyRect(),
211+
target: el,
212+
time: frame.timestamp
213+
};
214+
}
188215
let { bottom, right } = bcr;
189216
let rootBounds: ClientRect = {
190217
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 && !state.lastEntry.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 && !state.lastEntry.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
}

0 commit comments

Comments
 (0)