Case Study: Anatole Touvron’s Portfolio

Like many developers, I’ve decided to redo my portfolio in the summer of 2021. I wanted to do everything from scratch, including the design and development, because I liked the idea of:

  • doing the whole project on my own
  • working on something different than coding
  • being 100% responsible for the website
  • being free to do whatever I want

I know that I wanted to do something simple but efficient. I’ve tried many times to do another portfolio but I’ve always felt stuck at some point and wanted to wipe everything clean by remaking the design or the entire development. I talked to many developers about this and I think that we all know this feeling of getting tired of a project because of how long it is taking to finish. For the first time, I’ve managed to never get this feeling simply by designing this entire project in one afternoon and developing it in two weeks.

Without going further, I want to point out that prior to that I’ve studied @lhbizarro’s course on how to create an immersive website from scratch and I would not have been able to create my portfolio this fast without. It is a true wealth of knowledge that I now use on a regular basis.

Going fast

One of the best tips that I can give to any developer in order to not get tired of doing their portfolio is to go as fast as possible. If you know that you get tired easily with a project, I highly suggest doing it when you have the time and then rush it like crazy. This goes for the coding and for the design part.

Get inspired by websites that you like, find colours that match your vibe, fonts that you like, use your favourite design tool and don’t overthink it.

Things can always get better but if you don’t stick to what you’re doing you will never be able to finish it.

I’m really proud and happy about my portfolio but I know that it isn’t that creative and that crazy compared to stuff that you can see online. I’ve set the bar at a level that I knew I could reach and I think that this is a good way of being sure that you’ll have a new website that will make you progress and feel good about yourself.

Text animations

For the different text animations I’ve chose to do something that I’ve been doing a long time, which is animating lines for paragraphs and letters or words for titles with the help of the Intersection Observer API.

Using it is the most efficient and optimal way to do such animations as it only triggers once in your browser and allows you to have many spans translating without making your user’s machine launch like a rocket.

You have to know that, the more elements there are to animate, the harder it will be to have a smooth experience on every device, which is why I used the least amount of spans to do my animations.

To split my text I always use GSAP’s SplitText because it spoon-feeds you the least fun part of the work. It’s really easy to use and never disappoints.

How to animate

There is multiple ways of animating your divs or spans and I’ve decided to show you two ways of doing it:

  • fully with JavaScript
  • CSS and JavaScript-based

Controlling an animation entirely with JavaScript allows you to have code that’s easier to read and understand because everything will be in the same file. But it could a bit less optimal because you’ll be using JavaScript animation libraries.

Open the following example to see the code:

With CSS your code can get a bit out of hand if you are not organised. There’s a lot more classes to take care of. It’s up to you what you prefer to use but I feel like it’s important to talk about both options because each can have their advantages and disadvantages depending on your needs.

The next example shows how to do it with CSS + JS (open the Codesandbox):

Infinite slider

I’d like to show you the HTML and CSS part of the infinite slider on my portfolio. If you’d like to understand the WebGL part, I can recommend the following tutorial: Creating an Infinite Circular Gallery using WebGL with OGL and GLSL Shaders

The main idea of an infinite slider is to duplicate the actual content so that you can never see the end of it.

You can see the example here (open the Codesandbox for a better scroll experience):

Basically, you wrap everything in a big division that contains the doubled content and when you’ve translated the equivalent of 50% of the big wrapper, you put everything back to its initial position. It’s more like an illusion and it will make more sense when you look at this schematic drawing:

At this very moment the big wrapper snaps back to its initial position to mimic the effect of an infinite slider.

WebGL

Although I won’t explain the WebGL part because Luis already explains it in his tutorial, I do want to talk about the post-processing part with OGL because I love this process and it provides a very powerful way of adding interactions to your website.

To do the post-processing, all you need is the following:

this.post = new Post(this.gl);
this.pass = this.post.addPass({
  fragment,
  uniforms: {
    uResolution: {
      value: new Vec2(1, window.innerHeight / window.innerWidth)
    },
    uMouse: {
      value: new Vec2()
    },
    uVelo: {
      value: 0
    },
    uAmount: {
      value: 0
    }
  }
});

We pass the mouse position, velocity and the time to the uniform. It will allow us to create a grain effect, and an RGB distortion based on the mouse position. You can find many shaders like this and I find it really cool to have it. And it’s not that hard to implement.

float circle(vec2 uv, vec2 disc_center, float disc_radius, float border_size) {
  uv -= disc_center;
  uv*= uResolution;
  float dist = sqrt(dot(uv, uv));
  return smoothstep(disc_radius+border_size, disc_radius-border_size, dist);
}

float random( vec2 p )
{
  vec2 K1 = vec2(
    23.14069263277926,
    2.665144142690225
  );
  return fract( cos( dot(p,K1) ) * 12345.6789 );
}

void main() {
  vec2 newUV = vUv;

  float c = circle(newUV, uMouse, 0.0, 0.6);

  float r = texture2D(tMap, newUV.xy += c * (uVelo * .9)).x;
	float g = texture2D(tMap, newUV.xy += c * (uVelo * .925)).y;
	float b = texture2D(tMap, newUV.xy += c * (uVelo * .95)).z;

  vec4 newColor = vec4(r, g, b, 1.);

  newUV.y *= random(vec2(newUV.y, uAmount));
  newColor.rgb += random(newUV)* 0.10;

  gl_FragColor = newColor;
}

You can find the complete implementation here:

Conclusion

I hoped that you liked this case study and learned a few tips and tricks! If you have any questions you can hit me up @anatoletouvron 🙂

