W3docs

Scrolling in JavaScript

Gli eventi di scrolling in JavaScript permettono di reagire allo scorrimento della pagina, utili per lazy loading e animazioni.

Capire gli eventi e le tecniche di scrolling in JavaScript

Gli eventi di scrolling in JavaScript ti permettono di reagire alla posizione dell'utente sulla pagina. Questa è la base per funzionalità come gli effetti parallax, l'attivazione di animazioni in base alla posizione di scorrimento, l'implementazione dello scroll infinito, i pulsanti "torna in cima", le intestazioni fisse e le barre di avanzamento della lettura. Questa guida spiega come leggere la posizione di scorrimento corrente, come scorrere in modo programmatico, come gestire l'evento scroll in modo efficiente e quando utilizzare il moderno IntersectionObserver.

Leggere la posizione di scorrimento corrente

Prima di reagire allo scrolling, di solito è necessario sapere di quanto è scorsa la pagina. Le proprietà più affidabili si trovano sull'oggetto window:

  • window.scrollY — distanza di scorrimento verticale in pixel (anche window.pageYOffset, un alias più vecchio).
  • window.scrollX — distanza di scorrimento orizzontale in pixel (alias window.pageXOffset).

Per un singolo elemento scorrevole, usa invece element.scrollTop e element.scrollLeft. Questi valori sono in lettura/scrittura: assegnando un valore l'elemento viene portato direttamente a quella posizione.

// How far down the whole page has the user scrolled?
console.log(window.scrollY); // e.g. 0 at the top, 420 partway down

// Total scrollable height of the document
const docHeight = document.documentElement.scrollHeight;
const winHeight = window.innerHeight;

// How close to the bottom are we (0 = top, 1 = bottom)?
const progress = window.scrollY / (docHeight - winHeight);
console.log(progress);

Per gli offset relativi agli elementi e un approfondimento su getBoundingClientRect, consulta JavaScript Coordinates e Window Sizes and Scrolling.

Scorrere in modo programmatico

Non si ascolta solo lo scrolling — lo si può anche attivare. Questi metodi accettano l'opzione behavior: 'smooth' per una transizione animata invece di un salto istantaneo:

  • window.scrollTo(x, y) — scorre fino a una posizione assoluta.
  • window.scrollBy(dx, dy) — scorre di una quantità relativa rispetto alla posizione corrente.
  • element.scrollIntoView(options) — scorre in modo che un elemento specifico sia visibile.
// Jump to the very top, smoothly
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });

// Nudge down by one viewport height
window.scrollBy({ top: window.innerHeight, behavior: 'smooth' });

// Bring an element into view (e.g. after navigating to an anchor)
document.querySelector('#section-2')
  .scrollIntoView({ behavior: 'smooth', block: 'start' });

Questo è il modo corretto per implementare un pulsante "torna in cima" o una navigazione fluida verso ancore nella pagina. Usa querySelector per ottenere prima l'elemento di destinazione.

L'evento scroll in JavaScript

L'evento scroll si attiva quando viene scrollata la visualizzazione del documento o un elemento scorrevole. È uno degli eventi più utilizzati per design dinamici e interattivi.

Concetti chiave

  • Frequenza degli eventi: L'evento scroll può attivarsi decine di volte al secondo, quindi il suo handler viene eseguito molto spesso. Eseguire operazioni pesanti (letture del layout, scritture nel DOM, chiamate di rete) a ogni attivazione causa uno scrolling discontinuo. La soluzione standard è throttle o debounce dell'handler.
  • Scrolling su window vs. elemento: Puoi ascoltare lo scroll sull'intera finestra (window.addEventListener('scroll', ...)) o su un elemento specifico con overflow scorrevole (el.addEventListener('scroll', ...)).
  • scroll non fa bubble: Un evento scroll su un elemento non risale al documento, quindi collega il listener all'elemento che scorre effettivamente.

Throttle vs. Debounce

Entrambi limitano la frequenza di esecuzione del tuo handler, ma si comportano in modo diverso:

  • Debounce aspetta che lo scrolling si fermi per wait ms, poi esegue una sola volta. Ideale per "fare qualcosa quando l'utente finisce di scorrere" (es. salvare la posizione di scorrimento).
  • Throttle esegue al massimo una volta ogni wait ms durante lo scrolling. Migliore per effetti continui come le barre di avanzamento, dove si desiderano aggiornamenti regolari durante lo scorrimento.
