WebGL Scroll Spiral

A couple of decorative and inspirational WebGL background scroll effects for websites powered by regl. The idea is to twist some images and hexagonal grid patterns on scroll, creating an interesting effect.

Today we want to show you some shader art made with WebGL. The idea is to create a decorative background effect by twisting images and hexagonal grid patterns smoothly on page scroll. The effect aims to be as light as possible on desktops or mobile devices.

Attention: You’ll need to have WebGL support in your browser in order to see the effect.

Basic HTML / CSS structure

Let’s have a quick look at some HTML and CSS we need to define. We’ll use a fixed canvas element that we’ll size to be fullscreen:

<body>
	<canvas id="webgl"></canvas>
	...
</body>
canvas#webgl {
	display: block;
	position: fixed;
	top: 0;
	left: 0;
	width: 100vw;
	height: 100vh;
	z-index: -1;
}

The WebGL part

We’ll be using the regl library which is a light-weight WebGL wrapper. If you are getting started with WebGL, it’s a great way to begin writing your own shaders and custom graphical effects using this fast library that has a functional data-driven style inspired by React. If you want to get started with shaders, check out this article by Paul Lewis: An Introduction to Shaders.

Let’s have a look at the interesting parts of the code that we use throughout the demos.

GLSL

<script src="js/regl.min.js"></script>
<script type="x-shader/x-fragment" id="fragmentShader">
	#define TWO_PI 6.2831853072
#define PI 3.14159265359

// On iOS, specifying highp works more smoothly.
	precision highp float;

	uniform float globaltime;
	uniform vec2 resolution;
	uniform float aspect;
	uniform float scroll;
	uniform float velocity;

...
</script>

Initialize regl from a canvas element

var canvas = document.querySelector('#webgl');
var regl = createREGL({
	canvas: canvas,
	onDone: function(error, regl) {
		if (error) { alert(error); }
	}
});

Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.

Create a regl draw command

var draw = regl({
	// Fragment Shader
	frag: document.querySelector('#fragmentShader').textContent,
	// Vertex Shader
	vert:	'attribute vec2 position;
		void main() {
			gl_Position = vec4(3.0 * position, 0.0, 1.0);
		}',
	attributes: { position: [ [-1, 0], [0, -1], [1, 1] ] },
	count: 3,
	uniforms: {
		globaltime: regl.prop('globaltime'),
		resolution: regl.prop('resolution'),
		aspect: regl.prop('aspect'),
		scroll: regl.prop('scroll'),
		velocity: regl.prop('velocity')
	}
});

Our fragment shader is the GLSL code in the script element #fragmentShader.

Hooking in a per-frame callback function

The code that runs the regl processes for each frame of requestAnimationFrame is defined in the callback function. In addition, we also define variables that store the scroll status.

// Scroll variables
var scroll = 0.0, velocity = 0.0, lastScroll = 0.0;

regl.frame(function(ctx) {
	// Resize a canvas element with the aspect ratio (100vw, 100vh)
	var aspect = canvas.scrollWidth / canvas.scrollHeight;
	canvas.width = 512 * aspect;
	canvas.height = 512;

		// Scroll amount (0.0 to 1.0)
	scroll = window.pageYOffset / (document.documentElement.scrollHeight - window.innerHeight);
	// Scroll velocity
	velocity = velocity * 0.99 + (scroll - lastScroll);
	lastScroll = scroll;

	// Clear the draw buffer
	regl.clear({ color: [0, 0, 0, 0] });

	// Execute a REGL draw command
	draw({
		globaltime: ctx.time,
		resolution: [ctx.viewportWidth, ctx.viewportHeight],
		aspect: aspect,
		scroll: scroll,
		velocity: velocity
	});
});

The important thing is that the size of the canvas is set smaller than the display size (100vw, 100vh). This will make the actual drawing size up to width x height, which improves speed.

Scroll state variables

We have created three variables for storing the scroll state: scroll, velocity and lastScroll. scroll contains the scroll position which is usually in the range of 0.0 to 1.0 (sometimes this value might fall out of range by operations such as swiping down).

The velocity variable holds the scrolling taking the law of inertia into account. When scrolling down this will be a positive value and when scrolling up, it will be a negative value. The factor 0.99 means keeping the speed in the previous frame to 99%. The acceleration is calculated from the difference between scroll and lastScroll.

Hope you enjoy this WebGL experiment!

References and Credits

Xoihazard

Xoihazard is a creative coder and abstract painting artist in Japan.

Stay in the loop: Get your dose of frontend twice a week

Fresh news, inspo, code demos, and UI animations—zero fluff, all quality. Make your Mondays and Thursdays creative!

Feedback 14

Comments are closed.
  1. You should’nt have changed of cigarettes’ brand… Weird and beautiful (but weird mainly).
    Good stuff (I’m not talking about your cigarettes).

  2. Ha! My comment was deleted. I guess you guys didn’t feel the same way about Arrival as i did :/

    • Hi Mark, no comment was deleted! Are you sure it was properly submitted? Yesterday we had a short downtime so it might also be related to that. Thanks for your feedback.

  3. This. Is. Amazing.
    So cool. I did (sadly) not understand much about the actual code, but still. This is wonderful. What will the web wizards come up with next.

  4. @Pedro – It was indeed submitted correctly because it was on the site for a spell. Ah well, I guess it’s lost now, like tears in the rain. To give context to my comment above, the missing comment was full of praise for the demos especially number 5 which reminded me of the film Arrival.