The post Case Study: Anatole Touvron’s Portfolio appeared first on Codrops.

Kinetic Typography Page Transition

The other day I was drooling over HOLOGRAPHIK’s amazing kinetic typography animations. Especially this one caught my eye. There’s a very interesting (very short) part where some words are being rotated and zoomed in, and I couldn’t help but think of a page transitions that somehow incorporates that idea.

So I went ahead and tried it on a simple layout to see how it could work. Turns out it’s a lot of fun and there’s so many possibilities for making this look and feel right for a specific design.

The initial screen has some items:

Once we click on an item, the typography page transition happens. The faded background letters starts to rotate and become more visible and finally, they move out, revealing the next screen:

Here’s the whole animation (the color is actually a bit off in the video):

I really hope you enjoy this one and find it inspirational! Let me know what you think on Twitter @codrops or @crnacura! Thank you for checking by!

The post Kinetic Typography Page Transition appeared first on Codrops.

Layout with Reveal Animations and Content Preview

Today I’d like to share a little experiment with you that has some fancy animations for a menu and a content preview. I love to play with the overflow-hidden trick that we can use to make elements disappear and show again (by being cut off). We call this form of hiding an element reveal effect on Codrops and we’ve played with it here, for example.

The idea is to show/hide lines of text fluidly and repeat this animation pattern throughout the design:

Note that this is just an animation proof-of-concept and not a properly made template!

I really hope you like this little experiment and find it inspiring!

The post Layout with Reveal Animations and Content Preview appeared first on Codrops.

How to Code the K72 Marquee Hover Animation

A while back, the folks of K72 released their amazing new website made by the award winning agency Locomotive that boasts with coolness and great design. It has many engaging details but the one I love the most is the fun menu hover effect that involves a marquee:

In this tutorial I’ll show how to create this direction-aware marquee hover effect. We’ll not code the opening animation of the menu itself, but instead focus on animations involved when hovering a menu item.

We won’t be looking under the hood and how the great folks of Locomotive did it but instead, do our version from the visual effect.

Let’s get started!

The Markup

We’ll need a couple of elements to be able to pull off the “reveal” effect. This animation consists of translating one element (where the overflow is hidden) in one direction while moving its child in the opposite direction. The illusion created is that the element reduces its height and gets cut off.

Another structure we need to take care of, is the the one for the marquee. We cover the CSS-only marquee animation in this tutorial. So the structure will be similar but we’ll simplify things a bit style-wise (we won’t have an offset for the items) so less “calculations” will be needed.

So this is the markup we’ll set up for the menu and a menu item:

<nav class="menu">
	<div class="menu__item">
		<a class="menu__item-link">Guayaquil</a>
		<div class="marquee">
			<div class="marquee__inner-wrap">
				<div class="marquee__inner" aria-hidden="true">
					<span>Frank Tower</span>
					<div class="marquee__img" style="background-image:url(img/1.jpg);"></div>
					<span>Dom Dom</span>
					<div class="marquee__img" style="background-image:url(img/2.jpg);"></div>
					<span>Santa Maria</span>
					<div class="marquee__img" style="background-image:url(img/3.jpg);"></div>
					<span>Big Molly</span>
					<div class="marquee__img" style="background-image:url(img/4.jpg);"></div>
					<span>Frank Tower</span>
					<div class="marquee__img" style="background-image:url(img/1.jpg);"></div>
					<span>Dom Dom</span>
					<div class="marquee__img" style="background-image:url(img/2.jpg);"></div>
					<span>Santa Maria</span>
					<div class="marquee__img" style="background-image:url(img/3.jpg);"></div>
					<span>Big Molly</span>
					<div class="marquee__img" style="background-image:url(img/4.jpg);"></div>
				</div><!--/marquee__inner-->
			</div><!--/marquee__inner-wrap-->
		</div><!--/marquee-->
	</div><!--/menu__item-->
	<!-- ... -->
</nav><!--/menu-->

For the looping marquee animation, we duplicate our content. Let’s look into the details for that in a moment.

The marquee element and its child, marquee__inner-wrap, will be used for the cut off reveal effect.

Let’s now take care of the styling.

The CSS

We’ll start by styling the menu item:

.menu__item {
	cursor: default;
	position: relative;
	overflow: hidden;
	text-align: center;
	box-shadow: 0 -1px var(--color-border);
}

.menu__item:last-child {
	box-shadow: 0 1px var(--color-border), 0 -1px var(--color-border);
}

Since we want the little border to be visible when moving over to another item, we’ll use a box shadow. The variables are defined in the body styles.

What is important here is that the element’s overflow is set to “hidden” because we’ll be sliding the inner elements up and down and we don’t want to see them.

The link is styled simply and we also take care of focus styles:

.menu__item-link {
	display: block;
	position: relative;
	cursor: pointer;
	text-decoration: none;
}

.menu__item-link:focus,
.menu__item-link:focus-visible {
	color: var(--menu-focus);
}

.menu__item-link:focus:not(:focus-visible) {
	color: var(--color-link);
}

The marquee will be positioned absolutely and we’ll translate it down by default, while the child will be translated up. When hovering, we’ll reset these translations dynamically using JavaScript depending on where we come from with the mouse:

.marquee {
	position: absolute;
	top: 0;
	left: 0;
	overflow: hidden;
	width: 100%;
	height: 100%;
	pointer-events: none;
	background: var(--marquee-bg);
	transform: translate3d(0,101%,0);
}

.marquee__inner-wrap {
	height: 100%;
	width: 100%;
	transform: translate3d(0,-101%,0);
}

The inner marquee element will be as large as its content and we’ll have an animation running:

