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.

Ripple Effect on a Texture with Three.js

In this ALL YOUR HTML coding session you will learn how to recreate the interesting ripple effect seen on the homunculus.jp website with Three.js. We’ll have a look at render targets and use a little bit of math.

This coding session was streamed live on November 21, 2021.

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

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

The post Ripple Effect on a Texture with Three.js 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.

Coding the Mouse Particle Effect from Mark Appleby’s Website

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 plus the demo!

In this episode of ALL YOUR HTML, I decompile the particles effect from Mark Appleby’s website and show you how you can create a particle system from scratch, using no libraries at all. I also address some performance tips, and ideas about SDF in the 2D world.

This coding session was streamed live on Oct 11, 2020.

Check out the live demo.

Original website: Mark Appleby

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

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

The post Coding the Mouse Particle Effect from Mark Appleby’s Website appeared first on Codrops.