A set of interesting looking image transitions including distortion and warp effects made with WebGL.


From our monthly sponsor: Automate manual QA and catch visual bugs with Percy’s all-in-one visual testing and review platform.

Everybody loves images. They are bright and colorful and we can do fun things with them. Even text sometimes strives to be an image:

     | @ @   Woof! 
     |                 _  
     |  _/------____ ((| |))
     |               `--' |   
 ____|_       ___|   |___.' 

Once you want to show more than one image, you can’t help making a transition between them. Or is it just me?

Jokes aside, image transitions are all over the web. They can be powered by CSS, SVG or WebGL. But of course, the most efficient way to work with graphics in the browser is using the Graphics Processor, or GPU. And the the best way to do this is with WebGL, specifically with shaders written in GLSL.

Today we want to show you some interesting image transition experiments that reveal the boundless possibilities of WebGL. These effects were inspired by the countless incredible design examples and effects seen on websites like The Avener and Oversize Studio.


I will be using the Three.js framework for my transitions. It doesn’t really matter what library you use, it could have also been the amazing Pixi.js library, or simply (but not so straightforward) native WebGL. I’ve used native WebGL in my previous experiment, so this time I’m going to use Three.js. It also seems most beginner friendly to me.

Three.js uses concepts like Camera, Scene and Objects. We will create a simple Plane object, add it to Scene and put it in front of the Camera, so that it is the only thing that you can see. There is a template for that kind of object, PlaneBufferGeometry:

To cover the whole screen with a plane you need a little bit of geometry. The Camera has a fov (field of view), and the plane has a size. So with some calculations you can get it to fill your whole screen:

camera.fov = 2*(180/Math.PI)*Math.atan(PlaneSize/(2*CameraDistance));

Looks complicated, but it’s just getting the angle(fov), knowing all the distances here:

That is actually the end of the 3D part, everything else will be happening in 2D.


In case you are not yet familiar with this language, I highly advise you to check out the wonderful Book Of Shaders.

So, we have a plane and we have a fragment shader attached to it that calculates each pixels color. How do we make a transition? The simplest one done with a shader looks like this:

void main() {
  vec4 image1 = texture2D(texture1,uv);
  vec4 image2 = texture2D(texture2,uv);
  gl_FragColor = mix(image1, image2, progress);

Where progress is a number between 0 and 1, indicating the progress of the animation.

With that kind of code you will get a simple fade transition between images. But that’s not that cool, right?

Cool transitions

Usually all transitions are based on changing so called UVs, or the way texture is wrapped on the plane. So for instance, multiplying UV scales the image, adding a number just shifts the image on the plane.

UVs are nothing magical. Think of them as a coordinate system for pixels on a plane:

Let’s start with some basic code:

gl_FragColor = texture2D(texture,uv);

This just shows an image on the screen. Now let’s adjust that code a bit:

gl_FragColor = texture2D(texture,fract(uv   uv));

By taking the fractional part, we make sure that all the values stay between 0 and 1. And if UV was from 0 to 1, doubling it means it will be from 0 to 2, so we should see the fractional part changing from 0 to 1, and from 0 to 1 again!

And that’s what you get: a repeated image. Now let’s try something different: subtracting UV and using the progress for the animation:

gl_FragColor = texture2D(texture, uv - uv * vec2(1.,0) * progress * 0.5);

First, we make sure that we are only changing one axis of UV, by multiplying it with vec2(1.,0). So, when the progress is 0, it should be the default image. Let’s see:

Now we can stretch the image! Let’s combine those two effects into one.

gl_FragColor = texture2D(uTextureOne, uv - fract(uv * vec2(5.,0.)) * progress * 0.1 );

So basically, we do the stretching and repeat it 5 times. We could use any other number as well.

Much better! Next, if we add another image, we get the effect that you can see in demo 7.

Cool isn’t it? Just two simple arithmetic operations, and you get an interesting transition effect.

That’s just one way of changing UVs. Check out all the other demos, and try to guess what’s the math behind them! Try to come up with your own unique animation and share it with me!

References and Credits