Questo Controller di Stimulus.js permette di gestire le intersezioni tra elementi del DOM allo scroll attraverso l’aggiunta di classi nel momento in cui due elementi si intersecano.
Di seguito il codice del Controller:
import { Controller } from 'stimulus' /** * @class IntersectionManagerController * Gestisce l'intersezione allo scroll di elementi del dom attraverso il toggle di una classe. * * Funziona attraverso 3 possibili targets: * - scroller -> identifica tutti gli elementi al cui scroll possono verificarsi intersezioni (window di default). * - dependant -> tutti gli elementi il cui aspetto dipende dall'intersezione con altri elementi. * - performer -> tutti gli elementi da cui dipendono i target dependant in caso di intersezione. * * Utilizzo: * - impostare il controller su un elemento del DOM (รจ consigliato utilizzare un wrapper dell'intera pagina). * - impostare i target performer e, per ognuno di essi, inserire un data attribute che identifica il nome dell'evento che si verifica all'intersezione (data-intersection-event="nome_evento"). * - impostare i target dependant * Durante lo scroll della window (o di eventuali target scroller) i dependant, al momento dell'intersezione con un performer si ritroveranno in automatico aggiunta la classe "intersection-nome_evento". */ export default class IntersectionManagerController extends Controller { static targets = ['scroller', 'dependant', 'performer'] connect() { // ascolto lo scroll del window window.addEventListener('scroll', (e) => { this._manageScrollEvent() }) // ascolto lo scroll di eventuai scroller this.scrollerTargets.forEach((s) => { s.addEventListener('scroll', (e) => { this._manageScrollEvent() }) }) this._manageScrollEvent() } _manageScrollEvent(e) { // ottengo la lista delle dipendenze ed il loro bounding const dependants = [] this.dependantTargets.forEach(dependant => { const dependantBounding = dependant.getBoundingClientRect() if (!this._isBoundingInViewport(dependantBounding)) return dependants.push({ el: dependant, bounding: dependantBounding }) }) // controllo i performer visibili nella viewport const history = {} this.performerTargets.forEach(performer => { const performerBounding = performer.getBoundingClientRect() const performerEvent = performer.getAttribute('data-intersection-event') const performerInViewport = this._isBoundingInViewport(performerBounding) if (!history[performerEvent]) history[performerEvent] = {} // controllo eventuali intersezioni tra performer e dependant dependants.forEach((dependant, i) => { const intersectionClass = 'intersection-' + performerEvent if (performerInViewport && this._isBoundingsIntersaction(performerBounding, dependant.bounding)) { dependant.el.classList.add(intersectionClass) history[performerEvent][i] = true } else if (!history[performerEvent][i]) { dependant.el.classList.remove(intersectionClass) } }) }) } _isBoundingInViewport(bounding) { return bounding.top >= 0 || bounding.bottom > 0 } _isBoundingsIntersaction(b1, b2) { const isInside = (val, limit1, limit2) => val > limit1 && val < limit2 return isInside(b2.top, b1.top, b1.top + b1.height) || isInside(b2.bottom, b1.top, b1.bottom) } }
Esempio di HTML:
<div data-controller="intersectionManager"> <div data-target="intersectionManager.performer" data-intersection-event="nome_evento"></div> <div data-target="intersectionManager.dependant"></div> </div>