Lines to Content Layout Animation

Today I’d like to share a little layout animation with you. The idea is to switch from a line view to a larger content view while animating a tiny image. This time, I used GreenSock’s Flip plugin which made things a lot easier as it does most of the work for transitioning an element to a new state and new layout.

The effect is inspired by part of this cool video by Holographik:

Typography elements disappear while images get animated to larger views.

The initial view of our layout is the following:

When clicking on one of the images, we transition to a new layout which looks as follows:

The whole flow looks like this:

The Flip plugin is a very useful tool that you can use for many complex cases. It also factors in nested transforms which is something quite special as it allows for powerful view switching without compromising your structures.

Hope you enjoy this demo and find it useful! Thanks for checking by!

The post Lines to Content Layout Animation appeared first on Codrops.

Pixel Distortion Effect with Three.js

The creative coder’s dream is to rule pixels on their screen. To arrange them in beautiful patterns and do whatever you want with them. Well, this is exactly what we are going to do with this demo. Let’s distort and rule pixels with the power of our mouse cursor, just like the developers of the amazing Infinite Bad Guy website did!

Setup

The scene is the usual, we just create a fullscreen image on a screen, so it preserves the aspect ratio, and has its “background-size: cover” applied through the glsl shader. In the end, we have a geometry stretched for the whole viewport, and a little shader like this:

vec2 newUV = (vUv - vec2(0.5))*aspect + vec2(0.5);
    gl_FragColor = texture2D(uTexture,newUV);
    

The whole thing just shows the image, no distortions yet.

The Magnificent Data Texture

I hope by this time you know that any texture in WebGL is basically just numbers corresponding to each pixel’s color.

Three.js has a specific API to create your own textures pixel by pixel. It is called, no surprise, DataTexture. So let’s create another texture for our demo, with random numbers:

    const size = rows * columns;
    const data = new Float32Array(3 * size);

    for(let i = 0; i < size; i++) {
          const stride = i * 3;
          let r = Math.random() * 255 ;
          let r1 = Math.random() * 255 ;

          data[stride] = r; // red, and also X
          data[stride + 1] = r1; // green, and also Y
          data[stride + 2] = 0; // blue
        }
    this.texture = new THREE.DataTexture(data, width, height, THREE.RGBFormat, THREE.FloatType);
    

This is heavily based on the default example from the documentation. The only difference is, we are using FloatType texture, so we are not bound to only integer numbers. One of the interesting things is, that numbers should be between 0 and 255, even though, in the GLSL it will be 0..1 range anyway. You should just keep that in mind, so you are using correct number ranges.

What is also an interesting idea, is that GLSL doesn’t really care what the numbers mean in your data structures. It could be both color.rgb, and color.xyz. And that’s precisely what we will use here, we don’t care about exact color of this texture, we will use it as a distortion for our demo! Just as a nice data structure for GLSL.

But, just to understand better, this is what the texture will look like when you want to preview it:

You see those big rectangles because i picked something like 25×35 DataTexture size, which is really low-res.
Also, it has colors because im using two different random numbers for XY(Red-Green) variables, which results in this.

So now, we could already use this texture as a distortion in our fragment shader:

    vec4 color = texture2D(uTexture,newUV);
    vec4 offset = texture2D(uDataTexture,vUv);
    // we are distorting UVs with new texture values
    gl_FragColor = texture2D(uTexture,newUV - 0.02*offset.rg);
    

The Mouse and its power

So now, let’s make it dynamic! We will need a couple of things. First, we need the mouse position and speed. And also, the mouse radius, meaning, at what distance would the mouse distort our image.

A short explanation: On each step of the animation, I will loop through my grid cells aka pixels of DataTexture. And assign some values based on mouse position and speed. Second, im going to relax the distortion. This needs to be done, if the user stops moving mouse, the distortion should come to 0.

So, now the code looks like this, simplified a bit, for better understanding the concept:

    let data = DataTexture.image.data;
    // loop through all the pixels of DataTexture
    for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
        // get distance between mouse, and current DataTexture pixel
      let distance = distanceBetween(mouse, [i,j])
      if (distance < maxDistance) {

        let index = 3 * (i + this.size * j); // get the pixel coordinate on screen
        data[index] = this.mouse.vX ; // mouse speed
        data[index + 1] =  this.mouse.vY ; // mouse speed
      }
    }
    // slowly move system towards 0 distortion
    for (let i = 0; i < data.length; i += 3) {
      data[i] *= 0.9
      data[i + 1] *= 0.9
    }
    DataTexture.needsUpdate = true;

A couple of things are added to make it look better, but the concept is here. If you ever worked with particle systems, this is exactly that concept, except our particles never move, we just change some values of the particles (distortion inside each big pixel).

Result

I left the settings open in the last demo, so you can play with parameters and come up with your own unique feel of the animation. Let me know what it inspired you to create!

The post Pixel Distortion Effect with Three.js appeared first on Codrops.

Pixelated Distortion Effect with Three.js

In this ALL YOUR HTML stream and coding session we’ll be recreating the interactive pixel distortion effect seen on the website for the music video covers of “Infinite Bad Guy” made as an AI Experiment at Google and YouTube. We’ll be using Three.js and datatexture to achieve the look.

This coding session was streamed live on December 12, 2021.

Support: https://www.patreon.com/allyourhtml

Setup: https://gist.github.com/akella/a19954…

The post Pixelated Distortion Effect with Three.js appeared first on Codrops.

Deconstructing the homunculus.jp Distortion with Three.js

In this ALL YOUR HTML coding session we will be deconstructing the pixel river distortion seen on homunculus.jp with Three.js, and also trying out Theatre.js.

This coding session was streamed live on October 3, 2021.

Check out the live demo.

Try to change values and animate them; use the icon on the top left corner of the website.

Support: https://www.patreon.com/allyourhtml

Setup: https://gist.github.com/akella/a19954…

The post Deconstructing the homunculus.jp Distortion with Three.js appeared first on Codrops.

Thumbnail Hover Effect with SVG Filters

Already a while back, we explored applying SVG filters to images that appear when hovering a menu item. Today I thought that it would be interesting to play with a similar effect on thumbnails.

So the idea is to hover a small image and apply an SVG filter to it while sliding in another element, a caption that covers the image.

This kind of animation adds that little special extra to a design component like this. I really hope you like it and that it inspires you for new ideas.

I’ve used a different filter on each one of the images, so you can get an idea on the different possibilities here.

Please download it and use it as you wish, and thanks for stopping by!

Let me know what you think of it @crnacura or @codrops.

The post Thumbnail Hover Effect with SVG Filters appeared first on Codrops.

How to Code a Crosshair Mouse Cursor with Distortion Hover Effect

Today I’d like to show you how to code a special cursor effect. Custom cursors have been very popular and there’s so many creative possibilities that can enhance a certain design and an interaction logic.

Let’s have a look how to create a fullscreen crosshair cursor in SVG and how to distort the cursors’s lines with an SVG filter when hovering over links. We’ll also add a nice hover animation for some menu items using Splitting.js.

The Markup

For the SVG cursor we want a singe SVG for each line that has enough of space to get the distortion effect applied to it:

<div class="cursor">
	<svg class="cursor__line cursor__line--horizontal" viewBox="0 0 200 20" preserveAspectRatio="none">
		<line class="cursor__line-element" x1="0" y1="10" x2="200" y2="10" shape-rendering="crispEdges" vector-effect="non-scaling-stroke" />
	</svg>
	<svg class="cursor__line cursor__line--vertical" viewBox="0 0 20 200" preserveAspectRatio="none">
		<line class="cursor__line-element" x1="10" y1="0" x2="10" y2="200" shape-rendering="crispEdges" vector-effect="non-scaling-stroke" />
	</svg>
</div>

We’ll add the filters like this:

<div class="cursor">
	<svg class="cursor__line cursor__line--horizontal" viewBox="0 0 200 20" preserveAspectRatio="none">
		<defs>
			<filter id="filter-noise-x" x="-50%" y="-50%" width="200%" height="200%" 
			filterUnits="objectBoundingBox">
				<feTurbulence type="fractalNoise" baseFrequency="0" numOctaves="1" result="warp" />
				<feOffset dx="-30" result="warpOffset" />
				<feDisplacementMap xChannelSelector="R" yChannelSelector="G" scale="30" in="SourceGraphic" in2="warpOffset" />
			</filter>
		</defs>
		<line class="cursor__line-element" x1="0" y1="10" x2="200" y2="10" shape-rendering="crispEdges" vector-effect="non-scaling-stroke" />
	</svg>
	<svg class="cursor__line cursor__line--vertical" viewBox="0 0 20 200" preserveAspectRatio="none">
		<defs>
			<filter id="filter-noise-y" x="-50%" y="-50%" width="200%" height="200%" 
			filterUnits="objectBoundingBox">
				<feTurbulence type="fractalNoise" baseFrequency="0" numOctaves="1" result="warp" />
				<feOffset dy="-30" result="warpOffset" />
				<feDisplacementMap xChannelSelector="R" yChannelSelector="G" scale="30" in="SourceGraphic" in2="warpOffset" />
			</filter>
		</defs>
		<line class="cursor__line-element" x1="10" y1="0" x2="10" y2="200" shape-rendering="crispEdges" vector-effect="non-scaling-stroke" />
	</svg>
</div>

