@thejlifex/ngx-scroll-spy
Version:
An Angular library that can spy the position of elements (inside a scrollable container) and emit the currently active (visible) element in the viewport.
84 lines • 12.1 kB
JavaScript
import { Subject, fromEvent, debounceTime } from 'rxjs';
/**
* Encapsulates the logic for scroll spy on a scrollable element (`scrollContainer`).
*
* Scroll spy uses the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API).
*
* Supports window resizing.
*
* Inspired by:
* - https://grafikart.fr/tutoriels/scrollspy-js-page-491
* - https://codepen.io/diaphragm/pen/bGNBVxw
* - https://github.com/chefomar/angular-scroll-spy
*/
export class ScrollSpy {
constructor({ scrollContainer } = {}) {
this.spyTargetIsIntersectingSubject$ = new Subject();
/**
* Is emitted when a spy-target has transitioned into a state of intersection (`isIntersecting: true`) or out of a state of intersection (`isIntersecting: false`).
*/
this.spyTargetIsIntersecting$ = this.spyTargetIsIntersectingSubject$.asObservable();
this.spyTargets = [];
this.intersectionObserver = null;
this.resizeDebounceTime = 300;
/**
* Defines the (vertical) position of the IntersectionObserver line.
* Value between `0` (at the top) and `100` (at the bottom).
*
* @todo (For a future version of ngx-scroll-spy) This property should be configured by the user.
*/
this.offset = 20;
this.initIntersectionObserver(scrollContainer);
this.resizeSubscription = fromEvent(window, 'resize').pipe(debounceTime(this.resizeDebounceTime)).subscribe(() => this.initIntersectionObserver(scrollContainer));
}
/**
* Adds a spy-target and starts to observe that target.
*/
addTarget(spyTarget) {
this.spyTargets.push(spyTarget);
this.intersectionObserver?.observe(spyTarget.element);
}
/**
* Removes a spy-target and stops observing that target.
*/
removeTarget(spyTarget) {
this.spyTargets = this.spyTargets.filter(spyTarget => spyTarget.id !== spyTarget.id);
this.intersectionObserver?.unobserve(spyTarget.element);
}
/**
* Stops observing all spy-targets and completes spyTargetIsIntersecting$ subject.
*/
stopSpying() {
this.resizeSubscription?.unsubscribe();
this.intersectionObserver?.disconnect();
this.intersectionObserver = null;
this.spyTargets = [];
this.spyTargetIsIntersectingSubject$.complete();
}
/**
* Initialize IntersectionObserver.
*/
initIntersectionObserver(scrollContainer) {
this.intersectionObserver?.disconnect();
this.intersectionObserver = new IntersectionObserver((entries) => this.intersectionObserverCallback(entries), {
root: scrollContainer,
rootMargin: `-${this.offset}% 0px -${100 - this.offset}% 0px` // IntersectionObserver as one line.
});
for (const spyTarget of this.spyTargets) {
this.intersectionObserver.observe(spyTarget.element);
}
}
/**
* A function which is called when one or more spy-targets have transitioned into a state of intersection (`isIntersecting: true`) or out of a state of intersection (`isIntersecting: false`).
*/
intersectionObserverCallback(entries) {
for (const entry of entries) {
const spyTarget = this.spyTargets.find((item) => item.element === entry.target);
this.spyTargetIsIntersectingSubject$.next({
spyTarget,
isIntersecting: entry.isIntersecting
});
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2Nyb2xsLXNweS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL25neC1zY3JvbGwtc3B5L3NyYy9saWIvc2Nyb2xsLXNweS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsT0FBTyxFQUFnQixTQUFTLEVBQUUsWUFBWSxFQUFjLE1BQU0sTUFBTSxDQUFDO0FBS2xGOzs7Ozs7Ozs7OztHQVdHO0FBQ0gsTUFBTSxPQUFPLFNBQVM7SUFtQnBCLFlBQVksRUFBRSxlQUFlLEtBQXVCLEVBQUU7UUFqQnJDLG9DQUErQixHQUEwQyxJQUFJLE9BQU8sRUFBZ0MsQ0FBQztRQUN0STs7V0FFRztRQUNNLDZCQUF3QixHQUE2QyxJQUFJLENBQUMsK0JBQStCLENBQUMsWUFBWSxFQUFFLENBQUM7UUFFMUgsZUFBVSxHQUFnQixFQUFFLENBQUM7UUFDN0IseUJBQW9CLEdBQWdDLElBQUksQ0FBQztRQUNoRCx1QkFBa0IsR0FBVyxHQUFHLENBQUM7UUFDbEQ7Ozs7O1dBS0c7UUFDYyxXQUFNLEdBQVcsRUFBRSxDQUFDO1FBR25DLElBQUksQ0FBQyx3QkFBd0IsQ0FBQyxlQUFlLENBQUMsQ0FBQztRQUUvQyxJQUFJLENBQUMsa0JBQWtCLEdBQUcsU0FBUyxDQUFDLE1BQU0sRUFBRSxRQUFRLENBQUMsQ0FBQyxJQUFJLENBQ3hELFlBQVksQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsQ0FDdEMsQ0FBQyxTQUFTLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxDQUFDLHdCQUF3QixDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUM7SUFDcEUsQ0FBQztJQUVEOztPQUVHO0lBQ0gsU0FBUyxDQUFDLFNBQW9CO1FBQzVCLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ2hDLElBQUksQ0FBQyxvQkFBb0IsRUFBRSxPQUFPLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3hELENBQUM7SUFFRDs7T0FFRztJQUNILFlBQVksQ0FBQyxTQUFvQjtRQUMvQixJQUFJLENBQUMsVUFBVSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUFDLEVBQUUsS0FBSyxTQUFTLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDckYsSUFBSSxDQUFDLG9CQUFvQixFQUFFLFNBQVMsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDMUQsQ0FBQztJQUVEOztPQUVHO0lBQ0gsVUFBVTtRQUNSLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxXQUFXLEVBQUUsQ0FBQztRQUN2QyxJQUFJLENBQUMsb0JBQW9CLEVBQUUsVUFBVSxFQUFFLENBQUM7UUFDeEMsSUFBSSxDQUFDLG9CQUFvQixHQUFHLElBQUksQ0FBQztRQUNqQyxJQUFJLENBQUMsVUFBVSxHQUFHLEVBQUUsQ0FBQztRQUNyQixJQUFJLENBQUMsK0JBQStCLENBQUMsUUFBUSxFQUFFLENBQUM7SUFDbEQsQ0FBQztJQUVEOztPQUVHO0lBQ0ssd0JBQXdCLENBQUMsZUFBeUI7UUFDeEQsSUFBSSxDQUFDLG9CQUFvQixFQUFFLFVBQVUsRUFBRSxDQUFDO1FBQ3hDLElBQUksQ0FBQyxvQkFBb0IsR0FBRyxJQUFJLG9CQUFvQixDQUNsRCxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLDRCQUE0QixDQUFDLE9BQU8sQ0FBQyxFQUN2RDtZQUNFLElBQUksRUFBRSxlQUFlO1lBQ3JCLFVBQVUsRUFBRSxJQUFJLElBQUksQ0FBQyxNQUFNLFVBQVUsR0FBRyxHQUFHLElBQUksQ0FBQyxNQUFNLE9BQU8sQ0FBQyxvQ0FBb0M7U0FDbkcsQ0FDRixDQUFDO1FBQ0YsS0FBSyxNQUFNLFNBQVMsSUFBSSxJQUFJLENBQUMsVUFBVSxFQUFFO1lBQ3ZDLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1NBQ3REO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ssNEJBQTRCLENBQUMsT0FBb0M7UUFDdkUsS0FBSyxNQUFNLEtBQUssSUFBSSxPQUFPLEVBQUU7WUFDM0IsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxPQUFPLEtBQUssS0FBSyxDQUFDLE1BQU0sQ0FBRSxDQUFDO1lBQ2pGLElBQUksQ0FBQywrQkFBK0IsQ0FBQyxJQUFJLENBQUM7Z0JBQ3hDLFNBQVM7Z0JBQ1QsY0FBYyxFQUFFLEtBQUssQ0FBQyxjQUFjO2FBQ3JDLENBQUMsQ0FBQztTQUNKO0lBQ0gsQ0FBQztDQUNGIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgU3ViamVjdCwgU3Vic2NyaXB0aW9uLCBmcm9tRXZlbnQsIGRlYm91bmNlVGltZSwgT2JzZXJ2YWJsZSB9IGZyb20gJ3J4anMnO1xyXG5pbXBvcnQgeyBTY3JvbGxTcHlPcHRpb25zIH0gZnJvbSAnLi9tb2RlbHMvc2Nyb2xsLXNweS1vcHRpb25zJztcclxuaW1wb3J0IHsgU3B5VGFyZ2V0IH0gZnJvbSAnLi9tb2RlbHMvc3B5LXRhcmdldCc7XHJcbmltcG9ydCB7IFNweVRhcmdldElzSW50ZXJzZWN0aW5nRXZlbnQgfSBmcm9tICcuL21vZGVscy9zcHktdGFyZ2V0LWlzLWludGVyc2VjdGluZy1ldmVudCc7XHJcblxyXG4vKipcclxuICogRW5jYXBzdWxhdGVzIHRoZSBsb2dpYyBmb3Igc2Nyb2xsIHNweSBvbiBhIHNjcm9sbGFibGUgZWxlbWVudCAoYHNjcm9sbENvbnRhaW5lcmApLlxyXG4gKlxyXG4gKiBTY3JvbGwgc3B5IHVzZXMgdGhlIFtJbnRlcnNlY3Rpb24gT2JzZXJ2ZXIgQVBJXShodHRwczovL2RldmVsb3Blci5tb3ppbGxhLm9yZy9lbi1VUy9kb2NzL1dlYi9BUEkvSW50ZXJzZWN0aW9uX09ic2VydmVyX0FQSSkuXHJcbiAqXHJcbiAqIFN1cHBvcnRzIHdpbmRvdyByZXNpemluZy5cclxuICpcclxuICogSW5zcGlyZWQgYnk6XHJcbiAqIC0gaHR0cHM6Ly9ncmFmaWthcnQuZnIvdHV0b3JpZWxzL3Njcm9sbHNweS1qcy1wYWdlLTQ5MVxyXG4gKiAtIGh0dHBzOi8vY29kZXBlbi5pby9kaWFwaHJhZ20vcGVuL2JHTkJWeHdcclxuICogLSBodHRwczovL2dpdGh1Yi5jb20vY2hlZm9tYXIvYW5ndWxhci1zY3JvbGwtc3B5XHJcbiAqL1xyXG5leHBvcnQgY2xhc3MgU2Nyb2xsU3B5IHtcclxuXHJcbiAgcHJpdmF0ZSByZWFkb25seSBzcHlUYXJnZXRJc0ludGVyc2VjdGluZ1N1YmplY3QkOiBTdWJqZWN0PFNweVRhcmdldElzSW50ZXJzZWN0aW5nRXZlbnQ+ID0gbmV3IFN1YmplY3Q8U3B5VGFyZ2V0SXNJbnRlcnNlY3RpbmdFdmVudD4oKTtcclxuICAvKipcclxuICAgKiBJcyBlbWl0dGVkIHdoZW4gYSBzcHktdGFyZ2V0IGhhcyB0cmFuc2l0aW9uZWQgaW50byBhIHN0YXRlIG9mIGludGVyc2VjdGlvbiAoYGlzSW50ZXJzZWN0aW5nOiB0cnVlYCkgb3Igb3V0IG9mIGEgc3RhdGUgb2YgaW50ZXJzZWN0aW9uIChgaXNJbnRlcnNlY3Rpbmc6IGZhbHNlYCkuXHJcbiAgICovXHJcbiAgcmVhZG9ubHkgc3B5VGFyZ2V0SXNJbnRlcnNlY3RpbmckOiBPYnNlcnZhYmxlPFNweVRhcmdldElzSW50ZXJzZWN0aW5nRXZlbnQ+ID0gdGhpcy5zcHlUYXJnZXRJc0ludGVyc2VjdGluZ1N1YmplY3QkLmFzT2JzZXJ2YWJsZSgpO1xyXG4gIHByaXZhdGUgcmVzaXplU3Vic2NyaXB0aW9uPzogU3Vic2NyaXB0aW9uO1xyXG4gIHByaXZhdGUgc3B5VGFyZ2V0czogU3B5VGFyZ2V0W10gPSBbXTtcclxuICBwcml2YXRlIGludGVyc2VjdGlvbk9ic2VydmVyOiBJbnRlcnNlY3Rpb25PYnNlcnZlciB8IG51bGwgPSBudWxsO1xyXG4gIHByaXZhdGUgcmVhZG9ubHkgcmVzaXplRGVib3VuY2VUaW1lOiBudW1iZXIgPSAzMDA7XHJcbiAgLyoqXHJcbiAgICogRGVmaW5lcyB0aGUgKHZlcnRpY2FsKSBwb3NpdGlvbiBvZiB0aGUgSW50ZXJzZWN0aW9uT2JzZXJ2ZXIgbGluZS5cclxuICAgKiBWYWx1ZSBiZXR3ZWVuIGAwYCAoYXQgdGhlIHRvcCkgYW5kIGAxMDBgIChhdCB0aGUgYm90dG9tKS5cclxuICAgKlxyXG4gICAqIEB0b2RvIChGb3IgYSBmdXR1cmUgdmVyc2lvbiBvZiBuZ3gtc2Nyb2xsLXNweSkgVGhpcyBwcm9wZXJ0eSBzaG91bGQgYmUgY29uZmlndXJlZCBieSB0aGUgdXNlci5cclxuICAgKi9cclxuICBwcml2YXRlIHJlYWRvbmx5IG9mZnNldDogbnVtYmVyID0gMjA7XHJcblxyXG4gIGNvbnN0cnVjdG9yKHsgc2Nyb2xsQ29udGFpbmVyIH06IFNjcm9sbFNweU9wdGlvbnMgPSB7fSkge1xyXG4gICAgdGhpcy5pbml0SW50ZXJzZWN0aW9uT2JzZXJ2ZXIoc2Nyb2xsQ29udGFpbmVyKTtcclxuXHJcbiAgICB0aGlzLnJlc2l6ZVN1YnNjcmlwdGlvbiA9IGZyb21FdmVudCh3aW5kb3csICdyZXNpemUnKS5waXBlKFxyXG4gICAgICBkZWJvdW5jZVRpbWUodGhpcy5yZXNpemVEZWJvdW5jZVRpbWUpXHJcbiAgICApLnN1YnNjcmliZSgoKSA9PiB0aGlzLmluaXRJbnRlcnNlY3Rpb25PYnNlcnZlcihzY3JvbGxDb250YWluZXIpKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEFkZHMgYSBzcHktdGFyZ2V0IGFuZCBzdGFydHMgdG8gb2JzZXJ2ZSB0aGF0IHRhcmdldC5cclxuICAgKi9cclxuICBhZGRUYXJnZXQoc3B5VGFyZ2V0OiBTcHlUYXJnZXQpOiB2b2lkIHtcclxuICAgIHRoaXMuc3B5VGFyZ2V0cy5wdXNoKHNweVRhcmdldCk7XHJcbiAgICB0aGlzLmludGVyc2VjdGlvbk9ic2VydmVyPy5vYnNlcnZlKHNweVRhcmdldC5lbGVtZW50KTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIFJlbW92ZXMgYSBzcHktdGFyZ2V0IGFuZCBzdG9wcyBvYnNlcnZpbmcgdGhhdCB0YXJnZXQuXHJcbiAgICovXHJcbiAgcmVtb3ZlVGFyZ2V0KHNweVRhcmdldDogU3B5VGFyZ2V0KTogdm9pZCB7XHJcbiAgICB0aGlzLnNweVRhcmdldHMgPSB0aGlzLnNweVRhcmdldHMuZmlsdGVyKHNweVRhcmdldCA9PiBzcHlUYXJnZXQuaWQgIT09IHNweVRhcmdldC5pZCk7XHJcbiAgICB0aGlzLmludGVyc2VjdGlvbk9ic2VydmVyPy51bm9ic2VydmUoc3B5VGFyZ2V0LmVsZW1lbnQpO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogU3RvcHMgb2JzZXJ2aW5nIGFsbCBzcHktdGFyZ2V0cyBhbmQgY29tcGxldGVzIHNweVRhcmdldElzSW50ZXJzZWN0aW5nJCBzdWJqZWN0LlxyXG4gICAqL1xyXG4gIHN0b3BTcHlpbmcoKTogdm9pZCB7XHJcbiAgICB0aGlzLnJlc2l6ZVN1YnNjcmlwdGlvbj8udW5zdWJzY3JpYmUoKTtcclxuICAgIHRoaXMuaW50ZXJzZWN0aW9uT2JzZXJ2ZXI/LmRpc2Nvbm5lY3QoKTtcclxuICAgIHRoaXMuaW50ZXJzZWN0aW9uT2JzZXJ2ZXIgPSBudWxsO1xyXG4gICAgdGhpcy5zcHlUYXJnZXRzID0gW107XHJcbiAgICB0aGlzLnNweVRhcmdldElzSW50ZXJzZWN0aW5nU3ViamVjdCQuY29tcGxldGUoKTtcclxuICB9XHJcblxyXG4gIC8qKlxyXG4gICAqIEluaXRpYWxpemUgSW50ZXJzZWN0aW9uT2JzZXJ2ZXIuXHJcbiAgICovXHJcbiAgcHJpdmF0ZSBpbml0SW50ZXJzZWN0aW9uT2JzZXJ2ZXIoc2Nyb2xsQ29udGFpbmVyPzogRWxlbWVudCk6IHZvaWQge1xyXG4gICAgdGhpcy5pbnRlcnNlY3Rpb25PYnNlcnZlcj8uZGlzY29ubmVjdCgpO1xyXG4gICAgdGhpcy5pbnRlcnNlY3Rpb25PYnNlcnZlciA9IG5ldyBJbnRlcnNlY3Rpb25PYnNlcnZlcihcclxuICAgICAgKGVudHJpZXMpID0+IHRoaXMuaW50ZXJzZWN0aW9uT2JzZXJ2ZXJDYWxsYmFjayhlbnRyaWVzKSxcclxuICAgICAge1xyXG4gICAgICAgIHJvb3Q6IHNjcm9sbENvbnRhaW5lcixcclxuICAgICAgICByb290TWFyZ2luOiBgLSR7dGhpcy5vZmZzZXR9JSAwcHggLSR7MTAwIC0gdGhpcy5vZmZzZXR9JSAwcHhgIC8vIEludGVyc2VjdGlvbk9ic2VydmVyIGFzIG9uZSBsaW5lLlxyXG4gICAgICB9XHJcbiAgICApO1xyXG4gICAgZm9yIChjb25zdCBzcHlUYXJnZXQgb2YgdGhpcy5zcHlUYXJnZXRzKSB7XHJcbiAgICAgIHRoaXMuaW50ZXJzZWN0aW9uT2JzZXJ2ZXIub2JzZXJ2ZShzcHlUYXJnZXQuZWxlbWVudCk7XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBBIGZ1bmN0aW9uIHdoaWNoIGlzIGNhbGxlZCB3aGVuIG9uZSBvciBtb3JlIHNweS10YXJnZXRzIGhhdmUgdHJhbnNpdGlvbmVkIGludG8gYSBzdGF0ZSBvZiBpbnRlcnNlY3Rpb24gKGBpc0ludGVyc2VjdGluZzogdHJ1ZWApIG9yIG91dCBvZiBhIHN0YXRlIG9mIGludGVyc2VjdGlvbiAoYGlzSW50ZXJzZWN0aW5nOiBmYWxzZWApLlxyXG4gICAqL1xyXG4gIHByaXZhdGUgaW50ZXJzZWN0aW9uT2JzZXJ2ZXJDYWxsYmFjayhlbnRyaWVzOiBJbnRlcnNlY3Rpb25PYnNlcnZlckVudHJ5W10pOiB2b2lkIHtcclxuICAgIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xyXG4gICAgICBjb25zdCBzcHlUYXJnZXQgPSB0aGlzLnNweVRhcmdldHMuZmluZCgoaXRlbSkgPT4gaXRlbS5lbGVtZW50ID09PSBlbnRyeS50YXJnZXQpITtcclxuICAgICAgdGhpcy5zcHlUYXJnZXRJc0ludGVyc2VjdGluZ1N1YmplY3QkLm5leHQoe1xyXG4gICAgICAgIHNweVRhcmdldCxcclxuICAgICAgICBpc0ludGVyc2VjdGluZzogZW50cnkuaXNJbnRlcnNlY3RpbmdcclxuICAgICAgfSk7XHJcbiAgICB9XHJcbiAgfVxyXG59XHJcbiJdfQ==