.marquee__inner {
	height: 100%;
	width: fit-content;
	align-items: center;
	display: flex;
	position: relative;
	animation: marquee 15s linear infinite;
	will-change: transform;
}

@keyframes marquee {
	100% {
		transform: translate3d(-50%, 0, 0);
	}
}

Since we doubled the content, we know exactly when we have to “restart” the animation. At half of the element’s width, we’ll go back to the beginning, creating the illusion of an endless flow.

And finally, some styling for our text elements and the images:

.menu__item-link,
.marquee span {
	white-space: nowrap;
	font-size: 6vw;
	line-height: 1.2;
	font-weight: 600;
	padding: 1vh 1vw 0;
	text-transform: uppercase;
}

.marquee span {
	text-align: center;
	color: var(--marquee-text);
	font-weight: 400;
}

.marquee__img {
	width: 15vw;
	height: 70%;
	margin: 0 2vw;
	border-radius: 5vw;
	background-size: cover;
	background-position: 50% 50%;
}

And that’s all the styling! Let’s now take care of the dynamic part.

The JavaScript

The core of our script is the changing of the transforms based on the direction we are coming from with the mouse. John Stewart coded an elegant solution for this and we’ll integrate his code into our script. We’ll use GSAP.

Let’s first create our entry file (index.js) and initialize our Menu:

import { Menu } from './menu';
// initialize the menu
new Menu(document.querySelector('.menu'));

The Menu has a set of items:

import { MenuItem } from './menuItem';

export class Menu {
    constructor(el) {
        // .menu element
        this.DOM = {el: el};
        // the menu items
        this.DOM.menuItems = this.DOM.el.querySelectorAll('.menu__item');
        // array of MenuItem
        this.menuItems = [];
        this.DOM.menuItems.forEach(menuItem => this.menuItems.push(new MenuItem(menuItem)));
    }
}

We initialize a MenuItem instance for each of the menu’s items.

Now let’s create a class MenuItem where we add the mouse enter/leave logic. We want to animate both the .marquee and .marquee__inner-wrap elements when hovering over the .menu__item-link element. These two elements need to be translated in different directions so that we achieve the cut-off reveal effect.

Let’s start by initializing some elements and events:

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

export class MenuItem {
    constructor(el) {
        // .menu__item element
        this.DOM = {el: el};
        // .menu__item-link element
        this.DOM.link = this.DOM.el.querySelector('a.menu__item-link');
        // .marquee element
        this.DOM.marquee = this.DOM.el.querySelector('.marquee');
        // .marquee__inner-wrap element
        this.DOM.marqueeInner = this.DOM.marquee.querySelector('.marquee__inner-wrap');
        // some default options for the animation's speed and easing
        this.animationDefaults = {duration: 0.6, ease: 'expo'};
        // events initialization
        this.initEvents();
    }
    initEvents() {
        this.onMouseEnterFn = ev => this.mouseEnter(ev);
        this.DOM.link.addEventListener('mouseenter', this.onMouseEnterFn);
        this.onMouseLeaveFn = ev => this.mouseLeave(ev);
        this.DOM.link.addEventListener('mouseleave', this.onMouseLeaveFn);
    }
    // ...
}

When hovering in or out, we want the marquee content to be revealed by sliding out both, the marquee and marqueeInner elements. This animation should follow the mouse movement, meaning that if we enter the element from the top then the sliding effect will be from top to bottom and vice-versa. To achieve this, we need to set up the correct initial positions for both elements:

export class MenuItem {
    // ...
    mouseEnter(ev) {
        // find closest side to the mouse
        const edge = this.findClosestEdge(ev);
        
        // set the initial y position for both the marquee and marqueeInner elements
        // for the reveal effect to happen, both start at opposite positions
        // the directions are different depending on the direction the cursor enters the element (bottom or top)
        gsap.timeline({defaults: this.animationDefaults})
        .set(this.DOM.marquee, {y: edge === 'top' ? '-101%' : '101%'}, 0)
        .set(this.DOM.marqueeInner, {y: edge === 'top' ? '101%' : '-101%'}, 0)
        .to([this.DOM.marquee, this.DOM.marqueeInner], {y: '0%'}, 0);
    }
    mouseLeave(ev) {
        // find closest side to the mouse
        const edge = this.findClosestEdge(ev);
        
        gsap.timeline({defaults: this.animationDefaults})
        .to(this.DOM.marquee, {y: edge === 'top' ? '-101%' : '101%'}, 0)
        .to(this.DOM.marqueeInner, {y: edge === 'top' ? '101%' : '-101%'}, 0);
    }
    // find closest side to the mouse when entering/leaving
    findClosestEdge(ev) {
        const x = ev.pageX - this.DOM.el.offsetLeft;
        const y = ev.pageY - this.DOM.el.offsetTop;
        return closestEdge(x,y,this.DOM.el.clientWidth, this.DOM.el.clientHeight);
    }
    // ...
}

And that’s all! Our direction-aware marquee hover effect is done!

Check out the final demo.

Now, if you’d like another challenge, try to implement the opening/closing of the menu. The Locomotive team used a really great 3D effect here, so go on and try that or experiment with other cool “openings”.

I really hope you enjoyed this tutorial and found it useful!

The post How to Code the K72 Marquee Hover Animation appeared first on Codrops.

Diagonal Thumbnail Slideshow Animation

The other day I saw this very nice Dribbble shot by Anton Tkachev and couldn’t help but envision it as a slideshow with some large typography. So I went ahead and did a small demo which then turned into an exploration of several animations for the image tiles and the texts.

