Skip to content
Advertisement

Capturing product impression without making the website slow

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);
    }
Advertisement