Scrolling Events ยท Astro Tech Blog

Scrolling Events

Scroll events let you respond when the user scrolls โ€” useful for lazy loading, infinite scroll, parallax effects, and scroll-based animations.

The Scroll Event

window.addEventListener('scroll', function() {
  console.log(window.scrollY);
});

The scroll event fires on:

  • window โ€” page scrolling
  • Any element with overflow: auto/scroll

Scroll Position

window.scrollY     // how far the page is scrolled vertically
window.scrollX     // how far horizontally
element.scrollTop  // for a scrollable element
Demo: Scroll Event
HTML
<style>
#scroll-demo { height: 100px; overflow: auto; border: 2px solid #6366f1; border-radius: 6px; padding: 8px; background: #f8fafc; }
</style>
<div id='scroll-demo'>
<div style='height:300px;background:linear-gradient(180deg,#eef2ff,#c7d2fe);padding:8px;border-radius:4px;'>Scroll inside this box</div>
</div>
<pre id='scroll-info' style='background:#f1f5f9;padding:8px;border-radius:4px;'></pre>
JavaScript
const demo = document.getElementById('scroll-demo');
const out = document.getElementById('scroll-info');

demo.addEventListener('scroll', function() {
const maxScroll = this.scrollHeight - this.clientHeight;
const percent = maxScroll > 0 ? (this.scrollTop / maxScroll * 100).toFixed(0) : 0;
out.textContent =
'scrollTop: ' + this.scrollTop + 'px' + '\\n' +
'scrolled: ' + percent + '% of total';
});
Live Output Window

Scroll Direction

Detect whether the user is scrolling up or down:

Demo: Scroll Direction
HTML
<style>
#scroll-direction { height: 150px; overflow: auto; border: 2px solid #6366f1; border-radius: 6px; padding: 8px; background: #f8fafc; }
</style>
<div id='scroll-direction'>
<div style='height:400px;background:linear-gradient(180deg,#fef9c3,#fde68a);padding:8px;border-radius:4px;'>Scroll up and down</div>
</div>
<pre id='direction-log' style='background:#f1f5f9;padding:8px;border-radius:4px;'></pre>
JavaScript
const el = document.getElementById('scroll-direction');
const out = document.getElementById('direction-log');
let lastScroll = 0;

el.addEventListener('scroll', function() {
const current = this.scrollTop;
if (current > lastScroll) {
out.textContent = 'Direction: DOWN (scrolling down)';
} else if (current < lastScroll) {
out.textContent = 'Direction: UP (scrolling up)';
}
lastScroll = current;
});
Live Output Window

Throttling Scroll

Scroll events fire very frequently (60+ times per second). Always throttle:

let ticking = false;
window.addEventListener('scroll', function() {
  if (!ticking) {
    requestAnimationFrame(function() {
      // do the work
      ticking = false;
    });
    ticking = true;
  }
});

Or use a time-based throttle:

let lastScrollTime = 0;
window.addEventListener('scroll', function() {
  const now = Date.now();
  if (now - lastScrollTime < 100) return; // max 10 times per second
  lastScrollTime = now;
  // handler
});

Scrolled Past Threshold

Check if the user has scrolled past a given point:

Demo: Back to Top Button
HTML
<style>
#scroll-thresh { height: 150px; overflow: auto; border: 2px solid #6366f1; border-radius: 6px; padding: 8px; background: #f8fafc; }
#back-to-top { display: none; position: fixed; bottom: 20px; right: 20px; padding: 10px 16px; background: #6366f1; color: white; border: none; border-radius: 6px; cursor: pointer; }
</style>
<div id='scroll-thresh'>
<div style='height:400px;background:linear-gradient(180deg,#eef2ff,#c7d2fe);padding:8px;border-radius:4px;'>Scroll down to see the Back to Top button appear</div>
</div>
<button id='back-to-top'>Back to Top</button>
<pre id='threshold-log' style='background:#f1f5f9;padding:8px;border-radius:4px;'></pre>
JavaScript
const container = document.getElementById('scroll-thresh');
const btn = document.getElementById('back-to-top');
const out = document.getElementById('threshold-log');

container.addEventListener('scroll', function() {
if (this.scrollTop > 100) {
btn.style.display = 'block';
out.textContent = 'Scrolled past 100px โ€” showing button';
} else {
btn.style.display = 'none';
out.textContent = 'At top โ€” button hidden';
}
});

btn.addEventListener('click', function() {
container.scrollTo({ top: 0, behavior: 'smooth' });
});
Live Output Window

Infinite Scroll

Load more content when the user reaches the bottom:

scrollable.addEventListener('scroll', function() {
  if (this.scrollHeight - this.scrollTop - this.clientHeight < 50) {
    // Load more content
  }
});
Demo: Infinite Scroll
HTML
<style>
#infinite-list { height: 200px; overflow: auto; border: 2px solid #6366f1; border-radius: 6px; padding: 8px; background: #f8fafc; }
#infinite-list .item { padding: 8px 12px; margin: 4px 0; background: #eef2ff; border-radius: 4px; }
#infinite-list .loading { text-align: center; color: #64748b; padding: 8px; }
</style>
<div id='infinite-list'>
<div class='item'>Item 1</div>
<div class='item'>Item 2</div>
<div class='item'>Item 3</div>
</div>
<pre id='infinite-log' style='background:#f1f5f9;padding:8px;border-radius:4px;'></pre>
JavaScript
const list = document.getElementById('infinite-list');
const log = document.getElementById('infinite-log');
let itemCount = 3;

list.addEventListener('scroll', function() {
const nearBottom = this.scrollHeight - this.scrollTop - this.clientHeight < 30;
if (nearBottom) {
loadMore();
}
});

function loadMore() {
log.textContent = 'Loading more items...';
for (let i = 0; i < 5; i++) {
itemCount++;
const div = document.createElement('div');
div.className = 'item';
div.textContent = 'Item ' + itemCount;
list.appendChild(div);
}
log.textContent = 'Loaded ' + itemCount + ' items total';
}
Live Output Window

IntersectionObserver

For performance, prefer IntersectionObserver over scroll events when possible:

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      // element is visible
    }
  });
});
observer.observe(element);

Scroll Snapping

Modern browsers support CSS scroll-snap:

.container {
  scroll-snap-type: y mandatory;
}
.container > div {
  scroll-snap-align: start;
}

Key Takeaways

  • Scroll events fire rapidly โ€” always throttle with requestAnimationFrame or time gates
  • Track scrollY/scrollTop for position, compare values for direction
  • Use scrollHeight - scrollTop - clientHeight < threshold for infinite scroll detection
  • Consider IntersectionObserver instead โ€” no throttling needed, better performance
  • { passive: true } on scroll listeners enables browser optimizations
  • Smooth scrolling: element.scrollTo({ top: 0, behavior: 'smooth' })