# Building a WebGL Gallery in React

A tutorial on how to build a fluid media gallery in React powered by WebGL.

Canonical URL: https://www.transitivebullsh.it/building-a-webgl-gallery-in-react

Author: Travis Fischer

Published: 2018-12-12

Updated: 2026-09-15T08:13:00.000Z

Tags: React.js

![Building a WebGL Gallery in React](<https://assets.cultural-alignment.com/personal-site/media/c7b060a7786a4fedd854013aeb755ec4e2fe2158f1eb05ace522ee5060ff96d6.jpg>)

<a id="2772a2f9f65b47238da377f4f8af19d0"></a>

## Intro

This delightfully playful gallery is perfect for spicing up a user flow between multiple full pages of content. It’s responsive and very simple to use.

The implementation of [react-fluid-gallery](<https://github.com/transitive-bullshit/react-fluid-galleryhttps://github.com/transitive-bullshit/react-fluid-gallery>) is broken up into two layers: **React** and **WebGL**. The majority of the gallery’s code is agnostic to React, which allows us to easily extend the technique to other UI frameworks like Vue, Angular, or plain HTML+JS.

Play around with the [demo](<https://transitive-bullshit.github.io/react-fluid-gallery/>) & then let’s dive into how it all works!

> TL;DR Show me the code!

<a id="7d6174961f72425abd7f6ea0ae5e4fb1"></a>

## **React Wrapper**

The ` ReactFluidGallery ` component kicks things off by mounting an HTML5 ` canvas. ` This ` canvas ` will serve as the render target for the underlying WebGL simulation, ` FluidGallery `, which we’ll talk about in a bit.

It’s worth noting that is a common pattern in graphics programming. React stores a retained scenegraph that handles render updates when changes occur, and we’re introducing an imperative escape hatch into this retained tree of components via an HTML5 ` canvas `. (more [info](<https://en.wikipedia.org/wiki/Immediate_mode_%28computer_graphics%29>) on this distinction)

In addition to mounting the canvas render target, ` ReactFluidGallery ` includes logic for passing on resize, scroll, and touch events to the underlying ` FluidGallery ` simulation.

[Open embedded content](<https://codesandbox.io/embed/5x5zoqw94x?autoresize=1&fontsize=14&module=%2Fsrc%2Freact-fluid-gallery.js&view=editor>)

ReactFluidGallery wrapper component.

<a id="6c986b109d164e2db6e5388b96b1352c"></a>

## **WebGL Simulation**

The underlying ` FluidGallery ` class implements the core gallery scrolling and WebGL display logic.

All of the WebGL bits are greatly simplified thanks to [Three.js](<https://threejs.org/>), which provides some convenient abstractions and renders its output to the previously mounted ` canvas `.

[Open embedded content](<https://codesandbox.io/embed/5x5zoqw94x?autoresize=1&fontsize=14&module=%2Fsrc%2Ffluid-gallery.js&view=editor>)

WebGL FluidGallery class.

Let’s break down what’s going on here in detail.

1. ` FluidGallery.contructor() `: First, we initialize all of our slides as WebGL [Textures](<https://threejs.org/docs/#api/en/loaders/TextureLoader>). ` _initTexture ` takes special care to differentiate between video and image textures. We then follow fairly standard Three.js initialization steps, including creating a [WebGLRenderer](<https://threejs.org/docs/#api/en/renderers/WebGLRenderer>), a [PerspectiveCamera](<https://threejs.org/docs/#api/en/cameras/PerspectiveCamera>), a [ShaderMaterial](<https://threejs.org/docs/#api/en/materials/ShaderMaterial>) that wraps our custom vertex and fragment shaders, and a 3D [Scene](<https://threejs.org/docs/#api/en/scenes/Scene>) that contains one object, a single [Plane](<https://threejs.org/docs/#api/en/geometries/PlaneGeometryhttps://threejs.org/docs/#api/en/geometries/PlaneGeometry>) that will be resized to fit the entire render canvas. The Plane has our ShaderMaterial attached, so our custom fragment shader will run once per pixel on the output canvas.
1. ` FluidGallery.onScroll(event) `: This is called from our React wrapper every time the user scrolls via the mouse wheel or touch events.
1. ` FluidGallery.update() `: This gets called once per animation frame. We’re using ` requestAnimationFrame ` via the [raf](<https://yarnpkg.com/en/package/raf>) package to ensure that our simulation updates and renders smoothly. Internally, we’re aggregating the current scroll velocity via ` this._speed ` and adding it to ` this._position ` each frame. We also apply some friction to the scroll speed so it eventually slows down. ` this._position ` is a floating point number that goes from zero up to the number of slides in the gallery, where an integer value means a single slide is being shown and a floating point value between two integers represents a partial transition between those two slides. Finally, we update all of the shader parameters to reflect their current values depending on which slide(s) are currently visible.
1. ` FluidGallery.render() `: This also gets called once per animation frame. It just renders the Three.js scene to the target ` canvas `.

[Open embedded content](<https://codesandbox.io/embed/5x5zoqw94x?autoresize=1&fontsize=14&module=%2Fsrc%2Ffrag.js&view=editor>)

Fragment shader for FluidGallery effect.

The Three.js scene uses a shader material to render pixels on the canvas. WebGL v1 shader pipelines include a vertex shader and a fragment shader. For our purposes, we’re using a very simple vertex shader with a more complicated fragment shader displayed above.

Note that these shaders are not JavaScript, but rather [GLSL](<https://www.khronos.org/opengl/wiki/Core_Language_%28GLSL%29>) source files that are exported from JavaScript via ES6 template strings for simplicity of bundling. Most bundlers don’t know how to handle ` glsl ` files, but by exporting them as strings from JS files, they’ll work with any JavaScript bundler.

**The real magic happens in this fragment shader**, which takes in two textures and some parameters detailing the current state of the simulation. For example, if ` progress ` is 0, ` texture1 ` will be the first slide, ` texture2 ` will be the second slide, and the output of the fragment shader will be a pixel for pixel reconstruction of ` texture1 `. If progress is 2.5, for instance, ` texture1 ` will be the third slide, ` texture2 ` will be the fourth slide, and the output will be a roughly 50% mix between ` texture1 ` and ` texture2 `.

If you’d like to learn more about the power of GLSL shaders, check out this great [list of resources](<https://github.com/radixzz/awesome-glslhttps://github.com/radixzz/awesome-glsl>)!

![Check out react-fluid-gallery on GitHub.](<https://assets.cultural-alignment.com/personal-site/media/c7b060a7786a4fedd854013aeb755ec4e2fe2158f1eb05ace522ee5060ff96d6.jpg>)

Check out [react-fluid-gallery](<https://github.com/transitive-bullshit/react-fluid-gallery>) on GitHub.

<a id="fbcaaa77615c48f392e6514fd311f1cf"></a>

## **Credit**

The original version of this awesome gallery technique was published on the personal website of [Tao Tajima](<http://taotajima.jp/>).

The React [package](<https://github.com/transitive-bullshit/react-fluid-gallery>) was bundled using [create-react-library](<https://github.com/transitive-bullshit/create-react-library>).

[GitHub - transitive-bullshit/react-fluid-gallery: Fluid media gallery for React powered by WebGL.](<https://github.com/transitive-bullshit/react-fluid-gallery>)

Fluid media gallery for React powered by WebGL. Contribute to transitive-bullshit/react-fluid-gallery development by creating an account on GitHub.
