# How to Implement Infinite Scroll in the Exercise Browser (index.html)

> Learn how to implement infinite scroll in the exercise browser by adding a sentinel, caching data, and using the Intersection Observer API in index.html.

- Repository: [Hasan Emir Yıldırım/exercises-dataset](https://github.com/hasaneyldrm/exercises-dataset)
- Tags: how-to-guide
- Published: 2026-07-28

---

**Infinite scroll in the `hasaneyldrm/exercises-dataset` exercise browser is achieved by adding a sentinel element to [`index.html`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/index.html), caching [`data/exercises.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json) client-side, and using the Intersection Observer API in [`scripts/infiniteScroll.js`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/scripts/infiniteScroll.js) to append slices of 20 exercises at a time to the `#exercise-list` container.**

The exercise browser in the `hasaneyldrm/exercises-dataset` repository is a static HTML page that currently renders its full catalog from a single JSON file. Implementing infinite scroll in the exercise browser replaces that single-page dump with a chunked, performance-friendly experience that loads data as the user scrolls.

## Architecture for a Static Infinite Scroll

Because the repository is pure client-side, the infinite-scroll behavior is simulated by loading [`data/exercises.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json) once into memory and slicing it on the client. This keeps the static-site nature of the repository intact while providing a fluid, paginated user experience. The implementation relies on three core pieces: the container and sentinel in [`index.html`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/index.html), the master data in [`data/exercises.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json), and the pagination engine in [`scripts/infiniteScroll.js`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/scripts/infiniteScroll.js).

## Marking the Scroll Trigger in index.html

The main page requires a container to hold exercise cards and a hidden sentinel element that signals when to load more. In [`index.html`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/index.html), add the following markup:

```html
<body>
  <main id="exercise-list"></main>
  <!-- Sentinel that will be observed -->
  <div id="sentinel"></div>

  <script src="scripts/infiniteScroll.js"></script>
</body>

```

## Implementing the Pagination Engine

Create [`scripts/infiniteScroll.js`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/scripts/infiniteScroll.js) to cache the dataset, render slices, and detect scroll position. The script reads [`../data/exercises.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/../data/exercises.json) once, stores it in the `allExercises` array, and appends pages of 20 items to the existing DOM without re-rendering the full list.

```javascript
// Path to the static JSON file
const EXERCISES_URL = '../data/exercises.json';
const PAGE_SIZE = 20;          // Number of exercises per request
let allExercises = [];         // Holds the full dataset after first load
let offset = 0;                // Current cursor

// Render a slice of exercises into the DOM
function renderExercises(slice) {
  const container = document.getElementById('exercise-list');
  slice.forEach(ex => {
    const card = document.createElement('article');
    card.className = 'exercise-card';
    card.innerHTML = `
      <h2>${ex.title}</h2>
      <p>${ex.description}</p>`;
    container.appendChild(card);
  });
}

// Load the next page and render it
function loadNextPage() {
  if (offset >= allExercises.length) return; // No more items

  const nextSlice = allExercises.slice(offset, offset + PAGE_SIZE);
  renderExercises(nextSlice);
  offset += PAGE_SIZE;
}

// Initial fetch of the whole data file (only once)
fetch(EXERCISES_URL)
  .then(res => res.json())
  .then(data => {
    allExercises = data;
    loadNextPage();        // Render the first page
    observeSentinel();     // Start listening for scroll events
  })
  .catch(err => console.error('Failed to load exercises:', err));

// Set up IntersectionObserver on the sentinel element
function observeSentinel() {
  const sentinel = document.getElementById('sentinel');
  const observer = new IntersectionObserver(entries => {
    if (entries[0].isIntersecting) {
      loadNextPage();
    }
  });
  observer.observe(sentinel);
}

```

The **sentinel** stays at the bottom of the document. When it enters the viewport, `entries[0].isIntersecting` becomes true and `loadNextPage()` appends the next batch of exercises. Because the data is cached in `allExercises` after the first fetch, subsequent calls are instantaneous.

## Handling the End of the Dataset

When the `offset` cursor reaches `allExercises.length`, the `loadNextPage()` function returns early and stops requesting new slices. This prevents unnecessary renders and gracefully handles the end of the static catalog. If the repository ever gains a real back-end, replace the in-memory `slice()` logic with paginated API requests such as `/exercises?offset=…&limit=…`.

## Optional Enhancements

- **Add a loading spinner** that displays while the next page is being prepared.
- **Persist `offset` in `localStorage`** so users resume their scroll position after a page refresh.
- **Debounce the observer callback** if the page size is large, preventing rapid consecutive renders when the sentinel flickers.

## Summary

- Implementing infinite scroll in the exercise browser requires adding a sentinel element to [`index.html`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/index.html), caching [`data/exercises.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json) client-side, and slicing the array in [`scripts/infiniteScroll.js`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/scripts/infiniteScroll.js).
- The **Intersection Observer API** triggers `loadNextPage()` when the sentinel enters the viewport, which is more performant than traditional `scroll` event listeners.
- The `renderExercises()` function appends new cards to `#exercise-list` without re-rendering existing items, minimizing DOM thrashing.
- Once `offset` exceeds `allExercises.length`, pagination stops automatically, providing a clean end-of-data experience.

## Frequently Asked Questions

### How do I stop infinite scroll when all exercises are loaded?

The `loadNextPage()` function checks `if (offset >= allExercises.length) return;` before slicing the array. When the offset exceeds the total number of exercises, the function exits silently and no additional cards are appended.

### Why is Intersection Observer better than a scroll event listener?

The **Intersection Observer API** delegates visibility detection to the browser, eliminating the need to poll `window.scrollY` on every frame. It is more performant and works correctly even when the scrollable container is an element other than the window.

### Can infinite scroll work without loading the entire JSON file first?

In the current `hasaneyldrm/exercises-dataset` architecture, the simplest approach is to fetch [`data/exercises.json`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/data/exercises.json) once and slice it on the client. If a back-end is introduced later, replace the single fetch with paginated API requests using query parameters like `?offset=${offset}&limit=20`.

### What is the role of the sentinel element?

The sentinel is an empty `<div id="sentinel"></div>` placed after the last rendered card in [`index.html`](https://github.com/hasaneyldrm/exercises-dataset/blob/main/index.html). The `IntersectionObserver` watches this element, and when it scrolls into view, it triggers the next page load.