function debounce(func, wait) {
  let timeout;
  return function (...args) {
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(this, args), wait);
  };
}

function throttle(func, wait) {
  let last = 0;
  return function (...args) {
    const now = Date.now();
    if (now - last >= wait) {
      last = now;
      func.apply(this, args);
    }
  };
}

// Debounce: runs once 100ms after scrolling stops
window.addEventListener('scroll', debounce(() => {
  // handle scroll logic here
}, 100));

// Throttle: runs at most every 100ms while scrolling
window.addEventListener('scroll', throttle(() => {
  // update a progress bar, etc.
}, 100));

Entrambi gli helper basati su setTimeout fanno uso delle API di scheduling setTimeout / setInterval.

Esempi pratici di gestione degli eventi scroll

Esempio 1: Mostra/nascondi la navigazione allo scroll

Questo esempio mostra come nascondere una barra di navigazione quando si scorre verso il basso e mostrarla di nuovo quando si scorre verso l'alto, un pattern comune in molti siti moderni per massimizzare lo spazio sullo schermo.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Scroll Event Navigation Example</title>
    <style>
      #navbar {
        position: fixed;
        top: 0;
        width: 100%;
        background-color: #333;
        color: white;
        text-align: center;
        padding: 10px;
        transition: top 0.3s;
      }
      body {
        padding: 0;
        margin: 0;
        height: 1500px; /* to ensure scrolling */
        font-family: Arial, sans-serif;
      }
    </style>
  </head>
  <body>
    <p style="display: flex; justify-content: center; align-items: center; margin-top: 50vh;"><strong>When you scroll down, the navigation bar disappears. Scroll back up, and it reappears!</strong></p>

    <div id="navbar">Navigation Bar</div>

    <script>
      let lastScrollTop = 0;
      window.addEventListener(
        "scroll",
        function () {
          let currentScroll = window.pageYOffset || document.documentElement.scrollTop;
          if (currentScroll > lastScrollTop) {
            document.getElementById("navbar").style.top = "-50px"; // Adjust based on nav height
          } else {
            document.getElementById("navbar").style.top = "0px";
          }
          lastScrollTop = currentScroll <= 0 ? 0 : currentScroll; // For Mobile or negative scrolling
        },
        false
      );
    </script>
  </body>
</html>

Spiegazione:

  • Lo script tiene traccia dell'ultima posizione di scorrimento e la confronta con quella corrente. Se la posizione corrente è più alta, significa che l'utente sta scorrendo verso il basso, quindi la barra di navigazione viene nascosta spostando la sua posizione top fuori dallo schermo.
  • Scorrendo verso l'alto, la barra di navigazione riappare.

Esempio 2: Attivazione di animazioni allo scroll

Questo esempio mostra come attivare animazioni quando gli elementi entrano nel viewport durante lo scrolling.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Scroll Animation Trigger</title>
    <style>
        .box {
            width: 100px;
            height: 100px;
            background: red;
            opacity: 0;
            transition: opacity 2s;
            margin: 600px auto;  /* Ensures it starts out of view */
        }
    </style>
</head>
<body>
<p>Keep scrolling down to see the animation!</p>
<div class="box"></div>

<script>
    let hasAnimated = false;
    window.addEventListener('scroll', function() {
        const box = document.querySelector('.box');
        const rect = box.getBoundingClientRect();
        
        if (rect.top < window.innerHeight && !hasAnimated) {
            box.style.opacity = 1;  // Fade in the box when it comes into view
            hasAnimated = true;
        }
    });
</script>
</body>
</html>

Spiegazione:

  • Controllo visibilità: Lo script verifica se il bordo superiore dell'elemento .box è all'interno del viewport e cambia la sua opacità a 1, attivando un effetto di dissolvenza in entrata. Un flag impedisce che l'animazione si attivi nuovamente agli eventi scroll successivi.

Esempio 3: Effetto parallax allo scroll

