๐Ÿ‘ˆ - Torna alla Wiki

Gestire le intersezioni tra elementi del DOM con Stimulus.js

Pubblicato da admin | ultima modifica Agosto 29, 2020

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>