-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Many IntersectObserver object created #22
Comments
Hey @izderadicka thanks for that insight. I'll take a look and get back to you :) |
Quick and dirty fix. export const inview = (node: HTMLElement, options = {}) => {
//adapted from https://github.com/maciekgrzybek/svelte-inview
let defaultOptions = {
root: null,
rootMargin: '0px',
threshold: 0,
unobserveOnEnter: false,
}
const { root, rootMargin, threshold, unobserveOnEnter } = {
...defaultOptions,
...options,
};
let prevPos = {
x: undefined,
y: undefined,
};
let scrollDirection: ScrollDirection = {
vertical: undefined,
horizontal: undefined,
};
if (typeof IntersectionObserver !== 'undefined' && node) {
// Global variable to hold observers;
window._inview_observers = window._inview_observers || {};
// Unique id for each observer (defaults to root for viewport)
let observerid = "root";
if (root != null) {
observerid = root.getAttribute("data-svelte-inview-id");
if (!observerid) {
observerid = "_" + Math.floor(Math.random() * 100000);
root.setAttribute("data-svelte-inview-id", observerid);
}
}
window._inview_observers[observerid] = window._inview_observers[observerid] || new IntersectionObserver(
(entries, _observer) => {
entries.forEach((singleEntry) => {
if (prevPos.y > singleEntry.boundingClientRect.y) {
scrollDirection.vertical = 'up';
} else {
scrollDirection.vertical = 'down';
}
if (prevPos.x > singleEntry.boundingClientRect.x) {
scrollDirection.horizontal = 'left';
} else {
scrollDirection.horizontal = 'right';
}
prevPos = {
y: singleEntry.boundingClientRect.y,
x: singleEntry.boundingClientRect.x,
};
const detail = {
inView: singleEntry.isIntersecting,
entry: singleEntry,
scrollDirection,
node: singleEntry.target,
observer: _observer,
};
singleEntry.target.dispatchEvent(new CustomEvent('change', { detail }));
if (singleEntry.isIntersecting) {
singleEntry.target.dispatchEvent(new CustomEvent('enter', { detail }));
unobserveOnEnter && _observer.unobserve(node);
} else {
singleEntry.target.dispatchEvent(new CustomEvent('leave', { detail }));
}
});
},
{
root,
rootMargin,
threshold,
}
);
// This dispatcher has to be wrapped in setTimeout, as it won't work otherwise.
// Not sure why is it happening, maybe a callstack has to pass between the listeners?
// Definitely something to investigate to understand better.
setTimeout(() => {
node.dispatchEvent(
new CustomEvent('init', { detail: { observer: window._inview_observers[observerid], node } })
);
}, 0);
window._inview_observers[observerid].observe(node);
return {
destroy() {
window._inview_observers[observerid].unobserve(node);
},
};
}
} |
@brahma-dev it is A solution, but definitely not THE solution :) Keeping things in a global variable doesn't sound like good idea to me, although it might be a solution for a quick fix :) |
@maciekgrzybek here's an improved version of @brahma-dev 's code that creates a single store that holds all observers. It appears to behave properly. import { tick } from 'svelte';
import { writable, get, type Readable } from 'svelte/store';
import type {
ObserverEventDetails,
Options,
Position,
ScrollDirection,
Event,
LifecycleEventDetails,
} from './types';
export function createMapStore<T>(initialValue: Record<string, T> = {}) : Readable<Record<string, T>> & {
get: (k:string) => T;
set: (key:string, value:T) => void;
remove: (k:string) => void;
update: (fn: (m:Record<string, T>) => Record<string, T>) => void;
} {
const store = writable(initialValue);
return {
subscribe: store.subscribe,
update: store.update,
get: (k:string) => get(store)[k],
set: (key:string, value:T) => store.update(m => Object.assign({}, m, {[key]: value})),
remove(k:string) {
store.update(s => {
delete s[k];
return s;
});
},
}
}
// Store to hold 'global' observers
export const inview_observers = createMapStore<IntersectionObserver>({});
export default function inview (node: HTMLElement, options:Options = {}){
//adapted from https://github.com/maciekgrzybek/svelte-inview
const { root, rootMargin, threshold, unobserveOnEnter } = {
...defaultOptions,
...options,
};
let prevPos: Position = {
x: undefined,
y: undefined,
};
let scrollDirection: ScrollDirection = {
vertical: undefined,
horizontal: undefined,
};
// This ensures it's running in the browser, so we're safe to modify the shared store
if (typeof IntersectionObserver !== 'undefined' && node) {
let observerid:string = "root";
// Unique id for each observer (defaults to root for viewport)
if (root != null) {
if (!root.getAttribute("data-svelte-inview-id")) {
observerid = "_" + Math.floor(Math.random() * 100000);
root.setAttribute("data-svelte-inview-id", observerid);
}
}
if (!inview_observers.get(observerid)) inview_observers.set(observerid, new IntersectionObserver(
(entries, _observer) => {
entries.forEach((singleEntry) => {
if ((prevPos.y ?? 0) > singleEntry.boundingClientRect.y) {
scrollDirection.vertical = 'up';
} else {
scrollDirection.vertical = 'down';
}
if ((prevPos.x ?? 0) > singleEntry.boundingClientRect.x) {
scrollDirection.horizontal = 'left';
} else {
scrollDirection.horizontal = 'right';
}
prevPos = {
y: singleEntry.boundingClientRect.y,
x: singleEntry.boundingClientRect.x,
};
const detail = {
inView: singleEntry.isIntersecting,
entry: singleEntry,
scrollDirection,
node: singleEntry.target,
observer: _observer,
};
singleEntry.target.dispatchEvent(createEvent('inview_change', detail));
if (singleEntry.isIntersecting) {
singleEntry.target.dispatchEvent(createEvent('inview_enter', detail));
unobserveOnEnter && _observer.unobserve(node);
} else {
singleEntry.target.dispatchEvent(createEvent('inview_leave', detail));
}
});
},
{
root,
rootMargin,
threshold,
}
))
inview_observers.get(observerid).observe(node);
tick().then(() => {
node.dispatchEvent(
createEvent('inview_init', { detail: { observer: inview_observers.get(observerid), node } })
);
});
return {
destroy() {
if (inview_observers.get(observerid)) {
inview_observers.get(observerid)?.unobserve(node);
inview_observers.remove(observerid);
}
},
}
} else {
return {
destroy() {},
}
}
} |
@rgon that looks great, can you prepare a PR for that? |
I have been looking into code and if I understand code correctly new
IntersectObserver
is created for every component, that has inview action.My scenario has one root element which has many (thousands) of child elements/components , which lazy load an image, when they get into view.
As per this article: https://www.bennadel.com/blog/3954-intersectionobserver-api-performance-many-vs-shared-in-angular-11-0-5.htm it looks that there is notable performance advantage for single
IntersectObserver
having many observed elements, comparing to manyIntersectObservers
, each one just with one observed element.What do you think?
The text was updated successfully, but these errors were encountered: