I am working on a website where we have thousands of products. I have to capture the impression of all products users can see in their viewport. So I created a directory
and I used IntersectionObserver
, and referred to it inside the HTML
code of that product. The issue is that it’s causing a performing impact on the mobile site as soon as the user scrolls. How can I capture impressions without slowing my website?
export class IntersectionObserverDirective implements OnDestroy, OnInit, AfterViewInit { @Input() debounceTime = 0; @Input() threshold = 1; @Output() visible = new EventEmitter<HTMLElement>(); isSSR: boolean = typeof window === 'undefined'; private observer: IntersectionObserver | undefined; private subject$ = new Subject<{ entry: IntersectionObserverEntry; observer: IntersectionObserver; }>(); constructor(private element: ElementRef) {} ngOnInit() { this.createObserver(); } ngAfterViewInit() { this.startObservingElements(); } ngOnDestroy() { if (this.observer) { this.observer.disconnect(); this.observer = undefined; } this.subject$.next(); this.subject$.complete(); } private isVisible(element: HTMLElement) { return new Promise(resolve => { const observer = new IntersectionObserver(([entry]) => { resolve(entry.intersectionRatio === 1); observer.disconnect(); }); observer.observe(element); }); } private createObserver() { const options = { rootMargin: '0px', threshold: this.threshold, }; const isIntersecting = (entry: IntersectionObserverEntry) => entry.isIntersecting || entry.intersectionRatio > 0; this.observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (isIntersecting(entry)) { this.subject$.next({ entry, observer }); } }); }, options); } private startObservingElements() { if (!this.observer) { return; } this.observer.observe(this.element.nativeElement); this.subject$ .pipe(delay(this.debounceTime), filter(Boolean)) .subscribe(async ({ entry, observer }) => { const target = entry.target as HTMLElement; const isStillVisible = await this.isVisible(target); if (isStillVisible) { this.visible.emit(target); observer.unobserve(target); } }); } }
Advertisement
Answer
Just add a timeout in createObserver()
. Something like this.
private createObserver() { const options = { rootMargin: '0px', threshold: this.threshold, }; let timeouts = {}; const isIntersecting = (entry: IntersectionObserverEntry) => entry.isIntersecting || entry.intersectionRatio > 0; this.observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (isIntersecting(entry)) { timeouts[entry.target.id] = setTimeout(() => { this.subject.next({ entry, observer }); }, 500); } else { clearTimeout(timeouts[entry.target.id]) } }); }, options); }