So the main idea is to animate the tilted thumbnails out of the viewport when navigating to the next or previous slide. While the thumbnails move out, the titles get animated too, in that reveal/unreveal fashion that seems to be quite trendy now. The direction of the motion depends on wether we’re navigating back or forth.

The animations are powered by GreenSock’s GSAP animation library.

I’m totally in love with IvyMode and this demo was just a perfect excuse to use Jan Maack’s typeface again!

The images used in the demos are by generative artist Manolo Ide who offers his artwork for free.

I really hope this gives you a starting point for exploring more effects!

The post Diagonal Thumbnail Slideshow Animation appeared first on Codrops.

Making Stagger Reveal Animations for Text

Thibaud Allie made this wonderful animation which you can see live on the site of Dani Morales. It happens when you click on “About” (and then “Close”). This kind of show/hide animation on the typographic elements is being used in many designs lately. At Codrops we call it a “reveal” animation.

I fell in love with that lettering effect and wanted to reimplement it using GSAP and Splitting.js. So I made a similar typography based layout and move the lines of text by staggering the letter animations.

The simplified markup looks like this:

<section class="content__item content__item--home content__item--current">
	<p class="content__paragraph" data-splitting>Something</p>
	<p class="content__paragraph" data-splitting>More</p>
	...
</section>
<section class="content__item content__item--about">
	<p class="content__paragraph" data-splitting>Something</p>
	<p class="content__paragraph" data-splitting>Else</p>
	...
</section>

Note that the necessary style for the content__paragraph element is overflow: hidden so that the reveal/unreveal animation works.

All elements with the “data-splitting” attribute will be split up into spans that we can then animate individually. So let’s have a look at the JavaScript for that.

Initially, we need to import some libraries and styles:

import "splitting/dist/splitting.css";
import "splitting/dist/splitting-cells.css";
import Splitting from "splitting";
import { gsap } from 'gsap';
import { preloadFonts } from './utils';
import Cursor from "./cursor";

I’m using some Adobe Fonts here (Freight Big Pro and Tenon) so let’s preload them:

preloadFonts('lwc3axy').then(() => document.body.classList.remove('loading'));

Then it’s time to split all the texts into spans:

Splitting();

The design has a custom cursor that we call as follows:

new Cursor(document.querySelector('.cursor'))

Let’s get all relevant DOM elements:

let DOM = {
    content: {
        home: {
            section: document.querySelector('.content__item--home'),
            get chars() {
                return this.section.querySelectorAll('.content__paragraph .word > .char, .whitespace');
            },
            isVisible: true
        },
        about: {
            section: document.querySelector('.content__item--about'),
            get chars() {
                return this.section.querySelectorAll('.content__paragraph .word > .char, .whitespace')
            },
            get picture() {
                return this.section.querySelector('.content__figure');
            },
            isVisible: false
        }
    },
    links: {
        about: {
            anchor: document.querySelector('a.frame__about'),
            get stateElement() {
                return this.anchor.children;
            }
        },
        home: document.querySelector('a.frame__home')
    }
};

Let’s have a look at the GSAP timeline now where all the magic happens (and also some default settings):

const timelineSettings = {
    staggerValue: 0.014,
    charsDuration: 0.5
};
const timeline = gsap.timeline({paused: true})
    .addLabel('start')
    // Stagger the animation of the home section chars
    .staggerTo( DOM.content.home.chars, timelineSettings.charsDuration, {
        ease: 'Power3.easeIn',
        y: '-100%',
        opacity: 0
    }, timelineSettings.staggerValue, 'start')
    // Here we do the switch
    // We need to toggle the current class for the content sections
    .addLabel('switchtime')
    .add( () => {
        DOM.content.home.section.classList.toggle('content__item--current');
        DOM.content.about.section.classList.toggle('content__item--current');
    })
    // Change the body's background color
    .to(document.body, {
        duration: 0.8,
        ease: 'Power1.easeInOut',
        backgroundColor: '#c3b996'
    }, 'switchtime-=timelineSettings.charsDuration/4')
    // Start values for the about section elements that will animate in
    .set(DOM.content.about.chars, {
        y: '100%'
    }, 'switchtime')
    .set(DOM.content.about.picture, {
        y: '40%',
        rotation: -4,
        opacity: 0
    }, 'switchtime')
    // Stagger the animation of the about section chars
    .staggerTo( DOM.content.about.chars, timelineSettings.charsDuration, {
        ease: 'Power3.easeOut',
        y: '0%'
    }, timelineSettings.staggerValue, 'switchtime')
    // Finally, animate the picture in
    .to( DOM.content.about.picture, 0.8, {
        ease: 'Power3.easeOut',
        y: '0%',
        opacity: 1,
        rotation: 0
    }, 'switchtime+=0.6');

When we click on the “About” or logo/home link we want to toggle the current content by playing and reversing the timeline. We also want to toggle the current state of the “About” and “Close” link:

const switchContent = () => {
    DOM.links.about.stateElement[0].classList[DOM.content.about.isVisible ? 'add' : 'remove']('frame__about-item--current');
    DOM.links.about.stateElement[1].classList[DOM.content.about.isVisible ? 'remove' : 'add']('frame__about-item--current');
    timeline[DOM.content.about.isVisible ? 'reverse' : 'play']();
    DOM.content.about.isVisible = !DOM.content.about.isVisible;
    DOM.content.home.isVisible = !DOM.content.about.isVisible;
};

DOM.links.about.anchor.addEventListener('click', () => switchContent());
DOM.links.home.addEventListener('click', () => {
    if ( DOM.content.home.isVisible ) return;
    switchContent();
});