Questo esempio dimostra un semplice effetto parallax in cui l'immagine di sfondo si muove a una velocità diversa rispetto al contenuto in primo piano mentre si scorre la pagina.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Enhanced Parallax Scrolling</title>
    <style>
        body, html {
            height: 100%;
            margin: 0;
            font-family: Arial, sans-serif;
            overflow-x: hidden; /* Prevent horizontal scroll */
        }
        .parallax {
            height: 100vh; /* Full height of the viewport */
            position: relative;
            background: url('https://via.placeholder.com/1920x1080') no-repeat center center; 
            background-size: cover;
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            font-size: 36px;
            letter-spacing: 1px;
        }
        .content {
            height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            background: white;
            color: #333;
            font-size: 24px;
            padding: 0 20px;
            text-align: center;
            box-sizing: border-box;
            border-top: 1px solid #ccc;
            border-bottom: 1px solid #ccc;
        }
    </style>
</head>
<body>

<div class="content">Scroll down to see the parallax effect!</div>
<div class="parallax">Stunning Parallax!</div>
<div class="content">Keep scrolling to see more effects.</div>
<div class="parallax"></div>
<div class="content">You have reached the end. Amazing, right?</div>

<script>
    document.addEventListener('scroll', function() {
        document.querySelectorAll('.parallax').forEach(function(el) {
            const factor = 0.5; // Change this for more or less parallax
            const offset = window.pageYOffset * factor - 300; // Adjusts the starting position of background
            el.style.backgroundPositionY = offset + 'px';
        });
    });
</script>

</body>
</html>

Spiegazione:

  1. Stili CSS: La classe .parallax imposta l'immagine di sfondo in modo che riempia il contenitore e la centra. L'esempio si affida interamente a JavaScript per il posizionamento, evitando il CSS background-attachment: fixed, che può causare problemi di prestazioni sui dispositivi mobili.
  2. Funzionalità JavaScript: Allo scroll, lo script calcola una nuova posizione verticale per l'immagine di sfondo a partire dall'offset di scorrimento. Regolando dinamicamente backgroundPositionY, l'immagine si sposta a una velocità diversa rispetto al contenuto della pagina, creando l'effetto di profondità parallax.

In sintesi, man mano che si scorre, le immagini di sfondo si muovono più lentamente del testo, facendo sembrare che si trovino a una profondità diversa.

L'alternativa moderna: IntersectionObserver

Per il caso comune di "fare qualcosa quando un elemento diventa visibile" (lazy loading, animazioni di dissolvenza, scroll infinito), IntersectionObserver è l'approccio moderno consigliato. Invece di eseguire il codice a ogni evento scroll e leggere manualmente le posizioni, il browser ti notifica in modo asincrono quando un elemento supera una soglia — molto più efficiente e privo di scatti.

const observer = new IntersectionObserver(
  (entries) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        entry.target.classList.add('visible'); // run once it enters view
        observer.unobserve(entry.target);      // stop watching it
      }
    });
  },
  { threshold: 0.25 } // fire when 25% of the element is visible
);

document.querySelectorAll('.box').forEach((el) => observer.observe(el));

Usa l'evento scroll quando hai bisogno di una lettura continua della posizione di scorrimento (parallax, barre di avanzamento). Usa IntersectionObserver quando ti interessa solo che un elemento sia entrato o uscito dal viewport. È concettualmente simile all'API MutationObserver.

Impedire e ripristinare lo scroll

Un requisito comune dell'interfaccia utente è bloccare lo scroll della pagina mentre un modale o un menu è aperto. Non è possibile annullare in modo affidabile l'evento scroll con preventDefault() (si attiva dopo che lo scorrimento è già avvenuto). Invece, è sufficiente alternare il CSS overflow:

// Lock scrolling (e.g. when opening a modal)
document.body.style.overflow = 'hidden';

// Restore it when the modal closes
document.body.style.overflow = '';

Conclusione

Gestire gli eventi scroll in modo efficace è una competenza fondamentale per gli sviluppatori web, che consente di creare siti più interattivi e ottimizzati per le prestazioni. Che si stia implementando miglioramenti all'interfaccia utente come barre di navigazione dinamiche o effetti parallax sulla pagina, comprendere e gestire correttamente lo scrolling in JavaScript può migliorare drasticamente l'esperienza utente.

Esercizi

Pratica
Quali azioni sono usi appropriati dell'evento scroll JavaScript nello sviluppo web?
Quali azioni sono usi appropriati dell'evento scroll JavaScript nello sviluppo web?
Was this page helpful?