Let’s set up our menu with some data-splitting attributes:

<nav class="menu">
	<a href="#content-1" class="menu__item">
		<span data-splitting class="menu__item-title">Maputo</span>
		<span data-splitting class="menu__item-sub">Nights</span>
	</a>
	<a href="#content-1" class="menu__item">
		<span data-splitting class="menu__item-title">Gaborone</span>
		<span data-splitting class="menu__item-sub">Vibes</span>
	</a>
	<a href="#content-1" class="menu__item">
		<span data-splitting class="menu__item-title">Kinshasa</span>
		<span data-splitting class="menu__item-sub">Walks</span>
	</a>
</nav>

The CSS

First, we need to hide our cursor by default, and we just want to show it if the user has a pointing device, like a mouse. So we add a media query with the any-pointer media feature:

.cursor {
	display: block;
}

@media (any-pointer:fine) {
	.cursor {
		position: fixed;
		top: 0;
		left: 0;
		display: block;
		pointer-events: none;
		z-index: 1001;
	}

	.no-js .cursor {
		display: none;
	}

	.cursor__line {
		position: fixed;
		display: block;
		will-change: transform, opacity;
	}
	
	.cursor__line--horizontal {
		top: -10px;
		left: -10%;
		width: 120%;
		height: 20px;
	}
	
	.cursor__line--vertical {
		left: -10px;
		top: -10%;
		height: 120%;
		width: 20px;
	}
	
	.cursor__line-element {
		fill: none;
		stroke: var(--cursor-stroke);
		stroke-width: var(--cursor-stroke-width);
	}

}

The variables are define in the body.

For the menu, we’ll use some flexbox magic to lay out the menu items beneath each other:

.menu {
	display: flex;
	flex-direction: column;
	width: 100vw;
	height: calc(100vh - 13rem);
	position: relative;
	justify-content: flex-start;
	align-items: center;
}

.menu {
	text-align: center;
	padding-top: 10vh;
}

.menu__item {
	display: inline-block;
	margin-bottom: 3rem;
	text-decoration: none;
	color: var(--color-menu);
	font-family: vortice-concept, sans-serif;
}

.menu__item-title {
	line-height: 1;
	font-size: 7.5vw;
}

.menu__item-sub {
	font-size: 1.5vw;
	display: block;
}

@media screen and (min-width: 53em) {
	.menu {
		height: 100vh;
		justify-content: center;
	}
}

The JavaScript

Let’s create our custom cursor. So we have two SVGs, one for each line. As we saw in the markup earlier, each one of the SVGs will include a filter that we’ll apply to the respective line when hovering over a menu link.

Let’s start coding the entry JavaScript file (index.js):


import { Cursor } from './cursor';

// initialize custom cursor
const cursor = new Cursor(document.querySelector('.cursor'));

// mouse effects on all links
[...document.querySelectorAll('a')].forEach(link => {
    link.addEventListener('mouseenter', () => cursor.enter());
    link.addEventListener('mouseleave', () => cursor.leave());
});

Now, let’s create a class for the cursor (in cursor.js):


import { gsap } from 'gsap';
import { getMousePos } from './utils';

// Track the mouse position and update it on mouse move
let mouse = {x: 0, y: 0};
window.addEventListener('mousemove', ev => mouse = getMousePos(ev));

export class Cursor {
    constructor(el) {
    }
    // hovering over a link
    enter() {
    }
    // hovering out a link
    leave() {
    }
    // create the turbulence animation timeline on the cursor line elements
    createNoiseTimeline() {
    }
    // render styles and loop
    render() {
        // ...
        requestAnimationFrame(() => this.render());
    }
}

What we do here is to update the mouse position as we move the mouse around. For that, we use the getMousePos function (in utils.js).

Let’s move on to the next interesting part:


...
constructor(el) {
    // main DOM element which includes the 2 svgs, each for each line
    this.DOM = {el: el};
    // both horizontal and vertical lines
    this.DOM.lines = this.DOM.el.children;
    [this.DOM.lineHorizontal, this.DOM.lineVertical] = this.DOM.lines;
    // hide initially
    gsap.set(this.DOM.lines, {opacity: 0});
    ...
}
...

We initialize the line DOM elements and hide them initially.

We want to update the lines transform (translation values) as we move the mouse. For that, let’s create an object that stores the translation state:


...
constructor(el) {
    ...
    // style properties that will change as we move the mouse (translation)
    this.renderedStyles = {
        tx: {previous: 0, current: 0, amt: 0.15},
        ty: {previous: 0, current: 0, amt: 0.15}
    };
    ...
}
...

With interpolation, we can achieve a smooth animation effect when moving the mouse. The “previous” and “current” values are the values we’ll be interpolating. The current value of one of these “animatable” properties will be one between these two values at a specific increment. The value of “amt” is the amount to interpolate. As an example, the following formula calculates our current translationX value:

this.renderedStyles.tx.previous = lerp(this.renderedStyles.tx.previous, this.renderedStyles.tx.current, this.renderedStyles.tx.amt);

...
constructor(el) {
    ...
    // svg filters (ids)
    this.filterId = {
        x: '#filter-noise-x',
        y: '#filter-noise-y'
    };
    // the feTurbulence elements per filter
    this.DOM.feTurbulence = {
        x: document.querySelector(`${this.filterId.x} > feTurbulence`),
        y: document.querySelector(`${this.filterId.y} > feTurbulence`)
    }
    // turbulence current value
    this.primitiveValues = {turbulence: 0};
    // create the gsap timeline that will animate the turbulence value
    this.createNoiseTimeline();
}
...

Next, we initialize the filter ids, the feTurbulence elements for each SVG filter (one per line) and also the current turbulence value. Then we create the GSAP timeline that will take care of updating the baseFrequency of each filter with the current turbulence value. Here’s how that timeline and the methods that start/stop it look like:


...
createNoiseTimeline() {
    // turbulence value animation timeline:
    this.tl = gsap.timeline({
        paused: true,
        onStart: () => {
            // apply the filters for each line element
            this.DOM.lineHorizontal.style.filter = `url(${this.filterId.x}`;
            this.DOM.lineVertical.style.filter = `url(${this.filterId.y}`;
        },
        onUpdate: () => {
            // set the baseFrequency attribute for each line with the current turbulence value
            this.DOM.feTurbulence.x.setAttribute('baseFrequency', this.primitiveValues.turbulence);
            this.DOM.feTurbulence.y.setAttribute('baseFrequency', this.primitiveValues.turbulence);
        },
        onComplete: () => {
            // remove the filters once the animation completes
            this.DOM.lineHorizontal.style.filter = this.DOM.lineVertical.style.filter = 'none';
        }
    })
    .to(this.primitiveValues, { 
        duration: 0.5,
        ease: 'power1',
        // turbulence start value
        startAt: {turbulence: 1},
        // animate to 0
        turbulence: 0
    });
}
enter() {
    // start the turbulence timeline
    this.tl.restart();
}
leave() {
    // stop the turbulence timeline
    this.tl.progress(1).kill();
}
...

The animation will change the turbulence value (which starts at 1 and ends at 0) and apply it to each feTurbulence’s baseFrequency attribute. The filters get applied to the lines in the beginning and removed once the animation is completed.

To finalize the constructor method we fade in the lines and start updating the translation values the first time we move the mouse:


...
constructor(el) {
    ...
    import { lerp, getMousePos } from './utils';
    ...
    // on first mousemove fade in the lines and start the requestAnimationFrame rendering function
    this.onMouseMoveEv = () => {
        this.renderedStyles.tx.previous = this.renderedStyles.tx.current = mouse.x;
        this.renderedStyles.ty.previous = this.renderedStyles.ty.previous = mouse.y;
        gsap.to(this.DOM.lines, {duration: 0.9, ease: 'Power3.easeOut', opacity: 1});
        requestAnimationFrame(() => this.render());
        window.removeEventListener('mousemove', this.onMouseMoveEv);
    };
    window.addEventListener('mousemove', this.onMouseMoveEv);
}
...

Now we’re only missing the actual method that updates the lines’ translation values as we move the mouse:



...
render() {
    // update the current translation values
    this.renderedStyles['tx'].current = mouse.x;
    this.renderedStyles['ty'].current = mouse.y;
    // use linear interpolation to delay the translation animation
    for (const key in this.renderedStyles ) {
        this.renderedStyles[key].previous = lerp(this.renderedStyles[key].previous, this.renderedStyles[key].current, this.renderedStyles[key].amt);
    }
    // set the new values
    gsap.set(this.DOM.lineVertical, {x: this.renderedStyles['tx'].previous});
    gsap.set(this.DOM.lineHorizontal, {y: this.renderedStyles['ty'].previous});
    // loop this until the end of time
    requestAnimationFrame(() => this.render());
}

As an extra, let’s add a little glitch effect to the menu items texts when we hover over them. We’ll use the Splitting library to split the menu texts into spans/chars so we can animate each one individually. We want to change the translation values of each character and also it’s color. Let’s update our index.js file:


import { Cursor } from './cursor';
import { MenuItem } from './menuItem';
// Splitting (used to split the menu item texts to spans/characters)
import 'splitting/dist/splitting.css';
import 'splitting/dist/splitting-cells.css';
import Splitting from 'splitting';

// initialize Splitting
const splitting = Splitting();

// initialize custom cursor
const cursor = new Cursor(document.querySelector('.cursor'));