And that’s all the magic! It’s not too complicated and you can do many nice effects with this kind of letter animation.

Thank you for reading and hopefully this was relevant and helpful 🙂

Please reach out to me @codrops or @crnacura if you have any questions or suggestions.

Credits

The post Making Stagger Reveal Animations for Text appeared first on Codrops.

Making Gooey Image Hover Effects with Three.js

Flash’s grandson, WebGL has become more and more popular over the last few years with libraries like Three.js, PIXI.js or the recent OGL.js. Those are very useful for easily creating a blank board where the only boundaries are your imagination. We see more and more, often subtle integration of WebGL in an interface for hover, scroll or reveal effects. Examples are the gallery of articles on Hello Monday or the effects seen on cobosrl.co.

In this tutorial, we’ll use Three.js to create a special gooey texture that we’ll use to reveal another image when hovering one. Head over to the demo to see the effect in action. For the demo itself, I’ve created a more practical example that shows a vertical scrollable layout with images, where each one has a variation of the effect. You can click on an image and it will expand to a larger version while some other content shows up (just a mock-up). We’ll go over the most interesting parts of the effect, so that you get an understanding of how it works and how to create your own.

I’ll assume that you are comfortable with JavaScript and have some knowledge of Three.js and shader logic. If you’re not, have a look at the Three.js documentation or The Book of Shaders, Three.js Fundamentals or Discover Three.js.

Attention: This tutorial covers many parts; if you prefer, you can skip the HTML/CSS/JavaScript part and go directly go to the shaders section.

Now that we are clear, let’s do this!

Create the scene in the DOM

Before we start making some magic, we are first going to mark up the images in the HTML. It will be easier to handle resizing our scene after we’ve set up the initial position and dimension in HTML/CSS rather than positioning everything in JavaScript. Moreover, the styling part should be only made with CSS, not JavaScript. For example, if our image has a ratio of 16:9 on desktop but a 4:3 ratio on mobile, we just want to handle this using CSS. JavaScript will only get the new values and do its stuff.

// index.html

<section class="container">
	<article class="tile">
		<figure class="tile__figure">
			<img data-src="path/to/my/image.jpg" data-hover="path/to/my/hover-image.jpg" class="tile__image" alt="My image" width="400" height="300" />
		</figure>
	</article>
</section>

<canvas id="stage"></canvas>
// style.css

.container {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 100%;
	height: 100vh;
	z-index: 10;
}

.tile {
	width: 35vw;
	flex: 0 0 auto;
}

.tile__image {
	width: 100%;
	height: 100%;
	object-fit: cover;
	object-position: center;
}

canvas {
	position: fixed;
	left: 0;
	top: 0;
	width: 100%;
	height: 100vh;
	z-index: 9;
}

As you can see above, we have create a single image that is centered in the middle of our screen. Did you notice the data-src and data-hover attributes on the image? These will be our reference images and we’ll load both of these later in our script with lazy loading.

Don’t forget the canvas. We’ll stack it below our main section to draw the images in the exact same place as we have placed them before.

Create the scene in JavaScript

Let’s get started with the less-easy-but-ok part! First, we’ll create the scene, the lights, and the renderer.

// Scene.js

import * as THREE from 'three'

export default class Scene {
	constructor() {
		this.container = document.getElementById('stage')

		this.scene = new THREE.Scene()
		this.renderer = new THREE.WebGLRenderer({
			canvas: this.container,
			alpha: true,
	  })

		this.renderer.setSize(window.innerWidth, window.innerHeight)
		this.renderer.setPixelRatio(window.devicePixelRatio)

		this.initLights()
	}

	initLights() {
		const ambientlight = new THREE.AmbientLight(0xffffff, 2)
		this.scene.add(ambientlight)
	}
}

This is a very basic scene. But we need one more essential thing in our scene: the camera. We have a choice between two types of cameras: orthographic or perspective. If we keep our image flat, we can use the first one. But for our rotation effect, we want some perspective as we move the mouse around.

In Three.js (and other libraries for WebGL) with a perspective camera, 10 unit values on our screen are not 10px. So the trick here is to use some math to transform 1 unit to 1 pixel and change the perspective to increase or decrease the distortion effect.

// Scene.js

const perspective = 800

constructor() {
	// ...
	this.initCamera()
}

initCamera() {
	const fov = (180 * (2 * Math.atan(window.innerHeight / 2 / perspective))) / Math.PI

	this.camera = new THREE.PerspectiveCamera(fov, window.innerWidth / window.innerHeight, 1, 1000)
	this.camera.position.set(0, 0, perspective)
}

We’ll set the perspective to 800 to have a not-so-strong distortion as we rotate the plane. The more we increase the perspective, the less we’ll perceive the distortion, and vice versa.

The last thing we need to do is to render our scene in each frame.

// Scene.js

constructor() {
	// ...
	this.update()
}

update() {
	requestAnimationFrame(this.update.bind(this))
	
	this.renderer.render(this.scene, this.camera)
}

If your screen is not black, you are on the right way!

Build the plane with the correct sizes

As we mentioned above, we have to retrieve some additional information from the image in the DOM like its dimension and position on the page.

// Scene.js

import Figure from './Figure'

constructor() {
	// ...
	this.figure = new Figure(this.scene)
}
// Figure.js

export default class Figure {
	constructor(scene) {
		this.$image = document.querySelector('.tile__image')
		this.scene = scene

		this.loader = new THREE.TextureLoader()

		this.image = this.loader.load(this.$image.dataset.src)
		this.hoverImage = this.loader.load(this.$image.dataset.hover)
		this.sizes = new THREE.Vector2(0, 0)
		this.offset = new THREE.Vector2(0, 0)

		this.getSizes()

		this.createMesh()
	}
}

