DEV Community

Cover image for Immersive Gallery: Mapping the MET API to Dynamic Soundscapes
notbigmuzzy
notbigmuzzy

Posted on

Immersive Gallery: Mapping the MET API to Dynamic Soundscapes

The last time I created an online museum experience, it kinda felt off. You walk from room to room, wait for things to load, and look at static images. It's not bad, but it lacks vibes.

So I decided to try a different approach: build something that felt less like a museum and more like a stream of consciousness. What if you could scrub through art history the way you scrub through a YouTube video?

Enter Gogh With The Flow :)


The Metropolitan Museum of Art has an incredible Open Access API. It’s a treasure trove of human history. But APIs have rate limits, and fetching data for 1450, 1451, 1452... as fast as a user can scroll would instantly hit a wall (or just be incredibly slow).

I wanted the experience to be fluid. No "Loading..." text. No spinners. Just flow.


To solve this, I employed a "hybrid" strategy:

  1. Pre-indexed "Shards": I scraped the collection to build lightweight JSON files for every year (e.g., 1889.json, 1450.json). These contain just the valid Object IDs for that year. They are tiny and load instantly.
  2. On-Demand Detail: When the dialer settles on a year, the app grabs a random selection of IDs from that shard and fetches the images.
  3. Lazy High-Res: We load optimized thumbnails first. Only when you click to "enter" a painting do we ping the API for the massive, high-resolution file, allowing you to zoom in and see the individual brushstrokes.

GSAP does the heavy lifting regarding animations. DOM manipulation is heavy but by using GSAP's InertiaPlugin and careful tweening, the transition between years—fading out the old era and drifting in the new—happens without layout trashing or "ghosting" ( as much as I could pull it off :) )

Example here showing how 'paralax' panels are handles with minimal perf impact

// The "Time Travel" Engine
const updatePanels = () => {
    // Calculate how fast we are scrubbing
    const scrollPos = dialer.scrollLeft;
    const delta = scrollPos - lastScrollPos;

    panels.forEach(panel => {
        // Create parallax depth: background moves slower, 
        // the "window" moves counter to create focus
        let speedMultiplier = panel.className.includes('panel-window') ? -1.5 : 
                              panel.className.includes('panel-further') ? 0.25 : 0.5;

        // Apply momentum without layout thrashing
        let currentX = gsap.getProperty(panel, 'x') || 0;
        currentX += delta * speedMultiplier;

        // Infinite Loop Logic: 
        // If a panel goes off-screen, warp it to the other side
        const screenPos = panel.offsetLeft + currentX;
        if (screenPos > viewportWidth + 200) {
            currentX -= viewportWidth + 400;
        } else if (screenPos < -200) {
            currentX += viewportWidth + 400;
        }

        // Use transforms for 60fps performance
        gsap.set(panel, { x: currentX });
    });
};
Enter fullscreen mode Exit fullscreen mode

Visuals are only half the story. To truly sell the time travel aspect, the soundscape shifts with you.

  • 1400s: Pre-Renaissance choral echoes
  • 1600s: Baroque strings
  • 1800s: Romantic orchestral swells
  • 1900s: Early variations of impressionistic sound

The audio player is set up so it cross-fades tracks as you traverse centuries and even handles "radio" logic, automatically queuing up a new random track from the current era when one finishes


If it sounds interesting, go ahead and give it a spin. 1889 is a good year ( but me personally, I'm into baroque heavily, didn't even realize it until debugging this thing :) )

Live Demo - https://notbigmuzzy.github.io/goghwiththeflow/
Source Code - https://github.com/notbigmuzzy/goghwiththeflow


Powered by the Metropolitan Museum of Art API.

Top comments (0)