// Menu Items
[...document.querySelectorAll('.menu > a')].forEach(el => new MenuItem(el));

// mouse effects on all links
[...document.querySelectorAll('a')].forEach(link => {
    link.addEventListener('mouseenter', () => cursor.enter());
    link.addEventListener('mouseleave', () => cursor.leave());
});

Assuming we have data-splitting set in all elements we want to split into chars, then we only need to call Splitting() and we then have each text split into a bunch of spans, for every letter of the text.

Let’s now have a look at our MenuItem class:


import { gsap } from 'gsap';

export class MenuItem {
    constructor(el) {
        this.DOM = {el};
        // all text chars (Splittingjs) 
        this.DOM.titleChars = this.DOM.el.querySelectorAll('span.char');
        // initial and final colors for each span char (before and after hovering)
        const bodyComputedStyle = getComputedStyle(document.body);
        this.colors = {
            initial: bodyComputedStyle.getPropertyValue('--color-menu'), 
            final: bodyComputedStyle.getPropertyValue('--color-link')
        };
        this.initEvents();
    }
    ...
}

We get a reference to all the characters of the menu item text and also it’s initial and final colors for the hover animation.


...
initEvents() {
    this.onMouseEnterEv = () => this.onMouseEnter();
    this.DOM.el.addEventListener('mouseenter', this.onMouseEnterEv);

    this.onMouseLeaveEv = () => this.onMouseLeave();
    this.DOM.el.addEventListener('mouseleave', this.onMouseLeaveEv);
}
...

We initialize the mouseenter/mouseleave events which will triggger the animation on the characters.

When hovering over a menu item we will randomly change its characters position and color, and when hovering out we reset the original color:

onMouseEnter() {
    if ( this.leaveTimeline ) {
        this.leaveTimeline.kill();
    }

    // let's try to do an animation that resembles a glitch effect on the characters
    // we randomly set new positions for the translation and rotation values of each char and also set a new color
    // and repeat this for 3 times
    this.enterTimeline = gsap.timeline({
        defaults: {
            duration: 0.05,
            ease: 'power3',
            x: () => gsap.utils.random(-15, 15),
            y: () => gsap.utils.random(-20, 10),
            rotation: () => gsap.utils.random(-5, 5),
            color: () => gsap.utils.random(0, 3) < 0.5 ? this.colors.final : this.colors.initial
        }
    })
    // repeat 3 times (repeatRefresh option will make sure the translation/rotation values will be different for each iteration)
    .to(this.DOM.titleChars, {
        repeat: 3,
        repeatRefresh: true
    }, 0)
    // reset translation/rotation and set final color
    .to(this.DOM.titleChars, {
        x: 0, 
        y: 0, 
        rotation: 0,
        color: this.colors.final
    }, '+=0.05');
}
onMouseLeave() {
    // set back the initial color for each char
    this.leaveTimeline = gsap.timeline()
    .to(this.DOM.titleChars, {
        duration: 0.4,
        ease: 'power3',
        color: this.colors.initial
    });
}

And that is all!

I hope this has not been too difficult to follow and that you have gained some insight into constructing this fancy effect.

Please let me know if you have any question @codrops or @crnacura.

Thank you for reading!

The post How to Code a Crosshair Mouse Cursor with Distortion Hover Effect appeared first on Codrops.

Rotating Loading Animation of 3D Shapes with Three.js

All things we see around are 3D shapes. Some of them are nice looking, some of them you’d like to touch and squeeze. But because it’s still quarantine time, and I don’t have many things around, I decided to create some virtual things to twist around. 🙂 Here’s what I ended up with:

How to do that?

To render everything I used Three.js, just like Mario Carillo in his amazing demo, go check it out! Three.js is the best and most popular library to do things with WebGL at the moment. And if you are just starting your 3D path, it is definitely the easiest way to get something working.

If you just rotate the shape, its nothing special, its just rotating.

Interesting things start to happen when you rotate different parts of the shape with different speeds. Let’s say I want the bottom part of the object to rotate first, and than the rest, let’s see whats going to happen.

To do something like that, you would need to use a Vertex shader. I used a little bit of math and rotated parts of the object depending on the Y or Z coordinate. The simplified code looks like this:

vec3 newposition = rotate(oldPosition, angle*Y_COORDINATE);

So with a coordinate change, the rotation would change as well. The rotate function itself includes a lot of Sine-Cosine’s and matrices. All the math you need to rotate a point around some axis. To give it a more natural feel, I also change the easing function, so it looks like elastic rubber.

Visuals, MatCap

To create a nice look for objects, we could have used lights and shadows and complicated materials. But because there’s not so much in the scene, we got away with the most simple kind of material: MatCaps. So instead of making complicated calculations, you just use a simple image to reference the lighting and color. Here is how it looks like:

And here is how the torus shape looks like with this texture applied to it:

Amazing, isn’t it? And so simple.

So combining Matcaps and a rotation method, we can create a lot of cool shape animations that look great, and have this rubber feeling.

Go and create your shape with this technique and make sure to share it with us! Hope this simple technique will inspire you to create your own animation! Have a good day!

The post Rotating Loading Animation of 3D Shapes with Three.js appeared first on Codrops.

Twisted Colorful Spheres with Three.js

I love blobs and I enjoy looking for interesting ways to change basic geometries with Three.js: bending a plane, twisting a box, or exploring a torus (like in this 10-min video tutorial). So this time, my love for shaping things will be the excuse to see what we can do with a sphere, transforming it using shaders. 

This tutorial will be brief, so we’ll skip the basic render/scene setup and focus on manipulating the sphere’s shape and colors, but if you want to know more about the setup check out these steps.

We’ll go with a more rounded than irregular shape, so the premise is to deform a sphere and use that same distortion to color it.

Vertex displacement

As you’ve probably been thinking, we’ll be using noise to deform the geometry by moving each vertex along the direction of its normal. Think of it as if we were pushing each vertex from the inside out with different strengths. I could elaborate more on this, but I rather point you to this article by The Spite aka Jaume Sanchez Elias, he explains this so well! I bet some of you have stumbled upon this article already.

So in code, it looks like this:

varying vec3 vNormal;

uniform float uTime;
uniform float uSpeed;
uniform float uNoiseDensity;
uniform float uNoiseStrength;

#pragma glslify: pnoise = require(glsl-noise/periodic/3d)

void main() {
  float t = uTime * uSpeed;
  // You can also use classic perlin noise or simplex noise,
  // I'm using its periodic variant out of curiosity
  float distortion = pnoise((normal + t), vec3(10.0) * uNoiseDensity) * uNoiseStrength;

  // Disturb each vertex along the direction of its normal
  vec3 pos = position + (normal * distortion);

  vNormal = normal;

  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}

And now we should see a blobby sphere:

See the Pen Vertex displacement by Mario (@marioecg) on CodePen.

You can experiment and change its values to see how the blob changes. I know we’re going with a more subtle and rounded distortion, but feel free to go crazy with it; there are audio visualizers out there that deform a sphere to the point that you don’t even think it’s based on a sphere.

Now, this already looks interesting, but let’s add one more touch to it next.

Noitation

…is just a word I came up with to combine noise with rotation (ba dum tss), but yes! Adding some twirl to the mix makes things more compelling.

If you’ve ever played with Play-Doh as a child, you have surely molded a big chunk of clay into a ball, grab it with each hand, and twisted in opposite directions until the clay tore apart. This is kind of what we want to do (except for the breaking part).

To twist the sphere, we are going to generate a sine wave from top to bottom of the sphere. Then, we are going to use this top-bottom wave as a rotation for the current position. Since the values increase/decrease from top to bottom, the rotation is going to oscillate as well, creating a twist:

varying vec3 vNormal;

uniform float uTime;
uniform float uSpeed;
uniform float uNoiseDensity;
uniform float uNoiseStrength;
uniform float uFrequency;
uniform float uAmplitude;

#pragma glslify: pnoise = require(glsl-noise/periodic/3d)
#pragma glslify: rotateY = require(glsl-rotate/rotateY)

void main() {
  float t = uTime * uSpeed;
  // You can also use classic perlin noise or simplex noise,
  // I'm using its periodic variant out of curiosity
  float distortion = pnoise((normal + t), vec3(10.0) * uNoiseDensity) * uNoiseStrength;

  // Disturb each vertex along the direction of its normal
  vec3 pos = position + (normal * distortion);

  // Create a sine wave from top to bottom of the sphere
  // To increase the amount of waves, we'll use uFrequency
  // To make the waves bigger we'll use uAmplitude
  float angle = sin(uv.y * uFrequency + t) * uAmplitude;
  pos = rotateY(pos, angle);    

  vNormal = normal;

  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}

Notice how the waves emerge from the top, it’s soothing. Some of you might find this movement therapeutic, so take some time to appreciate it and think about what we’ve learned so far…

See the Pen Noitation by Mario (@marioecg) on CodePen.

Alright! Now that you’re back let’s get on to the fragment shader.

Colorific

If you take a close look at the shaders before, you see, almost at the end, that we’ve been passing the normals to the fragment shader. Remember that we want to use the distortion to color the shape, so first let’s create a varying where we pass that distortion to:

varying float vDistort;

uniform float uTime;
uniform float uSpeed;
uniform float uNoiseDensity;
uniform float uNoiseStrength;
uniform float uFrequency;
uniform float uAmplitude;