First, we create another class where we pass the scene as a property. We set two new vectors, dimension and offset, in which we’ll store the dimension and position of our DOM image.

Furthermore, we’ll use a TextureLoader to “load” our images and convert them into a texture. We need to do that as we want to use these pictures in our shaders.

We need to create a method in our class to handle the loading of our images and wait for a callback. We could achieve that with an async function but for this tutorial, let’s keep it simple. Just keep in mind that you’ll probably need to refactor this a bit for your own purposes.

// Figure.js

// ...
	getSizes() {
		const { width, height, top, left } = this.$image.getBoundingClientRect()

		this.sizes.set(width, height)
		this.offset.set(left - window.innerWidth / 2 + width / 2, -top + window.innerHeight / 2 - height / 2)
	}
// ...

We get our image information in the getBoundingClientRect object. After that, we’ll pass these to our two variables. The offset is here to calculate the distance between the center of the screen and the object on the page.

// Figure.js

// ...
	createMesh() {
		this.geometry = new THREE.PlaneBufferGeometry(1, 1, 1, 1)
		this.material = new THREE.MeshBasicMaterial({
			map: this.image
		})

		this.mesh = new THREE.Mesh(this.geometry, this.material)

		this.mesh.position.set(this.offset.x, this.offset.y, 0)
		this.mesh.scale.set(this.sizes.x, this.sizes.y, 1)

		this.scene.add(this.mesh)
	}
// ...

After that, we’ll set our values on the plane we’re building. As you can notice, we have created a plane of 1 on 1px with 1 row and 1 column. As we don’t want to distort the plane, we don’t need a lot of faces or vertices. So let’s keep it simple.

But why scale it while we can set the size directly? Glad you asked.

Because of the resizing part. If we want to change the size of our mesh afterwards, there is no other proper way than this one. While it’s easier to change the scale of the mesh, it’s not for the dimension.

For the moment, we set a MeshBasicMaterial, just to see if everything is fine.

Get mouse coordinates

Now that we have built our scene with our mesh, we want to get our mouse coordinates and, to keep things easy, we’ll normalize them. Why normalize? Because of the coordinate system in shaders.

coordinate-system

As you can see in the figure above, we have normalized the values for both of our shaders. So to keep things simple, we’ll prepare our mouse coordinate to match the vertex shader coordinate.

If you’re lost at this point, I recommend you to read the Book of Shaders and the respective part of Three.js Fundamentals. Both have good advice and a lot of examples to help understand what’s going on.

// Figure.js

// ...

this.mouse = new THREE.Vector2(0, 0)
window.addEventListener('mousemove', (ev) => { this.onMouseMove(ev) })

// ...

onMouseMove(event) {
	TweenMax.to(this.mouse, 0.5, {
		x: (event.clientX / window.innerWidth) * 2 - 1,
		y: -(event.clientY / window.innerHeight) * 2 + 1,
	})

	TweenMax.to(this.mesh.rotation, 0.5, {
		x: -this.mouse.y * 0.3,
		y: this.mouse.x * (Math.PI / 6)
	})
}

For the tween parts, I’m going to use TweenMax from GreenSock. This is the best library ever. EVER. And it’s perfect for our purpose. We don’t need to handle the transition between two states, TweenMax will do it for us. Each time we move our mouse, TweenMax will update the position and the rotation smoothly.

One last thing before we continue: we’ll update our material from MeshBasicMaterial to ShaderMaterial and pass some values (uniforms) and shaders.

// Figure.js

// ...

this.uniforms = {
	u_image: { type: 't', value: this.image },
	u_imagehover: { type: 't', value: this.hover },
	u_mouse: { value: this.mouse },
	u_time: { value: 0 },
	u_res: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
}

this.material = new THREE.ShaderMaterial({
	uniforms: this.uniforms,
	vertexShader: vertexShader,
	fragmentShader: fragmentShader
})

update() {
	this.uniforms.u_time.value += 0.01
}

We passed our two textures, the mouse position, the size of our screen and a variable called u_time which we will increment each frame.

But keep in mind that it’s not the best way to do that. For example, we only need to increment when we are hovering the figure, not every frame. I’m not going into details, but performance-wise, it’s better to just update our shader only when we need it.

The logic behind the trick & how to use noise

Still here? Nice! Time for some magic tricks.

I will not explain what noise is and where it comes from. If you’re interested, be sure to read this page from The Book of Shaders. It’s well explained.

Long story short, Noise is a function that gives us a value between -1 and 1 based on values we pass through. It will output a random pattern but more organic.

Thanks to noise, we can generate a lot of different shapes, like maps, random patterns, etc.

noise-example1

noise-example2

Let’s start with a 2D noise result. Just by passing the coordinate of our texture, we’ll have something like a cloud texture.

noise-result1

But there are several kinds of noise functions. Let’s use a 3D noise by giving one more parameter like … the time? The noise pattern will evolve and change over time. By changing the frequency and the amplitude, we can give some movement and increase the contrast.

It will be our first base.

noise-result2

Second, we’ll create a circle. It’s quite easy to build a simple shape like a circle in the fragment shader. We just take the function from The Book of Shaders: Shapes to create a blurred circle, increase the contrast and voilà!

noise-result3

Last, we add these two together, play with some variables, cut a “slice” of this and tadaaa:

noise-result4

We finally mix our textures together based on this result and here we are, easy peasy lemon squeezy!

Let’s dive into the code.

Shaders