#pragma glslify: pnoise = require(glsl-noise/periodic/3d)
#pragma glslify: rotateY = require(glsl-rotate/rotateY)

void main() {
  float t = uTime * uSpeed;
  // You can also use classic perlin noise or simplex noise,
  // I'm using its periodic variant out of curiosity
  float distortion = pnoise((normal + t), vec3(10.0) * uNoiseDensity) * uNoiseStrength;

  // Disturb each vertex along the direction of its normal
  vec3 pos = position + (normal * distortion);

  // Create a sine wave from top to bottom of the sphere
  // To increase the amount of waves, we'll use uFrequency
  // To make the waves bigger we'll use uAmplitude
  float angle = sin(uv.y * uFrequency + t) * uAmplitude;
  pos = rotateY(pos, angle);    

  vDistort = distortion; // Train goes to the fragment shader! Tchu tchuuu

  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}

And use vDistort to color the pixels instead:

varying float vDistort;

uniform float uIntensity;

void main() {
  float distort = vDistort * uIntensity;

  vec3 color = vec3(distort);

  gl_FragColor = vec4(color, 1.0);
}

We should get a kind of twisted, smokey black and white color like so:

See the Pen Colorific by Mario (@marioecg) on CodePen.

With this basis, we’ll take it a step further and use it in conjunction with one of my favorite color functions out there.

Cospalette

Cosine palette is a very useful function to create and control color with code based on the brightness, contrast, oscillation of cosine, and phase of cosine. I encourage you to watch Char Stiles explain this further, which is soooo good. Final s/o to Inigo Quilez who wrote an article about this function some years ago; for those of you who haven’t stumbled upon his genius work, please do. I would love to write more about him, but I’ll save that for a poem.

Let’s use cospalette to input the distortion and see how it looks:

varying vec2 vUv;
varying float vDistort;

uniform float uIntensity;

vec3 cosPalette(float t, vec3 a, vec3 b, vec3 c, vec3 d) {
  return a + b * cos(6.28318 * (c * t + d));
}   

void main() {
  float distort = vDistort * uIntensity;

  // These values are my fav combination, 
  // they remind me of Zach Lieberman's work.
  // You can find more combos in the examples from IQ:
  // https://iquilezles.org/www/articles/palettes/palettes.htm
  // Experiment with these!
  vec3 brightness = vec3(0.5, 0.5, 0.5);
  vec3 contrast = vec3(0.5, 0.5, 0.5);
  vec3 oscilation = vec3(1.0, 1.0, 1.0);
  vec3 phase = vec3(0.0, 0.1, 0.2);

  // Pass the distortion as input of cospalette
  vec3 color = cosPalette(distort, brightness, contrast, oscilation, phase);

  gl_FragColor = vec4(color, 1.0);
}

¡Liiistoooooo! See how the color palette behaves similar to the distortion because we’re using it as input. Swap it for vUv.x or vUv.y to see different results of the palette, or even better, come up with your own input!

See the Pen Cospalette by Mario (@marioecg) on CodePen.

And that’s it! I hope this short tutorial gave you some ideas to apply to anything you’re creating or inspired you to make something. Next time you use noise, stop and think if you can do something extra to make it more interesting and make sure to save Cospalette in your shader toolbelt.

Explore and have fun with this! And don’t forget to share it with me on Twitter. If you got any questions or suggestions, let me know.

I hope you learned something new. Till next time! 

References and Credits

Thanks to all the amazing people that put knowledge out in the world!

The post Twisted Colorful Spheres with Three.js appeared first on Codrops.

Creating an Infinite Auto-Scrolling Gallery using WebGL with OGL and GLSL Shaders

Hello everyone, introducing myself a little bit first, I’m Luis Henrique Bizarro, I’m a Senior Creative Developer at Active Theory based in São Paulo, Brazil. It’s always a pleasure to me having the opportunity to collaborate with Codrops to help other developers learn new things, so I hope everyone enjoys this tutorial!

In this tutorial I’ll explain you how to create an auto scrolling infinite image gallery. The image grid is also scrollable be user interaction, making it an interesting design element to showcase works. It’s based on this great animation seen on Oneshot.finance made by Jesper Landberg.

I’ve been using the technique of styling images first with HTML + CSS and then creating an abstraction of these elements inside WebGL using some camera and viewport calculations in multiple websites, so this is the approach we’re going to use in this tutorial.

The good thing about this implementation is that it can be reused across any WebGL library, so if you’re more familiar with Three.js or Babylon.js than OGL, you’ll also be able to achieve the same results using a similar code, when it’s about shading and scaling the plane meshes.

So let’s get into it!

Implementing our HTML markup

The first step is implementing our HTML markup. We’re going to use <figure> and <img> elements, nothing special here, just the standard:

<div class="demo-1__gallery">
  <figure class="demo-1__gallery__figure">
    <img class="demo-1__gallery__image" src="images/demo-1/1.jpg">
  </figure>

  <!-- Repeating the same markup until 12.jpg. -->
</div>

Setting our CSS styles

The second step is styling our elements using CSS. One of the first things I do in a website is defining the font-size of the html element because I use rem to help with the responsive breakpoints.

This comes in handy if you’re doing creative websites that only require two or three different breakpoints, so I highly recommend starting using it if you haven’t adopted rem yet.

One thing I’m also using is calc() with the size of the designs. In our tutorial we’re going to use 1920 as our main width, scaling our font-size depending on the screen size of 100vw. This results in 10px at a 1920px screen, for example:

html {
  font-size: calc(100vw / 1920 * 10);
}

Now let’s style our grid of images. We want to freely place our images across the screen using absolute positioning, so we’re just going to set the height, width and left/top styles across all our demo-1 classes:

.demo-1__gallery {
  height: 295rem;
  position: relative;
  visibility: hidden;
}

.demo-1__gallery__figure {
  position: absolute;
 
  &:nth-child(1) {
    height: 40rem;
    width: 70rem;
  }
 
  &:nth-child(2) {
    height: 50rem;
    left: 85rem;
    top: 30rem;
    width: 40rem;
  }
 
  &:nth-child(3) {
    height: 50rem;
    left: 15rem;
    top: 60rem;
    width: 60rem;
  }
 
  &:nth-child(4) {
    height: 30rem;
    right: 0;
    top: 10rem;
    width: 50rem;
  }
 
  &:nth-child(5) {
    height: 60rem;
    right: 15rem;
    top: 55rem;
    width: 40rem;
  }
 
  &:nth-child(6) {
    height: 75rem;
    left: 5rem;
    top: 120rem;
    width: 57.5rem;
  }
 
  &:nth-child(7) {
    height: 70rem;
    right: 0;
    top: 130rem;
    width: 50rem;
  }
 
  &:nth-child(8) {
    height: 50rem;
    left: 85rem;
    top: 95rem;
    width: 40rem;
  }
 
  &:nth-child(9) {
    height: 65rem;
    left: 75rem;
    top: 155rem;
    width: 50rem;
  }
 
  &:nth-child(10) {
    height: 43rem;
    right: 0;
    top: 215rem;
    width: 30rem;
  }
 
  &:nth-child(11) {
    height: 50rem;
    left: 70rem;
    top: 235rem;
    width: 80rem;
  }
 
  &:nth-child(12) {
    left: 0;
    top: 210rem;
    height: 70rem;
    width: 50rem;
  }
}
 
.demo-1__gallery__image {
  height: 100%;
  left: 0;
  object-fit: cover;
  position: absolute;
  top: 0;
  width: 100%;
}

Note that we’re hiding the visibility of our HTML, because it’s not going to be visible for the users since we’re going to load these images inside the <canvas> element. But below you can find a screenshot of what the result will look like.

Creating our OGL 3D environment

Now it’s time to get started with the WebGL implementation using OGL. First let’s create an App class that is going to be the entry point of our demo and inside of it, let’s also create the initial methods: createRenderer, createCamera, createScene, onResize and our requestAnimationFrame loop with update.

import { Renderer, Camera, Transform } from 'ogl'

class App {
  constructor () {
    this.createRenderer()
    this.createCamera()
    this.createScene()
 
    this.onResize()
 
    this.update()
 
    this.addEventListeners()
  }
 
  createRenderer () {
    this.renderer = new Renderer({
      alpha: true
    })
 
    this.gl = this.renderer.gl
 
    document.body.appendChild(this.gl.canvas)
  }
 
  createCamera () {
    this.camera = new Camera(this.gl)
    this.camera.fov = 45
    this.camera.position.z = 5
  }
 
  createScene () {
    this.scene = new Transform()
  }
 
  /**
   * Wheel.
   */
  onWheel (event) {
 
  }
 
  /**
   * Resize.
   */
  onResize () {
    this.screen = {
      height: window.innerHeight,
      width: window.innerWidth
    }
 
    this.renderer.setSize(this.screen.width, this.screen.height)
 
    this.camera.perspective({
      aspect: this.gl.canvas.width / this.gl.canvas.height
    })
 
    const fov = this.camera.fov * (Math.PI / 180)
    const height = 2 * Math.tan(fov / 2) * this.camera.position.z
    const width = height * this.camera.aspect
 
    this.viewport = {
      height,
      width
    }
  }
 
  /**
   * Update.
   */
  update () {
    this.renderer.render({
      scene: this.scene,
      camera: this.camera
    })
 
    window.requestAnimationFrame(this.update.bind(this))
  }
 
  /**
   * Listeners.
   */
  addEventListeners () {
    window.addEventListener('resize', this.onResize.bind(this))
 
    window.addEventListener('mousewheel', this.onWheel.bind(this))
    window.addEventListener('wheel', this.onWheel.bind(this))
  }
}

new App()

Explaining some part of our App.js file

In our createRenderer method, we’re initializing one renderer with alpha enabled, storing our GL context (this.renderer.gl) reference in the this.gl variable and appending our <canvas> element to our document.body.

In our createCamera method, we’re just creating a new Camera and setting some of its attributes: fov and its z position.

In our createScene method, we’re using the Transform class, that is the representation of a new scene that is going to contain all our planes that represent our images in the WebGL environment.

The onResize method is the most important part of our initial setup. It’s responsible for three different things:

  1. Making sure we’re always resizing the <canvas> element with the correct viewport sizes.
  2. Updating our this.camera perspective dividing the width and height of the viewport.
  3. Storing in the variable this.viewport, the value representations that will help to transform pixels into 3D environment sizes by using the fov from the camera.

The approach of using the camera.fov to transform pixels in 3D environment sizes is an approach used very often in multiple WebGL implementations. Basically what it does is making sure that if we do something like: this.mesh.scale.x = this.viewport.width; it’s going to make our mesh fit the entire screen width, behaving like width: 100%, but in 3D space.

And finally in our update, we’re setting our requestAnimationFrame loop and making sure we keep rendering our scene.

Create our reusable geometry instance

It’s a good practice to keep memory usage low by always reusing the same geometry reference no matter what WebGL library you’re using. To represent all our images, we’re going to use a Plane geometry, so let’s create a new method and store this new geometry inside the this.planeGeometry variable.

import { Renderer, Camera, Transform, Plane } from 'ogl'
 
createGeometry () {
  this.planeGeometry = new Plane(this.gl)
}

Select all images and create a new class for each one

Now it’s time to use document.querySelector to select all our images and create one reusable class that is going to represent our images. (We’re going to create a single Media.js file later.)

createMedias () {
  this.mediasElements = document.querySelectorAll('.demo-1__gallery__figure')
  this.medias = Array.from(this.mediasElements).map(element => {
    let media = new Media({
      element,
      geometry: this.planeGeometry,
      gl: this.gl,
      scene: this.scene,
      screen: this.screen,
      viewport: this.viewport
    })
 
    return media
  })
}

As you can see, we’re just selecting all .demo-1__gallery__figure elements, going through them and generating an array of `this.medias` with new instances of Media.

Now it’s important to start attaching this array in important pieces of our setup code.

Let’s first include all our media inside the method onResize and also call media.onResize for each one of these new instances:

if (this.medias) {
  this.medias.forEach(media => media.onResize({
    screen: this.screen,
    viewport: this.viewport
  }))
}

And inside our update method, we’re going to call media.update() as well:

if (this.medias) {
  this.medias.forEach(media => media.update())
}

Setting up our Media.js file and class

Our Media class is going to use Mesh, Program and Texture classes from OGL to create a 3D plane and attribute a texture to it, which in our case is going to be our images.

In our constructor, we’re going to store all variables that we need and that were passed in the new Media() initialization from index.js:

import { Mesh, Program, Texture } from 'ogl'
 
import fragment from 'shaders/fragment.glsl'
import vertex from 'shaders/vertex.glsl'
 
export default class {
  constructor ({ element, geometry, gl, scene, screen, viewport }) {
    this.element = element
    this.image = this.element.querySelector('img')
 
    this.geometry = geometry
    this.gl = gl
    this.scene = scene
    this.screen = screen
    this.viewport = viewport
 
    this.createMesh()
    this.createBounds()
 
    this.onResize()
  }
}

In our createMesh method, we’ll load the image texture using the this.image.src attribute, then create a new Program, which is basically a representation of the material we’re applying to our Mesh. So our method looks like this:

createMesh () {
  const image = new Image()
  const texture = new Texture(this.gl)

  image.src = this.image.src
  image.onload = _ => {
    texture.image = image
  }

  const program = new Program(this.gl, {
    fragment,
    vertex,
    uniforms: {
      tMap: { value: texture },
      uScreenSizes: { value: [0, 0] },
      uImageSizes: { value: [0, 0] }
    },
    transparent: true
  })

  this.plane = new Mesh(this.gl, {
    geometry: this.geometry,
    program
  })

  this.plane.setParent(this.scene)
}

Looks pretty simple, right? After we generate a new Mesh, we’re setting the plane as children of this.scene, so we’re including our mesh inside our main scene.

As you’ve probably noticed, our Program receives fragment and vertex. These both represent the shaders we’re going to use on our planes. For now, we’re just using simple implementations of both.

In our vertex.glsl file we’re getting the uv and position attributes, and making sure we’re rendering our planes in the right 3D world position.

attribute vec2 uv;
attribute vec3 position;
 
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
 
varying vec2 vUv;
 