We won’t really need the vertex shader here so this is our code:

 // vertexShader.glsl
varying vec2 v_uv;

void main() {
	v_uv = uv;

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

ShaderMaterial from Three.js provides some useful default variables when you’re a beginner:

  • position (vec3): the coordinates of each vertex of our mesh
  • uv (vec2): the coordinates of our texture
  • normals (vec3): normal of each vertex our mesh have.

Here we’re just passing the UV coordinates from the vertex shader to fragment shader.

Create the circle

Let’s use the function from The Book of Shaders to build our circle and add a variable to handle the blurriness of our edges.

Moreover, we’ll add the mouse position to the origin of our circle. This way, the circle will be moving as long as we move our mouse over our image.

// fragmentShader.glsl
uniform vec2 u_mouse;
uniform vec2 u_res;

float circle(in vec2 _st, in float _radius, in float blurriness){
	vec2 dist = _st;
	return 1.-smoothstep(_radius-(_radius*blurriness), _radius+(_radius*blurriness), dot(dist,dist)*4.0);
}

void main() {
	vec2 st = gl_FragCoord.xy / u_res.xy - vec2(1.);
	// tip: use the following formula to keep the good ratio of your coordinates
	st.y *= u_res.y / u_res.x;

	vec2 mouse = u_mouse;
	// tip2: do the same for your mouse
	mouse.y *= u_res.y / u_res.x;
	mouse *= -1.;
	
	vec2 circlePos = st + mouse;
	float c = circle(circlePos, .03, 2.);

	gl_FragColor = vec4(vec3(c), 1.);
}

Make some noooooise

As we saw above, the noise function has several parameters and gives us a smooth cloudy pattern. How could we have that? Glad you asked.

For this part, I’m using glslify and glsl-noise, and two npm packages to include other functions. It keeps our shader a little bit more readable and avoids having a lot of displayed functions that we will not use after all.

// fragmentShader.glsl
#pragma glslify: snoise2 = require('glsl-noise/simplex/2d')

//...

varying vec2 v_uv;

uniform float u_time;

void main() {
	// ...

	float n = snoise2(vec2(v_uv.x, v_uv.y));

	gl_FragColor = vec4(vec3(n), 1.);
}

noise-result5

By changing the amplitude and the frequency of our noise (exactly like the sin/cos functions), we can change the render.

// fragmentShader.glsl

float offx = v_uv.x + sin(v_uv.y + u_time * .1);
float offy = v_uv.y - u_time * 0.1 - cos(u_time * .001) * .01;

float n = snoise2(vec2(offx, offy) * 5.) * 1.;

noise-result6

But it isn’t evolving through time! It is distorted but that’s it. We want more. So we will use noise3d instead and pass a 3rd parameter: the time.

float n = snoise3(vec3(offx, offy, u_time * .1) * 4.) * .5;

As you can see, I changed the amplitude and the frequency to have the render I desire.

Alright, let’s add them together!

Merging both textures

By just adding these together, we’ll already see an interesting shape changing through time.

noise-result7

To explain what’s happening, let’s imagine our noise is like a sea floating between -1 and 1. But our screen can’t display negative color or pixels more than 1 (pure white) so we are just seeing the values between 0 and 1.

explanation-noise1

And our circle is like a flan.

explanation-noise2

By adding these two shapes together it will give this very approximative result:

explanation-noise3

Our very white pixels are only pixels outside the visible spectrum.

If we scale down our noise and subtract a small number, it will be completely moving down your waves until it disappears above the surface of the ocean of visible colors.

noise-result8

float n = snoise(vec3(offx, offy, u_time * .1) * 4.) - 1.;

Our circle is still there but not enough visible to be displayed. If we multiply its value, it will be more contrasted.

float c = circle(circlePos, 0.3, 0.3) * 2.5;

noise-result9

We are almost there! But as you can see, there are still some details missing. And our edges aren’t sharp at all.

To avoid that, we’ll use the built-in smoothstep function.

float finalMask = smoothstep(0.4, 0.5, n + c);

gl_FragColor = vec4(vec3(finalMask), 1.);

Thanks to this function, we’ll cut a slice of our pattern between 0.4 et 0.5, for example. The shorter the space is between these values, the sharper the edges are.

Finally, we can mix our two textures to use them as a mask.

uniform sampler2D u_image;
uniform sampler2D u_imagehover;

// ...

vec4 image = texture2D(u_image, uv);
vec4 hover = texture2D(u_imagehover, uv);

vec4 finalImage = mix(image, hover, finalMask);

gl_FragColor = finalImage;

We can change a few variables to have a more gooey effect:

// ...

float c = circle(circlePos, 0.3, 2.) * 2.5;

float n = snoise3(vec3(offx, offy, u_time * .1) * 8.) - 1.;

float finalMask = smoothstep(0.4, 0.5, n + pow(c, 2.));

// ...

And voilà!

Check out the full source here or take a look at the live demo.

Mic drop

Congratulations to those who came this far. I haven’t planned to explain this much. This isn’t perfect and I might have missed some details but I hope you’ve enjoyed this tutorial anyway. Don’t hesitate to play with variables, try other noise functions and try to implement other effects using the mouse direction or play with the scroll!

If you have any questions, let me know in the comments section! I also encourage you to download the demo, it’s a little bit more complex and shows the effects in action with hover and click effects ¯\_(?)_/¯

References and Credits

Making Gooey Image Hover Effects with Three.js was written by Arno Di Nunzio and published on Codrops.

How to Create and Animate Rotated Overlays

Today we’d like to explore a specific reveal effect with you. If you saw the Crossroads Slideshow a while back, you might have noticed the page transition when the content is shown after an image gets clicked. We call this type of transitions a “reveal” animation because some content is already there while an overlay element animates out, revealing what’s underneath.

To make such an effect is pretty straightforward: simply place an overlay with the same or different color of the page background and animate it out of the viewport; whatever is under it will show. But there are two challenges here: one is if you’d like the overlay itself to have some content which you want to conceal, i.e. which you want to get cut off while hiding it and not simply move along with the parent when animating it out. The other challenge is to add a rotation and guarantee that the overlay covers the whole screen so that no gaps are shown when you move it out. When combining these two effects, things get really interesting.

So let’s tackle these two challenges in this little tip today and show some of the many possibilities for how to use these techniques in a page design.

The demos are kindly sponsored by Northwestern: Earn your MS degree entirely online. If you would like to sponsor one of our demos, find out more here.

Attention: Highly experimental prototyping, please view in a capable browser.

The reveal effect

The beauty of the reveal effect is that the technique is very simple, yet the result is so interesting: take any element that has its overflow set to “hidden” and animate it in some direction, while animating its child in the opposite direction. This creates a “cut off” look, the content appears to be steady in one place, as if we’re animating some kind of clipping mask. Yet we are only translating elements.

Under the hood, you can see what’s happening here:

Reveal_step1.2019-04-18 15_09_21

We simply move a container up. Now, let’s keep the content in place by reversing that movement and translating it in the opposite direction:

Reveal_opposite.2019-04-18 15_09_21

One last step is to add overflow: hidden to the parent:

Reveal_final2019-04-18 15_09_21

And that’s it! Now, if you want to spice things up a bit, you can add a different duration or easing to the reverse element or other animations to the inner elements.

Adding a rotation

The effect becomes a little bit more complicated when we want to add a rotation. When we rotate an element it will create gaps and not cover the background entirely anymore. So we need to make sure that it’s width and height is set in such a way that when rotated, there are no gaps.

Technically, we’re want the (minimum) bounding box of a rotated rectangle.

The following Stackoverflow thread gave us the right formula for our case: How to scale a rotated rectangle to always fit another rectangle

We only need to find the correct width and height, so the following bit is interesting to us:

When you rotate an axis-aligned rectangle of width w and height h by an angle ɸ, the width and height of the rotated rectangle’s axis-aligned bounding box are:

W = w·|cos ɸ| + h·|sin ɸ|
H = w·|sin ɸ| + h·|cos ɸ|

(The notation |x| denotes an absolute value.)

Additionally, we have to make sure that we keep the previous structure in place and that we show the content straight. So we need to rotate the content back. To ease our animation and not tinker with calculations we avoid moving the rotated content, but instead we’ll use the resized container for the motion.

In total we will use three containers to achieve all that:

<div class="content content--first"><!-- only rotated -->	
	<div class="content__move"><!-- resized and moved -->
		<div class="content__reverse"><!-- reverse rotation -->
			<!-- ... -->
		</div>
	</div>
</div>

If you look at the x-ray view of one of the demos, you can see the rotation and calculation of the new width and height:

Revealers_xray

Given this structure, there are really endless possibilities for rotated reveal and overlay animations.

Reveal2.2019-04-18 16_26_24

Think of multiple overlays. Think of matching animations of the elements that are being revealed or the ones that get hidden.

Reveal3.2019-04-18 16_28_20

There’s so much to explore!

Reveal5.2019-04-18 17_56_58

Have a look at our little compilation, we hope you enjoy it!

How to Create and Animate Rotated Overlays was written by Mary Lou and published on Codrops.

Crossroads Slideshow

Today we’d like to share an experimental slideshow with you. The main idea is to show three slides of a slideshow that is slightly rotated. The titles of each slide, which serve as a decorative element, overlay the images and are rotated in an opposing angle. This creates an interesting look, especially when animated. When clicking on one of the lateral slides, the whole thing moves, and when we click on the middle slide, we move everything up and reveal a content area.

The animations are powered by TweenMax.

Attention: Note that the demo is experimental and that we use modern CSS properties that might not be supported in older browsers. Edge has a problem with SVG data-uri cursors, so you won’t see the custom cursors in the demo there.

The initial view of the slideshow looks as follows:

CrossroadsSlideshow_01

When we click on the lateral slides, we can navigate. When clicking on the middle one, we open the respective content view for that item:

CrossroadsSlideshow_02

We also have a dark mode option:

CrossroadsSlideshow_03

CrossroadsSlideshow_04

Here’s how the animations look:

We hope you enjoy this slideshow and find it useful!

References and Credits

Crossroads Slideshow was written by Mary Lou and published on Codrops.

Layer Motion Slideshow

Today we’d like to share yet another CSS Grid-powered slideshow with you. The idea is to show and hide images with a reveal effect and add a parallax like effect to the main image and the title. For the title we’ve added two copied layers with an outline style which creates an interesting motion effect. For the animations we use TweenMax.

Attention: Note that we use modern CSS properties that might not be supported in older browsers.

The initial view of the slideshow looks as follows:

LayerMotionSlideshow_01

For each slide we have a custom grid layout with one main image that spans the full height of the page. When we go next, the images will be hidden with a sliding motion and the title letters disappear randomly. The new slide will reveal its images and the title in a similar fashion.

When moving the mouse, we move copied layers of the main image and the title to create a trail-like effect.

Once the plus after the excerpt is clicked, we show the content of the slide and change the background color:

LayerMotionSlideshow_02

We hope you like this slideshow and find it useful!

References and Credits

Layer Motion Slideshow was written by Mary Lou and published on Codrops.