void main() {
  vUv = uv;
 
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

In our fragment.glsl file, we’re receiving a tMap texture, as you can see in the tMap: { value: texture } declaration, and rendering it in our plane geometry:

precision highp float;
 
uniform sampler2D tMap;
 
varying vec2 vUv;
 
void main() {
  gl_FragColor.rgb = texture2D(tMap, vUv).rgb;
  gl_FragColor.a = 1.0;
}

The createBounds method is important to make sure we’re positioning and scaling our planes in the correct DOM elements positions, so it’s basically going to call for this.element.getBoundingClientRect() to get the right position of our planes, and then after that using these values to calculate the 3D values of our plane.

createBounds () {
  this.bounds = this.element.getBoundingClientRect()

  this.updateScale()
  this.updateX()
  this.updateY()
}

updateScale () {
  this.plane.scale.x = this.viewport.width * this.bounds.width / this.screen.width
  this.plane.scale.y = this.viewport.height * this.bounds.height / this.screen.height
}

updateX (x = 0) {
  this.plane.position.x = -(this.viewport.width / 2) + (this.plane.scale.x / 2) + ((this.bounds.left - x) / this.screen.width) * this.viewport.width
}

updateY (y = 0) {
  this.plane.position.y = (this.viewport.height / 2) - (this.plane.scale.y / 2) - ((this.bounds.top - y) / this.screen.height) * this.viewport.height
}

update (y) {
  this.updateScale()
  this.updateX()
  this.updateY(y)
}

As you’ve probably noticed, the calculations for scale.x and scale.y are going to stretch our plane to make it the same width and height of the <img> elements. And the position.x and position.y takes the offset from the element and makes our translate our planes to the correct x and y axis in 3D.

And let’s not forget our onResize method, which is basically going to call createBounds again to refresh our getBoundingClientRect values and make sure we keep our 3D implementation responsive as well.

onResize (sizes) {
  if (sizes) {
    const { screen, viewport } = sizes
 
    if (screen) this.screen = screen
    if (viewport) this.viewport = viewport
  }
 
  this.createBounds()
}

This is the result we’ve got so far.

Implement cover behavior in fragment shaders

As you’ve probably noticed, our images are stretched. It happens because we need to make proper calculations in the fragment shaders in order to have a behavior like object-fit: cover; or background-size: cover; in WebGL.

I like to use an approach to pass the images’ real sizes and do some ratio calculations inside the fragment shader, so let’s adapt our code to this approach. So in our Program, we’re going to pass two new uniforms called uPlaneSizes and uImageSizes:

const program = new Program(this.gl, {
  fragment,
  vertex,
  uniforms: {
    tMap: { value: texture },
    uPlaneSizes: { value: [0, 0] },
    uImageSizes: { value: [0, 0] }
  },
  transparent: true
})

Now we need to update our fragment.glsl and use these values to calculate our images ratios:

precision highp float;
 
uniform vec2 uImageSizes;
uniform vec2 uPlaneSizes;
uniform sampler2D tMap;
 
varying vec2 vUv;
 
void main() {
  vec2 ratio = vec2(
    min((uPlaneSizes.x / uPlaneSizes.y) / (uImageSizes.x / uImageSizes.y), 1.0),
    min((uPlaneSizes.y / uPlaneSizes.x) / (uImageSizes.y / uImageSizes.x), 1.0)
  );
 
  vec2 uv = vec2(
    vUv.x * ratio.x + (1.0 - ratio.x) * 0.5,
    vUv.y * ratio.y + (1.0 - ratio.y) * 0.5
  );
 
  gl_FragColor.rgb = texture2D(tMap, uv).rgb;
  gl_FragColor.a = 1.0;
}

And then we also need update our image.onload method to pass naturalWidth and naturalHeight to uImageSizes:

image.onload = _ => {
  program.uniforms.uImageSizes.value = [image.naturalWidth, image.naturalHeight]
  texture.image = image
}

And createBounds to update the uPlaneSizes uniforms:

createBounds () {
  this.bounds = this.element.getBoundingClientRect()
 
  this.updateScale()
  this.updateX()
  this.updateY()
 
  this.plane.program.uniforms.uPlaneSizes.value = [this.plane.scale.x, this.plane.scale.y]
}

That’s it! Now we have properly scaled images.

Implementing smooth scrolling

Before we implement our infinite logic, it’s good to start making scrolling work properly. In our setup code, we have included a onWheel method, which is going to be used to lerp some variables and make our scroll butter smooth.

In our constructor from index.js, let’s create the this.scroll object with these variables:

this.scroll = {
  ease: 0.05,
  current: 0,
  target: 0,
}

Now let’s update our onWheel implementation. When working with wheel events, it’s always important to normalize it, because it behaves differently based on the browser, I’ve been using normalize-wheel library to help on it:

import NormalizeWheel from 'normalize-wheel'

onWheel (event) {
  const normalized = NormalizeWheel(event)
  const speed = normalized.pixelY
 
  this.scroll.target += speed * 0.5
}

Let’s also create our lerp utility function inside the file utils/math.js:

export function lerp (p1, p2, t) {
  return p1 + (p2 - p1) * t
}

And now we just need to lerp from the this.scroll.current to the this.scroll.target inside the update method. And finally pass it to the media.update() methods:

update () {
  this.scroll.current = lerp(this.scroll.current, this.scroll.target, this.scroll.ease)
 
  if (this.medias) {
    this.medias.forEach(media => media.update(this.scroll.current))
  }
}

After that we already have a result like this.

Making our smooth scrolling infinite

The approach of making an infinite scrolling logic is basically repeating the same grid over and over while the user keeps scrolling your page. Since the user can scroll up or down, you also need to take under consideration what direction is being scrolled, so overall the algorithm should work this way:

  • If you’re scrolling down, your elements move up — when your first element isn’t on the screen anymore, you should move it to the end of the list.
  • If you’re scrolling up, your elements move to down — when your last element isn’t on the screen anymore, you should move it to the start of the list.

To explain it in a visual way, let’s say we’re scrolling down and the red area is our viewport and the blue elements are not in the viewport anymore.

When we are in this state, we just need to move the blue elements to the end of our gallery grid, which is the entire height of our gallery: 295rem.

Let’s include the logic for it then. First, we need to create a new variable called this.scroll.last to store the last value of our scroll, this is going to be checked to give us up or down strings:

this.scroll = {
  ease: 0.05,
  current: 0,
  target: 0,
  last: 0
}

In our update method, we need to include the following lines of validations and pass this.direction to our this.medias elements.

update () {
  this.scroll.current = lerp(this.scroll.current, this.scroll.target, this.scroll.ease)
 
  if (this.scroll.current > this.scroll.last) {
    this.direction = 'down'
  } else if (this.scroll.current < this.scroll.last) {
    this.direction = 'up'
  }
 
  if (this.medias) {
    this.medias.forEach(media => media.update(this.scroll.current, this.direction))
  }
 
  this.renderer.render({
    scene: this.scene,
    camera: this.camera
  })
 
  this.scroll.last = this.scroll.current
 
  window.requestAnimationFrame(this.update.bind(this))
}

Then we need to get the total gallery height and transform it to 3D dimensions, so let’s include a querySelector of .demo-1__gallery and call the createGallery method in our index.js constructor.

createGallery () {
  this.gallery = document.querySelector('.demo-1__gallery')
}

It’s time to do the real calculations using this selector, so in our onResize method, we need to include the following lines:

this.galleryBounds = this.gallery.getBoundingClientRect()
this.galleryHeight = this.viewport.height * this.galleryBounds.height / this.screen.height

The this.galleryHeight variable now is storing the 3D size of the entire grid, now we need to pass it to both onResize and new Media() calls:

if (this.medias) {
  this.medias.forEach(media => media.onResize({
    height: this.galleryHeight,
    screen: this.screen,
    viewport: this.viewport
  }))
}
this.medias = Array.from(this.mediasElements).map(element => {
  let media = new Media({
    element,
    geometry: this.planeGeometry,
    gl: this.gl,
    height: this.galleryHeight,
    scene: this.scene,
    screen: this.screen,
    viewport: this.viewport
  })
 
  return media
})

And then inside our Media class, we need to store the height as well in the constructor and also in the onResize methods:

constructor ({ element, geometry, gl, height, scene, screen, viewport }) {
  this.height = height
}
onResize (sizes) {
  if (sizes) {
    const { height, screen, viewport } = sizes
 
    if (height) this.height = height
    if (screen) this.screen = screen
    if (viewport) this.viewport = viewport
  }
}

Now we’re going to include the logic to move our elements based on their viewport position, just like our visual representation of the red and blue rectangles.

If the idea is to keep summing up a value based on the scroll and element position, we can achieve this by just creating a new variable called this.extra = 0, this is going to store how much we need to sum (or subtract) of our media, so in our constructor let’s include it:

constructor ({ element, geometry, gl, height, scene, screen, viewport }) {
    this.extra = 0
}

And let’s reset it on resizing the browser, to make all values consistent so it doesn’t break when users resizes their viewport:

onResize (sizes) {
  this.extra = 0
}

And in our updateY method, we’re going to include it as well:

updateY (y = 0) {
  this.plane.position.y = ((this.viewport.height / 2) - (this.plane.scale.y / 2) - ((this.bounds.top - y) / this.screen.height) * this.viewport.height) - this.extra
}

Finally, the only thing left now is updating the this.extra variable inside our update method, making sure we’re adding or subtracting the this.height depending on the direction.

const planeOffset = this.plane.scale.y / 2
const viewportOffset = this.viewport.height / 2
 
this.isBefore = this.plane.position.y + planeOffset < -viewportOffset
this.isAfter = this.plane.position.y - planeOffset > viewportOffset
 
if (direction === 'up' && this.isBefore) {
  this.extra -= this.height
 
  this.isBefore = false
  this.isAfter = false
}
 
if (direction === 'down' && this.isAfter) {
  this.extra += this.height
 
  this.isBefore = false
  this.isAfter = false
}

Since we’re working in 3D space, we’re dealing with cartesian coordinates, that’s why you can notice we’re dividing most things by two (ex: this.viewport.heighht / 2). So that’s also the reason why we had to do a different logic for the this.isBefore and this.isAfter checks.

Awesome, we’re almost finishing our demo! That’s how it looks now, pretty cool to have it endless right?

Including touch events

Let’s also include touch events, so this demo can be more responsive to user interactions! In our addEventListeners method, let’s include some window.addEventListener calls:

window.addEventListener('mousedown', this.onTouchDown.bind(this))
window.addEventListener('mousemove', this.onTouchMove.bind(this))
window.addEventListener('mouseup', this.onTouchUp.bind(this))
 
window.addEventListener('touchstart', this.onTouchDown.bind(this))
window.addEventListener('touchmove', this.onTouchMove.bind(this))
window.addEventListener('touchend', this.onTouchUp.bind(this))

Then we just need to implement simple touch events calculations, including the three methods: onTouchDown, onTouchMove and onTouchUp.

onTouchDown (event) {
  this.isDown = true

  this.scroll.position = this.scroll.current
  this.start = event.touches ? event.touches[0].clientY : event.clientY
}

onTouchMove (event) {
  if (!this.isDown) return

  const y = event.touches ? event.touches[0].clientY : event.clientY
  const distance = (this.start - y) * 2

  this.scroll.target = this.scroll.position + distance
}

onTouchUp (event) {
  this.isDown = false
}

Done! Now we also have touch events support enabled for our gallery.

Implementing direction-aware auto scrolling

Let’s also implement auto scrolling to make our interaction even better. In order to achieve that we just need to create a new variable that will store our speed based on the direction the user is scrolling.

So let’s create a variable called this.speed in our index.js file:

constructor () {
  this.speed = 2
}

This variable is going to be changed by our down and up validations we have in our update loop, so if the user is scrolling down, we’re going to keep the speed as 2, if the user is scrolling up, we’re going to replace it with -2, and before that we will sum this.speed to the this.scroll.target variable:

update () {
  this.scroll.target += this.speed

  this.scroll.current = lerp(this.scroll.current, this.scroll.target, this.scroll.ease)

  if (this.scroll.current > this.scroll.last) {
    this.direction = 'down'
    this.speed = 2
  } else if (this.scroll.current < this.scroll.last) {
    this.direction = 'up'
    this.speed = -2
  }
}

Implementing distortion shaders

Now let’s make everything even more interesting, it’s time to play a little bit with shaders and distort our planes while the user is scrolling through our page.

First, let’s update our update method from index.js, making sure we expose both current and last scroll values to all our medias, we’re going to do a simple calculation with them.

update () {
  if (this.medias) {
    this.medias.forEach(media => media.update(this.scroll, this.direction))
  }
}

And now let’s create two uniforms for our Program shader: uOffset and uViewportSizes, and pass them:

const program = new Program(this.gl, {
  fragment,
  vertex,
  uniforms: {
    tMap: { value: texture },
    uPlaneSizes: { value: [0, 0] },
    uImageSizes: { value: [0, 0] },
    uViewportSizes: { value: [this.viewport.width, this.viewport.height] },
    uStrength: { value: 0 }
  },
  transparent: true
})

As you can probably notice, we’re going to need to set uViewportSizes in our onResize method as well, since this.viewport changes when we resize, so to keep this.viewport.width and this.viewport.height up to date, we also need to include the following lines of code in onResize:

onResize (sizes) {
  if (sizes) {
    const { height, screen, viewport } = sizes
 
    if (height) this.height = height
    if (screen) this.screen = screen
    if (viewport) {
      this.viewport = viewport
 
      this.plane.program.uniforms.uOffset.value = [this.viewport.width, this.viewport.height]
    }
  }
}

Remember the this.scroll update we’ve made from index.js? Now it’s time to include a small trick to generate a speed value inside our Media.js:

update (y, direction) {
  this.updateY(y.current)
 
  this.plane.program.uniforms.uStrength.value = ((y.current - y.last) / this.screen.width) * 10
}

We’re basically checking the difference between the current and last values, which returns us some kind of “speed” of the scrolling, and dividing it by the this.screen.width, to keep our effect value behaving correctly independently of the width of our screen.

Finally now it’s time to play a little bit with our vertex shader. We’re going to bend our planes a little bit while the user is scrolling through the page. So let’s update our vertex.glsl file with this new code:

#define PI 3.1415926535897932384626433832795
 
precision highp float;
precision highp int;
 
attribute vec3 position;
attribute vec2 uv;
 
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
 
uniform float uStrength;
uniform vec2 uViewportSizes;
 
varying vec2 vUv;
 
void main() {
  vec4 newPosition = modelViewMatrix * vec4(position, 1.0);
 
  newPosition.z += sin(newPosition.y / uViewportSizes.y * PI + PI / 2.0) * -uStrength;
 
  vUv = uv;
 
  gl_Position = projectionMatrix * newPosition;
}

That’s it! Now we’re also bending our images creating an unique type of effect!

Explaining a little bit of the shader logic: basically what’s implemented in the newPosition.z line is taking into consideration the uViewportSize.y, which is our height from the viewport and the current position.y of our plane, getting the division of both and multiplying by PI that we defined on the very top of our shader file. And then we use the uStrength which is the strength of the bending, that is tight with our scrolling values, making it bend based on how faster you scroll the demo.

That’s the final result of our demo! I hope this tutorial was useful to you and don’t forget to comment if you have any questions!

Photography used in the demos by Planete Elevene and Jayson Hinrichsen.

The post Creating an Infinite Auto-Scrolling Gallery using WebGL with OGL and GLSL Shaders appeared first on Codrops.

Distorted Infinite WebGL Slider with Pixi.js

Editor’s note: We want to share more of the web dev and design community directly here on Codrops, so we’re very happy to start featuring Yuriy’s newest live coding sessions!

In this live stream of ALL YOUR HTML, I’ll show you how to create an infinite scrollable image slider with Pixi.js. It’s based on the website of Lamadone Studio and we’ll also be adding some distortions on scroll. The website was developed by the team of Socialclub.

This coding session was streamed live on November 1, 2020.

Original website: Lamadone Studio

Developers of original website: Socialclub

Support: https://www.patreon.com/allyourhtml

Setup: https://gist.github.com/akella/a19954…

The post Distorted Infinite WebGL Slider with Pixi.js appeared first on Codrops.

Exploring Animations for Menu Hover Effects

Last week I showed you how we can create a fancy menu hover animation with images. Today I’d like to share some of the interesting effects we can achieve with that technique. Tuning some variables, timings and filters, the possibilities are really endless, so I invite you to explore some of the things we can come up with in the these couple of demos. Don’t forget to move your mouse along the link to see some jazzy motion and filters in action 🙂

I really hope you enjoy these playful animations!

The post Exploring Animations for Menu Hover Effects appeared first on Codrops.

Interactive WebGL Hover Effects

I love WebGL, and in this article I will explain one of the cool effects you can make if you master shaders. The effect I want to recreate is originally from Jesper Landberg’s website. He’s a really cool dude, make sure to check out his stuff:

So let’s get to business! Let’s start with this simple HTML:

<div class="item">
    <img src="img.jpg" class="js-image" alt="">
    <h2>Some title</h2>
    <p>Lorem ipsum.</p>
</div>
<script src="app.js"></script>

Couldn’t be any easier! Let’s style it a bit to look prettier:

All the animations will happen in a Canvas element. So now we need to add a bit of JavaScript. I’m using Parcel here, as it’s quite simple to get started with. I’ll use Three.js for the WebGL part.

So let’s add some JavaScript and start with a basic Three.js setup from the official documentation:

import * as THREE from "three";

var scene = new THREE.Scene();
var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 );

var renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );


camera.position.z = 5;

var animate = function () {
	requestAnimationFrame( animate );

	cube.rotation.x += 0.01;
	cube.rotation.y += 0.01;

	renderer.render( scene, camera );
};

animate();

Let’s style the Canvas element:

body { margin: 0; }

canvas { 
	display: block; 
	position: fixed;
	z-index: -1; // put it to background
	left: 0; // position it to fill the whole screen
	top: 0; // position it to fill the whole screen
}

Once you have all this in place, you can just run it with `parcel index.html`. Now, you wouldn’t see much, its an empty 3D scene so far. Let’s leave the HTML for a moment, and concentrate on the 3D scene for now.

Let’s create a simple PlaneBufferGeometry object with an image on it. Just like this:

let TEXTURE = new TextureLoader().load('supaAmazingImage.jpg'); 
let mesh = new Mesh(
	new PlaneBufferGeometry(), 
	new MeshBasicMaterial({map: TEXTURE})
)

And now we’ll see the following:

Obviously we are not there yet, we need that color trail following our mouse. And of course, we need shaders for that. If you are interested in shaders, you’ve probably come across some tutorials on how to displace images, like displacing on hover or liquid distortion effects.

But we have a problem: we can only use shaders on (and inside) that image from the example above. But the effect is not constrained to any image borders, but rather, it’s fluid, covering more area, like the whole screen.

Postprocessing to the rescue

It turns out that the output of the Three.js renderer is just another image. We can make use of that and apply the shader displacement on that output!

Here is the missing part of the code:

// set up post processing
let composer = new EffectComposer(renderer);
let renderPass = new RenderPass(scene, camera);
// rendering our scene with an image
composer.addPass(renderPass);

// our custom shader pass for the whole screen, to displace previous render
let customPass = new ShaderPass({vertexShader,fragmentShader});
// making sure we are rendering it.
customPass.renderToScreen = true;
composer.addPass(customPass);

// actually render scene with our shader pass
composer.render()
// instead of previous
// renderer.render(scene, camera);

There are a bunch of things happening here, but it’s pretty straightforward: you apply your shader to the whole screen.

So let’s do that final shader with the effect:

// get small circle around mouse, with distances to it
float c = circle(uv, mouse, 0.0, 0.2);
// get texture 3 times, each time with a different offset, depending on mouse speed:
float r = texture2D(tDiffuse, uv.xy += (mouseVelocity * .5)).x;
float g = texture2D(tDiffuse, uv.xy += (mouseVelocity * .525)).y;
float b = texture2D(tDiffuse, uv.xy += (mouseVelocity * .55)).z;
// combine it all to final output
color = vec4(r, g, b, 1.);

You can see the result of this in the first demo.

Applying the effect to several images

A screen has its size, and so do images in 3D. So what we need to do now is to calculate some kind of relation of those two.

Just like I did in my previous article, we can make a plane with a width of 1, and fit it exactly to the screen width. So practically, we have WidthOfPlane=ScreenSize.

For our Three.js scene, this means that if want an image with a width of 100px on the screen, we will make a Three.js object with width of 100*(WidthOfPlane/ScreenSize). That’s it! With this kind of math we can also set some margins and positions easily.

When the page loads, I will loop through all the images, get their dimensions, and add them to my 3D world:

let images = [...document.querySelectorAll('.js-image')];
images.forEach(image=>{
	// and we have the width, height and left, top position of the image now!
	let dimensions = image.getBoundingClientRect();
	// hide original image
	image.style.visibility = hidden;
	// add 3D object to your scene, according to its HTML brother dimensions
	createMesh(dimensions);
})

Now it’s quite straightforward to make this HTML-3D hybrid.

Another thing that I added here is mouseVelocity. I used it to change the radius of the effect. The faster the mouse moves, the bigger the radius.

To make it scrollable, we would just need to move the whole scene, the same amount that the screen was scrolled. Using that same formula I mentioned before: NumberOfPixels*(WidthOfPlane/ScreenSize).

Sometimes it’s even easier to make WidthOfPlane equal to ScreenSize. That way, you end up with exactly the same numbers in both worlds!

Exploring different effects

With different shaders you can come up with any kind of effect with this approach. So I decided to play a little bit with the parameters.

Instead of separating the image in three color layers, we could simply displace it depending on the distance to the mouse:

vec2 newUV = mix(uv, mouse, circle); 
color = texture2D(tDiffuse,newUV);

And for the last effect I used some randomness, to get a pixelated effect around the mouse cursor.

In this last demo you can switch between effects to see some modifications you can make. With the “zoom” effect, I just use a displacement, but in the last one, I also randomize the pixels, which looks kinda cool to me!

I’d be happy to see your ideas for this animation. What kind of effect would you do with this technique?

Interactive WebGL Hover Effects was written by Yuriy Artyukh and published on Codrops.

Animated Custom Cursor Effects

So I have been playing with distortion effects using SVG filters recently and wanted to now try and apply these to a custom cursor. Imagine animating a circular custom cursor with those distortions when hovering over links (or any other element). Here are four demos that explore this idea.

The effects are done by applying SVG filters to a custom cursor element which is an SVG. Besides animating the cursor itself (scaling it up), the SVG filter is animated when hovering over anchors (or any other element you’d like this to interact with).

If you are interested in more of these kind of effects, have a look some previous related experiments:

I really hope you enjoy these and can make use of them! As always, feel free to use the designs in your projects.

Show me what you come up with and ping me @codrops!

Credits

Animated Custom Cursor Effects was written by Mary Lou and published on Codrops.

Ideas for Distorted Link Effects on Menus

After exploring some distortion effects on line elements for links, I couldn’t wait but try them on some big style menus. So here is a little set of demos that shows some ideas for how to use those distortion effects. They were a great excuse to play with some of my favorite typefaces available on Adobe Fonts including Bely Display, Freight and Rigatoni.

How about a tiny squiggly line under an outlined text:

Or some distortion on a thick line with a gradient:

Or maybe two lines hugging the lovely Rigatoni typeface:

Let’s put that swirly line in front of a sexy serif:

Or be bold and make a strong contrasty box with a wave inside:

Check out all the demos here.

I really hope you enjoy these styles and find them useful!

Please share your creations with me on Twitter @codrops!

Ideas for Distorted Link Effects on Menus was written by Mary Lou and published on Codrops.

Create a Wave Motion Effect on an Image with Three.js

Waves! Because who does not enjoy the visual comfort an oscillating motion has on the human eye? Well, I do and for this tutorial, I would like to explain how to make waves on a 3D plane with Three.js using simplex noise.

To keep things short, we’ll just focus on the plane effect and not on the smooth scrolling or setup required to synchronize the DOM with WebGL. For these, check out Jesper Landberg’s codepen on smooth scrolling, and Luigi De Rosa’s article on how EPIC mixed WebGL and the DOM for WeCargo.

We’ll asume you have some basic understanding of Three.js, vertex and fragment shaders, so we’ll skip things like how to set up a scene.

Now let’s begin.

Creating the mesh

First, we’ll create a mesh using a PlaneGeometry and a ShaderMaterial.

Since we want to add a texture later, we’ll give out PlaneGeometry the same proportions as our 400×600 image. So, 0.4 for the width and 0.6 for the height. Having the same proportions makes it so the texture doesn’t stretch.

this.geometry = new THREE.PlaneGeometry(0.4, 0.6, 16, 16);
this.material = new THREE.ShaderMaterial({
  vertexShader,
  fragmentShader,
  uniforms: {
    uTime: { value: 0.0 }
  },
  wireframe: true,
});
this.mesh = new THREE.Mesh(this.geometry, this.material);
this.scene.add(this.mesh);

As a performance side note, if you are rendering more than one element to a scene, make sure to reuse the geometry object.

Making some noise

We’re going to displace the plane using 3D simplex noise.

Our simplex noise function snoise, outputs values between -1 and 1 based on the position values you give it.

Simplex noise is amazing because it’s random and seamless! So it’s really useful to create organic patterns. Which is exactly what we want for our wave effect.

Now inside the vertex shader. Using the vertex positions of the plane, we’re going to sample simplex noise to get a wave distortion, adjusting its frequency and amplitude to control it. The frequency will only change the x vector component to make horizontal waves on the plane and adding a time uniform to make them move. For the distortion to take place forwards and backwards, we need to add the value to the z vector component.

varying vec2 vUv;
uniform float uTime;

void main() {
  vUv = uv;

  vec3 pos = position;
  float noiseFreq = 3.5;
  float noiseAmp = 0.15; 
  vec3 noisePos = vec3(pos.x * noiseFreq + uTime, pos.y, pos.z);
  pos.z += snoise(noisePos) * noiseAmp;

  gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.);
}

And that’s really it for the motion! Even though it’s moving, the effect doesn’t quite sell because the inside looks flat.

To give that extra flare, as it is displaced, we need to modify colors accordingly. But before we do that, let’s add some pretty image!

Adding the texture

First, let’s create a new uniform that holds the texture for the ShaderMaterial.

this.material = new THREE.ShaderMaterial({
  vertexShader,
  fragmentShader,
  uniforms: {
    uTime: { value: 0.0 },
    uTexture: { value: new THREE.TextureLoader().load(img) },
  },
});

Second, let’s sample the texture in the fragment shader.

varying vec2 vUv;
uniform sampler2D uTexture;

void main() {
  vec3 texture = texture2D(uTexture, vUv).rgb;
  gl_FragColor = vec4(texture, 1.);
}

It looks solid already. Cool! Let’s go a step further and make it even more interesting by displacing the texture coordinates.

Displacing the texture coordinates

We can use the noise value from the vertex shader, pass it to the fragment shader, and add it to the texture coordinates so the image follows the motion of the displacement.

To share the distortion data from the vertex shader to the fragment shader we’ll use a varying, and for now let’s disable the vertex movement so we can appreciate more the texture displacement.

varying vec2 vUv;
varying float vWave;
uniform float uTime;

void main() {
  vUv = uv;

  vec3 pos = position;
  float noiseFreq = 3.5;
  float noiseAmp = 0.15; 
  vec3 noisePos = vec3(pos.x * noiseFreq + uTime, pos.y, pos.z);
  pos.z += snoise(noisePos) * noiseAmp;
  vWave = pos.z; // Off it goes!

  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.);
}

A new variable inside the fragment shader will be needed to scale down the distortion so it’s not too sharp.

varying vec2 vUv;
varying float vWave;
uniform sampler2D uTexture;

void main() {
  float wave = vWave * 0.2;
  vec3 texture = texture2D(uTexture, vUv + wave).rgb;
  gl_FragColor = vec4(texture, 1.);
}

See how the texture moves in the same way but in two dimensions? What if instead of moving all the texture coordinates, we only did it for one color channel to give a more futuristic vibe?

Splitting the color channel

To move one color channel, we’ll need to separate each color vector of the texture, add the wave value to one of their coordinates, and combine them back together. I’ll try it on the blue channel, but you can experiment with any of them.

varying vec2 vUv;
varying float vWave;
uniform sampler2D uTexture;

void main() {
  float wave = vWave * 0.2;
  // Split each texture color vector
  float r = texture2D(uTexture, vUv).r;
  float g = texture2D(uTexture, vUv).g;
  float b = texture2D(uTexture, vUv + wave).b;
  // Put them back together
  vec3 texture = vec3(r, g, b);
  gl_FragColor = vec4(texture, 1.);
}

Now the movement is happening just on blue channel of the texture. Spooky!

Finally, we can enable again the vertex distortion to get the final result. Aaaand here it is:

I hope you made it until here! This is just one of countless possibilities you can do with shaders and Three.js. For example, in the second demo, I took a different approach making transitions using displacement maps — if you’re interested in it, perhaps we can leave that for another time.

Make sure to tweak the values to get different results, mess with the noise, or use another kind of noise (psst psst Worley), create something and have fun with it! Oh, and don’t forget to share it with me on Twitter. If you got any questions or suggestions, please let me know, too.

Hope you learned something new. Until next time!

References and Credits

Create a Wave Motion Effect on an Image with Three.js was written by Mario Carrillo and published on Codrops.

Distorted Link Effects with SVG Filters

Today we’d like to share some ideas for distorted link effects with you. Inspired by Adrien Denat’s Distorted Button Effects with SVG Filters, we wanted to explore some effects on links, specifically lines, using SVG Filters. There’s lots to explore here, so we’d made a simple demo that shows a couple of these effects.

If you’d like to understand the underlying mechanism of how an SVG filter effect works, we recommend checking out Adrien’s article. And as always, we highly recommend Sara’s series on SVG filters.

Using underlines, circles and squares creates fun results. You can apply the SVG filter to any HTML elements, including the link text itself (see the last example).

We hope you enjoy these effects and get inspired!

Distorted Link Effects with SVG Filters was written by Mary Lou and published on Codrops.

Audio-based Image Distortion Effects with WebGL

We’ve covered in the past how we can read data from Audio, using the p5.sound library and how we can use that data, to draw things in the canvas, using p5.js.

Well, what if instead of drawing a sketch, we used audio to distort an image? Today we want to show you some demos that play around that idea.

We’ve created some experiments using the theme of movie trailers where the background image of the movie poster is being distorted using a sound sample. It kind of adds some drama to an otherwise static image in this case.

Here’s a short video of the beginning of one of the effects:

How it works

We analyze the sound and map the range of frequencies, to some uniforms we pass in our fragment shader. Then depending on the effect/distortion we have, we can tweak different parameters, using the audio frequencies which constantly change overtime.

In our first demo, we create a simple sinewave in our fragment shader, by using the bass frequencies of the audio track to control its frequency and the mid frequencies to control its amplitude. Then we add the distortion in both axes (x & y) of our uv and add that distortion to the initial texture coordinates.

It looks like this:


  float wave = sin(uv.y * u_bass + u_time) * u_mid;
  vec2 d = vec2(wave); // could be vec2(wave, 0.0) or vec2(0.0, wave) for distortion only in 1 axis.
  vec4 image = texture2D(u_texture, uv + d);
  gl_FragColor = image;

The possibilities are endless if you want to play around that idea, it’s just a matter of what effect you’re after. Make sure you’re mapping values to your uniforms, that are within a range that can distort your visual, and you can always use some generic uniforms like u_time, that can put some ‘overdrive’ to your distortion.

Head over to the demos and check out the variations we’ve made.

Hope you’ll have fun with this one and be sure to share any of your own versions!

Reference & Credits

Audio-based Image Distortion Effects with WebGL was written by Yannis Yannakopoulos and published on Codrops.