GreenSock ScrollTrigger

High five to the Greensock gang for the ScrollTrigger release. The point of this new plugin is triggering animation when a page scrolls to certain positions, as well as when certain elements are in the viewport. Anything you’d want configurable about it, is. There’s been plenty of scroll-position libraries over the years, but Greensock has a knack for getting the APIs and performance just right — not to mention that because what you want is to trigger animations, now you’ve got Greensock at your fingertips making sure you’re in good hands. It’s tightly integrated with all the other animation possibilities of GSAP (e.g. animating a timeline based on scroll position).

They’ve got docs and a bunch of examples. I particularly like how they have a mistakes section with ways you can screw it up. Every project should do that.

CodePen is full of examples too, so I’ll take the opportunity to drop some here for your viewing pleasure. You can play with it on CodePen for free (search for it).

Screenshot of CodePen JavaScript settings with a completed search for greensock scroll to plugin in the external scripts section.

If you’re worried about too much motion, that’s something that you can do responsibly through prefers-reduced-motion, which is available both as a CSS media query and in JavaScript.


The post GreenSock ScrollTrigger appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Collective #638




Making things move

An excellent scrolly-telling breakdown of the recreation of Walter Leblanc’s print “Torsions” with Canvas. By Varun Vachhar.

Read it




Troubleshooting Caching

Michelle Barker shares some useful steps to ensure browsers serve the latest files after deploying changes on a website.

Read it






TS belt

TS Belt is a library for functional programming in TypeScript. Inspired by the Belt module for ReScript/Reason, it solves the problem of the existence of both undefined and null.

Check it out


PWAdvent

From the 1st to the 24th of December 2020, PWAdvent introduces a new progressive browser feature every day.

Check it out







SPCSS

SPCSS is a simple and plain style sheet for text-based websites.

Check it out





The post Collective #638 appeared first on Codrops.

Crafting a Scrollable and Draggable Parallax Slider

In this article, I’ll show you how to build a parallax slider with a fun reveal animation. I’ll be using GSAP, CSS Grid and Flexbox and I’ll assume that you have some basic knowledge on how to use these techniques. Besides that, I’ll be using a custom smooth scroll based on Virtual Scroll for a better experience.

The article is split up in 3 steps:

  1. The hover animation
  2. The open/expand animation
  3. The slider scroll/drag parallax effect

 For a better understanding I added motion videos for each step.

1. Hover animation

When we hover the button on the top right, 3 placeholder images will animate in the viewport from the right side.

Markup

<button class="button-slider-open js-slider-open" type="button">
  <svg>...</svg>
</button>

<div class="placeholders js-placeholders">
  <div class="placeholders__img-wrap js-img-wrap" style="--aspect-ratio: 0.8;">
    <img
      src="https://images.unsplash.com/photo-1479839672679-a46483c0e7c8?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&ar=0.8"
      class="placeholders__img"
    >
  </div>

  ...
</div>

Animation

We have a this.dom object where we store all the DOM elements such as the images. When initializing the app we call the setHoverAnimation which creates a GSAP timeline that is set default to paused. In the timeline we will animate the 3 images into the viewport so they’re partly visible. The user will then have the impression that they can be expanded on click.

this.dom = {};
this.dom.el = document.querySelector('.js-placeholders');
this.dom.images = this.dom.el.querySelectorAll('.js-img-wrap');
this.dom.buttonOpen = document.querySelector('.js-slider-open');
setHoverAnimation() {
  this.tl = gsap.timeline({ paused: true });

  this.tl
    .addLabel('start')

    .set(this.dom.el, { autoAlpha: 1 })
    .set(this.dom.images, { scale: 0.5, x: (window.innerWidth / 12) * 1.2, rotation: 0 })

    .to(this.dom.images, { duration: 1, stagger: 0.07, ease: 'power3.inOut', x: 0, y: 0 })
    .to(this.dom.images[0], { duration: 1, ease: 'power3.inOut', rotation: -4 }, 'start')
    .to(this.dom.images[1], { duration: 1, ease: 'power3.inOut', rotation: -2 }, 'start');
}

To trigger the animation when hovering the button, we created 2 events (handleMouseenter and handleMouseleave) which will play the GSAP timeline or simply reverse the animation.

this.dom.buttonOpen.addEventListener('mouseenter', this.handleMouseenter);
this.dom.buttonOpen.addEventListener('mouseleave', this.handleMouseleave);

handleMouseenter() {
  this.tl.play();
}

handleMouseleave() {
  this.tl.reverse();
}

2. Expand placeholder items

When we click the button on the top right, after hovering, the 3 placeholder images will animate to a 3 column grid. There will be more than 3 items in the slider, but only the first 3 will be visible in the viewport so it’s not necessary to animate the other items. Once the 3 placeholder items are in the correct position we can display the actual slider items underneath and remove the placeholder items.

Animation

First we have to calculate the position that the placeholder items will have to animate to. Subtracting the left position of the placeholder items with the left position of the slider items will get you the correct x position. To get the y position I’m doing the exact same thing, but use the top bounds instead.

const x1 = this.bounds.left - slider.items[0].bounds.left - 20;
const x2 = this.bounds.left - slider.items[1].bounds.left + 10;
const x3 = this.bounds.left - slider.items[2].bounds.left;

const y1 = this.bounds.top - slider.items[0].bounds.top + 10;
const y2 = this.bounds.top - slider.items[1].bounds.top - 30;
const y3 = this.bounds.top - slider.items[2].bounds.top + 30;

The placeholder items are smaller than the slider items so we have to scale them up. To calculate the scale value we will just divide the width of the placeholder items by the width of one of the slider items (they are all the same size).

const scale = slider.items[0].bounds.width / this.bounds.width;

The intersectX1 X2 X3 values are used to set the initial x position of the images inside its container. Each image in the slider will have its own x position stored. The x position will be used to animate the slider images inside its container, this will create the parallax effect.

const intersectX1 = slider.items[0].x;
const intersectX2 = slider.items[1].x;
const intersectX3 = slider.items[2].x;

A new GSAP timeline will be created for the expand animation. Once the timeline is completed setHoverAnimation will be fired so the placeholder items are reset and ready to animate again. Also we will start the reveal animation of the slider elements such as the text on the items and the close button, but we will not go into the text animations. You can find that in the final code.

this.tl = gsap.timeline({
  onComplete: () => {
    this.setHoverAnimation();
    slider.open();
  }
});

The 3 placeholder images will animate to the x and y position that we defined earlier. Let’s have a look at the GSAP timeline.

this.tl
  .addLabel('start')
  .to(this.dom.images[0], { duration: 1.67, ease: 'power3.inOut', x: -x1, y: -y1, scale, rotation: 0 }, 'start')
  .to(this.dom.images[1], { duration: 1.67, ease: 'power3.inOut', x: -x2, y: -y2, scale, rotation: 0 }, 'start')
  .to(this.dom.images[2], { duration: 1.67, ease: 'power3.inOut', x: -x3, y: -y3, scale, rotation: 0 }, 'start')
  .to(this.dom.images[0].querySelector('img'), { duration: 1.67, ease: 'power3.inOut', x: intersectX1 }, 'start')
  .to(this.dom.images[1].querySelector('img'), { duration: 1.67, ease: 'power3.inOut', x: intersectX2 }, 'start')
  .to(this.dom.images[2].querySelector('img'), { duration: 1.67, ease: 'power3.inOut', x: intersectX3 }, 'start',)
  .set(this.dom.el, { autoAlpha: 0 }, 'start+=1.67')

3. Parallax effect

The images are scaled up in its container that will have `overflow: hidden`. Once you move the slider, the images that are in the viewport will move with a slightly different speed.

Markup

<div class="slider js-slider">
  <div class="slider__container js-container" data-scroll>
    <div class="slider__item js-item" style="--aspect-ratio: 0.8;">
      <div class="slider__item-img-wrap js-img-wrap js-img" style="--aspect-ratio: 0.8;">
        <img
          src="https://images.unsplash.com/photo-1472835560847-37d024ebacdc?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=80&ar=0.8"
          class="slider__item-img"
        >
      </div>

      <div class="slider__item-content">
        <div class="slider__item-heading-wrap">
          <h3 class="slider__item-heading">
            Indigo
          </h3>
        </div>

        <div class="slider__item-button-wrap">
          <button class="button slider__item-button" type="button">
            Read more
          </button>
        </div>
      </div>
    </div>

    ...
  </div>
</div>
<div class="slider__progress-wrap js-progress-wrap">
  <div class="slider__progress js-progress"></div>
</div>

Animation

We will be using a smooth scroll on top of Virtual Scroll, but we will not go into how to set this up (please check the documentation). With interpolation, we can achieve this smooth parallax scrolling effect.

Once again we have a this.dom object where we will store all the DOM elements that we need.

this.dom = {};
this.dom.el = document.querySelector('.js-slider');
this.dom.container = this.dom.el.querySelector('.js-container');
this.dom.items = this.dom.el.querySelectorAll('.js-item');
this.dom.images = this.dom.el.querySelectorAll('.js-img-wrap');
this.dom.progress = this.dom.el.querySelector('.js-progress');

To keep track of the scroll progress we created a simple progress bar which defines how much we moved the slider. We simply calculate a value between 0 and 1 that represents the x position of the slider container. This value will be updated in a requestAnimationFrame and used to animate the scaleX value of the progress bar.

const max = -this.dom.container.offsetWidth + window.innerWidth;
const progress = ((scroll.state.last - 0) * 100) / (max - 0) / 100;

this.dom.progress.style.transform = `scaleX(${progress})`;

Before we start the animation of the parallax effect on the images we first store some data of each slider item in the array this.items. This array wil contain the image element, the bounds and the x position of the image.

setCache() {
  this.items = [];
  [...this.dom.items].forEach((el) => {
    const bounds = el.getBoundingClientRect();

    this.items.push({
      img: el.querySelector('img'),
      bounds,
      x: 0,
    });
  });
}

Now we’re finally ready to create the parallax effect on the images. The code below will be executed in a render function in a requestAnimationFrame. In this function we will be using the interpolated value of our scroll (scroll.state.last).

For a better performance we will only animate the images that are in the viewport. To do so we will detect which items are visible in the viewport.

const { bounds } = item;
const inView = scrollLast + window.innerWidth >= bounds.left && scrollLast < bounds.right;

If the item is visible in the viewport, we will calculate a value between 0 and 100 (percentage) that indicates how much of the target element is actually visible within the viewport.

const min = bounds.left - window.innerWidth;
const max = bounds.right;
const percentage = ((scrollLast - min) * 100) / (max - min);

Once we have that value stored, we can calculate a new value based on percentage that we will then transform into a pixel value like this.

const newMin = -(window.innerWidth / 12) * 3;
const newMax = 0;
item.x = ((percentage - 0) / (100 - 0)) * (newMax - newMin) + newMin;

After calculating that final x value we can now simply animate the image inside its container like this.

item.img.style.transform = `translate3d(${item.x}px, 0, 0)`;

This is what the render function looks like.

render() {
  const scrollLast = scroll.state.last;

  this.items.forEach((item) => {
    const { bounds } = item;
    const inView = scrollLast + window.innerWidth >= bounds.left && scrollLast < bounds.right;

    if (inView) {
      const min = bounds.left - window.innerWidth;
      const max = bounds.right;
      const percentage = ((scrollLast - min) * 100) / (max - min);
      const newMin = -(window.innerWidth / 12) * 3;
      const newMax = 0;
      item.x = ((percentage - 0) / (100 - 0)) * (newMax - newMin) + newMin;

      item.img.style.transform = `translate3d(${item.x}px, 0, 0) scale(1.75)`;
    }
  });
}

I hope this has been not too difficult to follow and that you have gained some insight into creating this parallax slider.

If you would like to see this slider live in action, let’s have a look at the architectural website for Nieuw Bergen by Gewest13. It’s used to showcase their seven buildings.

Please let me know if you have any questions @rluijtenant.

The post Crafting a Scrollable and Draggable Parallax Slider appeared first on Codrops.

Building a Svelte Static Website with Smooth Page Transitions

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, you’ll learn how image transitions with GLSL and Three.js work and how to build a static website with Svelte.js that will be using a third party API. Finally, we’ll code some smooth page transitions using GSAP and Three.js.

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

Check out the live demo.

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

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

The post Building a Svelte Static Website with Smooth Page Transitions appeared first on Codrops.

Image Stack Intro Animation

Today I want to share a simple intro animation with you. The concept is to first show an image stack and then animate each image to its position in the grid (or any other layout).

For the first demo, I actually used a simple serial layout and added some smooth scrolling with Locomotive Scroll. The animations are powered by GSAP.

The inspiration for this animation came from this beautiful intro animation Dribbble shot by Gil.

In the first demo the stack images move to a simple consecutive layout:

In the second demo, the images move to their respective position in the grid:

I really hope you have fun with this and thanks for checking it out!

The post Image Stack Intro Animation appeared first on Codrops.

Creating WebGL Effects with CurtainsJS

This article focuses adding WebGL effects to <image> and <video> elements of an already “completed” web page. While there are a few helpful resources out there on this subject (like these two), I hope to help simplify this subject by distilling the process into a few steps: 

  • Create a web page as you normally would.
  • Render pieces that you want to add WebGL effects to with WebGL.
  • Create (or find) the WebGL effects to use.
  • Add event listeners to connect your page with the WebGL effects.

Specifically, we’ll focus on the connection between regular web pages and WebGL. What are we going to make? How about a draggle image slider with an interactive mouse hover!

We won’t cover the core functionality of slider or go very far into the technical details of WebGL or GLSL shaders. However, there are plenty of comments in the demo code and links to outside resources if you’d like to learn more. 

We’re using the latest version of WebGL (WebGL2) and GLSL (GLSL 300) which currently do not work in Safari or in Internet Explorer. So, use Firefox or Chrome to view the demos. If you’re planning to use any of what we’re covering in production, you should load both the GLSL 100 and 300 versions of the shaders and use the GLSL 300 version only if curtains.renderer._isWebGL2 is true. I cover this in the demo above.

First, create a web page as you normally would

You know, HTML and CSS and whatnot. In this case, we’re making an image slider but that’s just for demonstration. We’re not going to go full-depth on how to make a slider (Robin has a nice post on that). But here’s what I put together:

  1. Each slide is equal to the full width of the page.
  2. After a slide has been dragged, the slider continues to slide in the direction of the drag and gradually slow down with momentum.
  3. The momentum snaps the slider to the nearest slide at the end point. 
  4. Each slide has an exit animation that’s fired when the drag starts and an enter animation that’s fired when the dragging stops.
  5. When hovering the slider, a hover effect is applied similar to this video.

I’m a huge fan of the GreenSock Animation Platform (GSAP). It’s especially useful for us here because it provides a plugin for dragging, one that enables momentum on drag, and one for splitting text by line . If you’re uncomfortable creating sliders with GSAP, I recommend spending some time getting familiar with the code in the demo above.

Again, this is just for demonstration, but I wanted to at least describe the component a bit. These are the DOM elements that we will keep our WebGL synced with. 

Next, use WebGL to render the pieces that will contain WebGL effects

Now we need to render our images in WebGL. To do that we need to:

  1. Load the image as a texture into a GLSL shader.
  2. Create a WebGL plane for the image and correctly apply the image texture to the plane.
  3. Position the plane where the DOM version of the image is and scale it correctly.

The third step is particularly non-trivial using pure WebGL because we need to track the position of the DOM elements we want to port into the WebGL world while keeping the DOM and WebGL parts in sync during scroll and user interactions.

There’s actually a library that helps us do all of this with ease: CurtainsJS! It’s the only library I’ve found that easily creates WebGL versions of DOM images and videos and syncs them without too many other features (but I’d love to be proven wrong on that point, so please leave a comment if you know of others that do this well).

With Curtains, this is all the JavaScript we need to add:

// Create a new curtains instance
const curtains = new Curtains({ container: "canvas", autoRender: false });
// Use a single rAF for both GSAP and Curtains
function renderScene() {
  curtains.render();
}
gsap.ticker.add(renderScene);
// Params passed to the curtains instance
const params = {
  vertexShaderID: "slider-planes-vs", // The vertex shader we want to use
  fragmentShaderID: "slider-planes-fs", // The fragment shader we want to use
  
 // Include any variables to update the WebGL state here
  uniforms: {
    // ...
  }
};
// Create a curtains plane for each slide
const planeElements = document.querySelectorAll(".slide");
planeElements.forEach((planeEl, i) => {
  const plane = curtains.addPlane(planeEl, params);
  // const plane = new Plane(curtains, planeEl, params); // v7 version
  // If our plane has been successfully created
  if(plane) {
    // onReady is called once our plane is ready and all its texture have been created
    plane.onReady(function() {
      // Add a "loaded" class to display the image container
      plane.htmlElement.closest(".slide").classList.add("loaded");
    });
  }
});

We also need to update our updateProgress function so that it updates our WebGL planes.

function updateProgress() {
  // Update the actual slider
  animation.progress(wrapVal(this.x) / wrapWidth);
  
  // Update the WebGL slider planes
  planes.forEach(plane => plane.updatePosition());
}

We also need to add a very basic vertex and fragment shader to display the texture that we’re loading. We can do that by loading them via <script> tags, like I do in the demo, or by using backticks as I show in the final demo.  

Again, this article will not go into a lot of detail on the technical aspects of these GLSL shaders. I recommend reading The Book of Shaders and the WebGL topic on Codrops as starting points.

If you don’t know much about shaders, it’s sufficient to say that the vertex shader positions the planes and the fragment shader processes the texture’s pixels. There are also three variable prefixes that I want to point out:

  • ins are passed in from a data buffer. In vertex shaders, they come from the CPU (our program). In fragment shaders, they come from the vertex shader.
  • uniforms are passed in from the CPU (our program).
  • outs are outputs from our shaders. In vertex shaders, they are passed into our fragment shader. In fragment shaders, they are passed to the frame buffer (what is drawn to the screen).

Once we’ve added all of that to our project, we have the same exact thing before but our slider is now being displayed via WebGL! Neat.

CurtainsJS easily converts images and videos to WebGL. As far as adding WebGL effects to text, there are several different methods but perhaps the most common is to draw the text to a <canvas> and then use it as a texture in the shader (e.g. 1, 2). It’s possible to do most other HTML using html2canvas (or similar) and use that canvas as a texture in the shader; however, this is not very performant.

Create (or find) the WebGL effects to use

Now we can add WebGL effects since we have our slider rendering with WebGL. Let’s break down the effects seen in our inspiration video:

  1. The image colors are inverted.
  2. There is a radius around the mouse position that shows the normal color and creates a fisheye effect.
  3. The radius around the mouse animates from 0 when the slider is hovered and animates back to 0 when it is no longer hovered.
  4. The radius doesn’t jump to the mouse’s position but animates there over time.
  5. The entire image translates based on the mouse’s position in reference to the center of the image.

When creating WebGL effects, it’s important to remember that shaders don’t have a memory state that exists between frames. It can do something based on where the mouse is at a given time, but it can’t do something based on where the mouse has been all by itself. That’s why for certain effects, like animating the radius once the mouse has entered the slider or animating the position of the radius over time, we should use a JavaScript variable and pass that value to each frame of the slider. We’ll talk more about that process in the next section.

Once we modify our shaders to invert the color outside of the radius and create the fisheye effect inside of the radius, we’ll get something like the demo below. Again, the point of this article is to focus on the connection between DOM elements and WebGL so I won’t go into detail about the shaders, but I did add comments to them.

But that’s not too exciting yet because the radius is not reacting to our mouse. That’s what we’ll cover in the next section.

I haven’t found a repository with a lot of pre-made WebGL shaders to use for regular websites. There’s ShaderToy and VertexShaderArt (which have some truly amazing shaders!), but neither is aimed at the type of effects that fit on most websites. I’d really like to see someone create a repository of WebGL shaders as a resource for people working on everyday sites. If you know of one, please let me know.

Add event listeners to connect your page with the WebGL effects

Now we can add interactivity to the WebGL portion! We need to pass in some variables (uniforms) to our shaders and affect those variables when the user interacts with our elements. This is the section where I’ll go into the most detail because it’s the core for how we connect JavaScript to our shaders.

First, we need to declare some uniforms in our shaders. We only need the mouse position in our vertex shader:

// The un-transformed mouse position
uniform vec2 uMouse;

We need to declare the radius and resolution in our fragment shader:

uniform float uRadius; // Radius of pixels to warp/invert
uniform vec2 uResolution; // Used in anti-aliasing

Then let’s add some values for these inside of the parameters we pass into our Curtains instance. We were already doing this for uResolution! We need to specify the name of the variable in the shader, it’s type, and then the starting value:

const params = {
  vertexShaderID: "slider-planes-vs", // The vertex shader we want to use
  fragmentShaderID: "slider-planes-fs", // The fragment shader we want to use
  
  // The variables that we're going to be animating to update our WebGL state
  uniforms: {
    // For the cursor effects
    mouse: { 
      name: "uMouse", // The shader variable name
      type: "2f",     // The type for the variable - https://webglfundamentals.org/webgl/lessons/webgl-shaders-and-glsl.html
      value: mouse    // The initial value to use
    },
    radius: { 
      name: "uRadius",
      type: "1f",
      value: radius.val
    },
    
    // For the antialiasing
    resolution: { 
      name: "uResolution",
      type: "2f", 
      value: [innerWidth, innerHeight] 
    }
  },
};

Now the shader uniforms are connected to our JavaScript! At this point, we need to create some event listeners and animations to affect the values that we’re passing into the shaders. First, let’s set up the animation for the radius and the function to update the value we pass into our shader:

const radius = { val: 0.1 };
const radiusAnim = gsap.from(radius, { 
  val: 0, 
  duration: 0.3, 
  paused: true,
  onUpdate: updateRadius
});
function updateRadius() {
  planes.forEach((plane, i) => {
    plane.uniforms.radius.value = radius.val;
  });
}

If we play the radius animation, then our shader will use the new value each tick.

We also need to update the mouse position when it’s over our slider for both mouse devices and touch screens. There’s a lot of code here, but you can walk through it pretty linearly. Take your time and process what’s happening.

const mouse = new Vec2(0, 0);
function addMouseListeners() {
  if ("ontouchstart" in window) {
    wrapper.addEventListener("touchstart", updateMouse, false);
    wrapper.addEventListener("touchmove", updateMouse, false);
    wrapper.addEventListener("blur", mouseOut, false);
  } else {
    wrapper.addEventListener("mousemove", updateMouse, false);
    wrapper.addEventListener("mouseleave", mouseOut, false);
  }
}


// Update the stored mouse position along with WebGL "mouse"
function updateMouse(e) {
  radiusAnim.play();
  
  if (e.changedTouches && e.changedTouches.length) {
    e.x = e.changedTouches[0].pageX;
    e.y = e.changedTouches[0].pageY;
  }
  if (e.x === undefined) {
    e.x = e.pageX;
    e.y = e.pageY;
  }
  
  mouse.x = e.x;
  mouse.y = e.y;
  
  updateWebGLMouse();
}


// Updates the mouse position for all planes
function updateWebGLMouse(dur) {
  // update the planes mouse position uniforms
  planes.forEach((plane, i) => {
    const webglMousePos = plane.mouseToPlaneCoords(mouse);
    updatePlaneMouse(plane, webglMousePos, dur);
  });
}


// Updates the mouse position for the given plane
function updatePlaneMouse(plane, endPos = new Vec2(0, 0), dur = 0.1) {
  gsap.to(plane.uniforms.mouse.value, {
    x: endPos.x,
    y: endPos.y,
    duration: dur,
    overwrite: true,
  });
}


// When the mouse leaves the slider, animate the WebGL "mouse" to the center of slider
function mouseOut(e) {
  planes.forEach((plane, i) => updatePlaneMouse(plane, new Vec2(0, 0), 1) );
  
  radiusAnim.reverse();
}

We should also modify our existing updateProgress function to keep our WebGL mouse synced.

// Update the slider along with the necessary WebGL variables
function updateProgress() {
  // Update the actual slider
  animation.progress(wrapVal(this.x) / wrapWidth);
  
  // Update the WebGL slider planes
  planes.forEach(plane => plane.updatePosition());
  
  // Update the WebGL "mouse"
  updateWebGLMouse(0);
}

Now we’re cooking with fire! Our slider now mets all of our requirements.

Two additional benefits of using GSAP for your animations is that it provides access to callbacks, like onComplete, and GSAP keeps everything perfectly synced no matter the refresh rate (e.g. this situation).

You take it from here!

This is, of course, just the tip of the iceberg when it comes to what we can do with the slider now that it is in WebGL. For example,  common effects like turbulence and displacement can be added to the images in WebGL. The core concept of a displacement effect is to move pixels around based on a gradient lightmap that we use as an input source. We can use this texture (that I pulled from this displacement demo by Jesper Landberg — you should give him a follow) as our source and then plug it into our shader. 

To learn more about creating textures like these, see this article, this tweet, and this tool. I am not aware of any existing repositories of images like these, but if you know of one please, let me know.

If we hook up the texture above and animate the displacement power and intensity so that they vary over time and based on our drag velocity, then it will create a nice semi-random, but natural-looking displacement effect:

It’s also worth noting that Curtains has its own React version if that’s how you like to roll.

That’s all I’ve got for now. If you create something using what you’ve learned from this article, I’d love to see it! Connect with me via Twitter.


The post Creating WebGL Effects with CurtainsJS appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Collective #626









Why Tailwind CSS

Shawn Wang shares why he changed his mind on Tailwind CSS, and why he now considers it the “Goldilocks Styling Solution”.

Read it




Clamp

Trys Mudford dives into one of CSS’s exciting new features for fluid scaling, clamp() for Utopia.

Read it











The post Collective #626 appeared first on Codrops.

Recreating the “100 Days of Poetry” Effect with Shader, ScrollTrigger and CSS Grid

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 that he records twice a month plus the demo he creates during the session!

For this episode of ALL YOUR HTML, I decided to replicate the effect seen on 100 DAYS OF POETRY with the technologies I know. So I did a fragment shader for the background effect, then coded a dummy CSS grid, and connected both of them with the power of GSAP’s ScrollTrigger plugin. This is of course a really simplified version of the website, but I hope you will learn something from the process of recreating this.

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

Check out the live demo.

Original website: https://100daysofpoetry.gallery/

By Keita Yamada https://twitter.com/p5_keita

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

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

The post Recreating the “100 Days of Poetry” Effect with Shader, ScrollTrigger and CSS Grid appeared first on Codrops.

Animating React Components With GreenSock

During the early days of the World Wide Web, things were rather static and boring. Webpages were mostly based on graphic design and layouts from the print world until animations were introduced. Animation can engage and hold people’s attention longer than a static web page and communicates an idea or concept more clearly and effectively.

However, when not done right, animations can hamper user interactions with your product and negatively impact traction. The GreenSock Animation Platform AKA (GSAP) is a powerful JavaScript library that enables front-end developers, animators and designers to create performant timeline based animations. It allows animation lovers take precise control of their animation sequences rather than the sometimes constraining keyframe and animation properties that CSS offers.

In this article, I’ll introduce you to some features of GSAP such as scrollTriggers, Timelines, Easing etc, at the end we’ll build an intuitive user interface by animating a React app with this features👌. Check out the finished project on codesandbox.

This article will be useful to you if:

  • You have been building animations on web applications with HTML, CSS, and JavaScript.
  • You are already building animated webpages in a React apps with packages like animate.css, React-motion, Framer-motion, and React-Spring, plus you want to check out alternatives.
  • You are a React enthusiast, and you’d like to build complex animations on React-based web applications.

We will look at how to build a variety of animations from an existing web project. Let’s get to it!

Note: This article assumes you are comfortable with HTML, CSS, JavaScript, and React.js.

What Is GSAP?

GreenSock Animation Platform also known as GSAP is an Ultra high-performance, professional-grade animation for the modern web that allows developers to animate their apps in a modular, declarative, and re-usable fashion. It is framework-agnostic and can be used across any JavaScript based project, it has a very minimal bundle size and will not bloat your app.

GSAP can perform canvas animations, used to create WebGL experiences, and create dynamic SVG animations and as great browser support.

Why Use GSAP?

Maybe you’re not quite ready to betray other frameworks yet, or you haven’t been convinced to embrace the goodies that come with GSAP. Allow me to give you a few reason why you may want to consider GSAP.

You Can Build Complex Animations

GSAP JavaScript library makes it possible for developers to build simple to very complex physics-based animations like in the case of these sites, it allows developers and designers sequence motion and controls the animation dynamically. It has lots of plugins such as DrawSVGPlugin, MorphSVGPlugin, and more, which makes creating SVG based animations and 2D/3D animations a reality. Asides integrating GSAP on DOM elements, you can use them within WebGL/Canvas/ Three.js context-based animations.

Furthermore, the easing capacity of GSAP is quite sophisticated, hence making it possible to create advance effects with multiple beziers as compared to the regular CSS animation.

Performance

GSAP has an impressive high performance across different browsers.

According to GSAP’s team, in their website, “GSAP is 20x faster than jQuery, plus GSAP is the fastest full-featured scripted animation tool on the planet. It's even faster than CSS3 animations and transitions in many cases.” Confirm speed comparison for yourself.

Furthermore, the GSAP animations perform effortlessly on both desktop computers, tablets, and smartphones. It is not needed to add a long list of prefixes, this is all being taken care of under the hood by GSAP.

You can check out more benefits on GSAP or see what Sarah Drasner as to say about it here.

Cons Of GSAP

Are you saying I should always use GSAP for every project? Of course not! I feel like, there’s only one reason you might not want to use GSAP. Let’s find out!

  • GSAP is solely a JavaScript-based animation library, hence it requires some knowledge of JavaScript and DOM manipulation to effectively utilize its methods and APIs. This learning curve downside leaves even more room for complications for a beginner starting out with JavaScript.
  • GSAP doesn’t cater to CSS based animations, hence if you are looking for a library for such, you might as well use keyframes in CSS animation.

If you’ve got any other reason, feel free to share it in the comment section.

Alright, now that your doubts are cleared, let’s jump over to some nitty-gritty in GSAP.

GSAP Basics

Before we create our animation using React, let’s get familiar with some methods and building blocks of GSAP.

If you already know the fundamentals of GSAP, you can skip this section and jump straight to the project section, where we’ll make a landing page skew while scrolling.

Tween

A tween is a single movement in an animation. In GSAP, a tween has the following syntax:

TweenMax.method(element, duration, vars)

Let’s take a look at what this syntax represents;

  1. method refers to the GSAP method you’ll like to tween with.
  2. element is the element you want to animate. If you want to create tweens for multiple elements at the same time, you can pass in an array of elements to element.
  3. duration is the duration of your tween. It is an integer in seconds (without the s suffix!).
  4. vars is an object of the properties you want to animate. More on this later.

GSAP methods

GSAP provides numerous methods to create animations. In this article, we’d mention only a few such as gsap.to, gsap.from, gsap.fromTo. You can check out other cool methods in their documentation. The methods discussed in this section will be used in building our project later in this tutorial.

  • gsap.to() the values to which an object should be animated i.e the end property values of an animated object — as shown below:
    gsap.to('.ball', {x:250, duration: 5})

To demonstrate the to method the codepen demo below shows that an element with a class of ball 250px will move across the x-axis in five seconds when the components mounts. If a duration isn't given, a default of 500 milliseconds would be used.

See the Pen GSAP REACT DEMO1 by Blessing Krofegha.

Note: x and y-axis represent the horizontal and vertical axis respectively, also in CSS transform properties such as translateX and translateY they are represented as x and y for pixel-measured transforms and xPercent and yPercent for percentage-based transforms.

To view the complete snippet of the code check the codepen playground.

  • gsap.from() — Defines the values an object should be animated from — i.e., the start values of an animation:
    gsap.from('.square', {duration:3, scale: 4})

The codepen demo show how an element with a class of square is resized from a scale of 4 in 3seconds when the components mounts. Check for the complete code snippet on this codepen.

See the Pen GSAP REACT DEMO2 by Blessing Krofegha.

  • gsap.fromTo() — lets you define the starting and ending values for an animation. It is a combination of both the from() and to() method.

Here’s how it looks;

gsap.fromTo('.ball',{opacity:0 }, {opacity: 1 , x: 200 , duration: 3 });
gsap.fromTo('.square', {opacity:0, x:200}, { opacity:1, x: 1 , duration: 3 });

This code would animates the element with a class of ball from an opacity of 0 to an opacity of 1 across the x-axis in 3 seconds and the square class is animated the from an opacity of 0 to 1 in 3 seconds across the x-axis only when the component mounts. To see how the fromTo method works and the complete code snippet, check the demo on CodePen below.

See the Pen React GSAP FromTo demo by Blessing Krofegha.

Note: Whenever we’re animating positional properties, such as left and top, we must ensure that the elements concerned must have a CSS position property of either relative, absolute, or fixed.

Easing

GSAP official documentation defined easing as the primary way to change the timing of your Tweens. It determines how an object changes position at different points. Ease controls the rate of change of animation in GSAP and is used to set the style of an object’s animation.

GSAP provides different types of eases and options to give you more control over how your animation should behave. It also provides an Ease Visualizer to help you choose your preferred ease settings.

There are three types of eases, and they vary in their operations.

  1. in() — Motion starts slowly, then picks up the pace toward the end of the animation.
  2. out() — The animation starts fast then slows down at the end of the animation.
  3. inOut() — The animation begins slow, picks up the pace halfway through, and ends slowly.

See the Pen React GSAP Easing demo by Blessing Krofegha.

In these easing example, we chained the tweens that displayed the three types of eases bounce.in, bounce.out and bounce.inOut, and set a delay of the number of seconds it takes the animation to complete before starting the next one only when the component is mounts. This pattern is repetitive, in the next next section we would see how we could use a timeline to do this better.

Timelines

A Timeline acts as a container for multiple tweens. It animates tweens in sequential order, and it is not dependent on the duration of the previous tween. Timeline makes it simple to control tweens as a whole and precisely manage their timing.

Timelines can be written by creating an instance of a timeline like so:

gsap.timeline();

You can also chain multiple tweens to a timeline in two different ways, in the code below:

##Method 1
const tl = gsap.timeline(); // create an instance and assign it a variable
tl.add(); // add tween to timeline 
tl.to('element', {});
tl.from('element', {});

##Method 2
gsap.timeline()
    .add() // add tween to timeline 
    .to('element', {})
    .from('element', {})

Let’s recreate the previous example with a timeline:

const { useRef, useEffect } = React;

const Balls = () => {
    useEffect(() => {
const tl = gsap.timeline(); tl.to('#ball1', {x:1000, ease:"bounce.in", duration: 3}) tl.to('#ball2', {x:1000, ease:"bounce.out", duration: 3, delay:3 }) tl.to('#ball3', {x:1000, ease:"bounce.inOut", duration: 3, delay:6 }) }, []); } ReactDOM.render(, document.getElementById('app'));

Inside a useEffect hook, we created a variable(tl) that holds an instance of a timeline, next we used the tl variable to animate our tween in sequential without depending on the previous tween to animate, passing the same properties as it were in the previous example. For the complete code snippet of this demo check the codepen playground below.

See the Pen React GSAP (Easing with Timeline) demo by Blessing Krofegha.

Now that we have gotten a feel of some the basic building blocks of GSAP, let’s see how we could build a complete animation in a typical React app in the next section. Let’s begin the flight! 🚀

Building an Animated Landing Page with React and GSAP

Let’s get to animate a React App. Ensure you clone the repo before you begin and run npm install, to install the dependencies.

What are we building?

Currently, our landing page contains a few texts a white background, a menu that doesn’t drop down, with really no animation. The following are what we’ll be adding to the landing page;

  • Animate the text and the logo on the homepage, so it eases out when the component is mounted.
  • Animate the menu, so it drops down when the menu is clicked.
  • Make the images in the gallery page skew 20deg when the page scrolls.
Animated page.

Check out the demo on codesandbox.

We’ll break the process of our landing page into components, so it will be easy to grasp. Here’s the process;

  • Define the animation methods,
  • Animate text and logo,
  • Toggle menu,
  • Make images skew 20deg on page scroll.

components

  • Animate.js — Defined all animation methods,
  • Image.js — import galley images,
  • Menu.js — Contains the menu toggle functionality,
  • Header.js — Contains navigation links.

Define animation methods

Create a component folder inside the src directory, and create an animate.js file. Copy and paste the following code into it.

import gsap from "gsap"
import { ScrollTrigger } from "gsap/ScrollTrigger";
//Animate text 
export const textIntro = elem => {
  gsap.from(elem, {
    xPercent: -20,
    opacity: 0,
    stagger: 0.2,
    duration: 2,
    scale: -1,
    ease: "back",
  });
};

Here, we imported gsap . We wrote an exported arrow function that animates the text on the landing page. Remember that gsap.from() method defines the values an object should be animated from. The function has an elem parameter that represents the class which needs to be animated. It takes a few properties and assigns values such as xPercent: -20 (transforms the object by -20%), gives the object no opacity, makes the object scale by -1, makes the object ease back in 2sec.

To see if this works, head over to App.js and include the following code.

...
//import textIntro
import {textIntro} from "./components/Animate"

...
//using useRef hook to access the textIntro DOM
 let intro = useRef(null)
  useEffect(() => {
    textIntro(intro)
  }, [])

function Home() {
  return (
    <div className='container'>
      <div className='wrapper'>
        <h5 className="intro" ref={(el) => (intro = el)}></h5>
          The <b>SHOPPER</b>, is a worldclass, innovative, global online ecommerce platform,
          that meets your everyday daily needs.
        </h5>
      </div>
    </div>
  );
}

Here, we import the textIntro method from the Aminate component. To access the DOM we used to useRef Hook. We created a variable intro whose value is set to null. Next, inside the useEffect hook, we called the textIntro method and the intro variable. Inside our home component, in the h5 tag, we defined the ref prop and passed in the intro variable.

Animated text.

Next, we have got a menu, but it isn’t dropping down when it’s clicked. Let’s make it work! Inside the Header.js Component, add the code below.

import React, { useState, useEffect, useRef } from "react";
import { withRouter, Link, useHistory } from "react-router-dom";
import Menu from "./Menu";
const Header = () => {
  const history = useHistory()
  let logo = useRef(null);
  //State of our Menu
  const [state, setState] = useState({
    initial: false,
    clicked: null,
    menuName: "Menu",
  });
  // State of our button
  const [disabled, setDisabled] = useState(false);
  //When the component mounts
  useEffect(() => {
    textIntro(logo);
    //Listening for page changes.
    history.listen(() => {
      setState({ clicked: false, menuName: "Menu" });
    });
  }, [history]);
  //toggle menu
  const toggleMenu = () => {
    disableMenu();
    if (state.initial === false) {
      setState({
        initial: null,
        clicked: true,
        menuName: "Close",
      });
    } else if (state.clicked === true) {
      setState({
        clicked: !state.clicked,
        menuName: "Menu",
      });
    } else if (state.clicked === false) {
      setState({
        clicked: !state.clicked,
        menuName: "Close",
      });
    }
  };
  // check if out button is disabled
  const disableMenu = () => {
    setDisabled(!disabled);
    setTimeout(() => {
      setDisabled(false);
    }, 1200);
  };
  return (
    <header>
      <div className="container">
        <div className="wrapper">
          <div className="inner-header">
            <div className="logo" ref={(el) => (logo = el)}>
              <Link to="/">SHOPPER.</Link>
            </div>
            <div className="menu">
              <button disabled={disabled} onClick={toggleMenu}>
                {state.menuName}
              </button>
            </div>
          </div>
        </div>
      </div>
      <Menu state={state} />
    </header>
  );
};
export default withRouter(Header);

In this component, we defined our menu and button state, inside the useEffect hook, we listened for page changes using useHistory hook, if the page changes we set the clicked and menuName state values to false and Menu respectively.

To handle our menu, we checked if the value of our initial state is false, if true, we change the value of initial , clicked and menuName to null, true and Close. Else we check if the button is clicked, if true we’d change the menuName to Menu. Next, we have a disabledMenu function that disables our button for 1sec when it’s clicked.

Lastly, in our button, we assigned disabled to disabled which is a boolean value that will disable the button when its value is true. And the onClick handler of the button is tied to the toggleMenu function. All we did here was toggle our menu text and passed the state to a Menu component, which we would create soonest. Let’s write the methods that will make our menu dropdown before creating the actual Menu component. Head over to Animate.js and paste this code into it.

....
//Open menu
export const menuShow = (elem1, elem2) => {
  gsap.from([elem1, elem2], {
    duration: 0.7,
    height: 0,
    transformOrigin: "right top",
    skewY: 2,
    ease: "power4.inOut",
    stagger: {
      amount: 0.2,
    },
  });
};
//Close menu
export const menuHide = (elem1, elem2) => {
  gsap.to([elem1, elem2], {
    duration: 0.8,
    height: 0,
    ease: "power4.inOut",
    stagger: {
      amount: 0.07,
    },
  });
};

Here, we have a function called menuShow, which skews the menu horizontally by 2degrees, eases the menu, offset’s the animation using the stagger property, and transforms the menu from right to top in 0.7sec, the same properties go for the menuHide function. To use these functions, create Menu.js file inside the components and paste this code into it.

import React, {useEffect, useRef} from 'react'
import { gsap } from "gsap"
import { Link } from "react-router-dom"
import {
  menuShow,
  menuHide,
  textIntro,
} from './Animate'
const Menu = ({ state }) => {
   //create refs for our DOM elements

  let menuWrapper = useRef(null)
  let show1 = useRef(null)
  let show2 = useRef(null)
  let info = useRef(null)
  useEffect(() => {
    // If the menu is open and we click the menu button to close it.
    if (state.clicked === false) {
      // If menu is closed and we want to open it.
      menuHide(show2, show1);
      // Set menu to display none
      gsap.to(menuWrapper, { duration: 1, css: { display: "none" } });
    } else if (
      state.clicked === true ||
      (state.clicked === true && state.initial === null)
    ) {
      // Set menu to display block
      gsap.to(menuWrapper, { duration: 0, css: { display: "block" } });
      //Allow menu to have height of 100%
      gsap.to([show1, show2], {
        duration: 0,
        opacity: 1,
        height: "100%"
      });
      menuShow(show1, show2);
      textIntro(info);

    }
  }, [state])

  return (
    <div ref={(el) => (menuWrapper = el)} className="hamburger-menu">
      <div
        ref={(el) => (show1 = el)}
        className="menu-secondary-background-color"
      ></div>
      <div ref={(el) => (show2 = el)} className="menu-layer">
        <div className="container">
          <div className="wrapper">
            <div className="menu-links">
              <nav>
                <ul>
                  <li>
                    <Link
                      ref={(el) => (line1 = el)}
                      to="/about-us"
                    >
                      About
                    </Link>
                  </li>
                  <li>
                    <Link
                      ref={(el) => (line2 = el)}
                      to="/gallery"
                    >
                      Gallery
                    </Link>
                  </li>
                  <li>
                    <Link
                      ref={(el) => (line3 = el)}
                      to="/contact-us"
                    >
                      Contact us
                    </Link>
                  </li>

                </ul>
              </nav>
              <div ref={(el) => (info = el)} className="info">
                <h3>Our Vision</h3>
                <p>
                  Lorem ipsum dolor sit amet consectetur adipisicing elit....
                </p>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}
export default Menu

What we did in the Menu component was to import the animated functions, which are menuShow, menuHide, and textIntro. Next, we assigned variables for each created refs for our DOM elements using the useRef hook and passed null as their values. Inside the useEffect hook, we check for the state of the menu, if clicked is false, we call the menuHide function, otherwise, if the clicked state is true we call the menuShow function. Lastly, we ensured that the DOM elements concerned are passed their specific refs which are menuWrapper, show1, show2. With that, we’ve got our menu animated.

Let’s see how it looks.

Animated Menu.

The last animation we would implement is make our images in our gallery skew when it scrolls. Let’s see the state of our gallery now.

Gallery without animation.

To implement the skew animation on our gallery, let’s head over to Animate.js and add a few codes to it.

....
//Skew gallery Images
export const skewGallery = elem1 => {
  //register ScrollTrigger
  gsap.registerPlugin(ScrollTrigger);
  // make the right edge "stick" to the scroll bar. force3D: true improves performance
    gsap.set(elem1, { transformOrigin: "right center", force3D: true });
    let clamp = gsap.utils.clamp(-20, 20) // don't let the skew go beyond 20 degrees. 
    ScrollTrigger.create({
      trigger: elem1,
      onUpdate: (self) => {
        const velocity = clamp(Math.round(self.getVelocity() / 300));
        gsap.to(elem1, {
          skew: 0,
          skewY: velocity,
          ease: "power3",
          duration: 0.8,
        });
      },
    });
}

We created a function called skewGallery, passed elem1 as a param, and registered ScrollTrigger.

ScrollTrigger is a plugin in GSAP that enables us to trigger scroll-based animations, like in this case of skewing the images while the page scrolls.

To make the right edge stick to the scroll bar we passed right center value to the transformOrigin property, we also set the force3D property to true in other to improve the performance.

We declared a clamp variable that calculates our skew and ensures it doesn’t exceed 20degs. Inside the ScrollTrigger object, we assigned the trigger property to the elem1 param, which would be the element that needs to be triggered when we call this function. We have an onUpdate callback function, inside it is a velocity variable that calculates the current velocity and divides it by 300.

Lastly, we animate the element from their current values by setting other values. We set skew to initially be at 0 and skewY to be the velocity variable at 0.8.

Next, we’ve got to call this function in our App.js file.

....
import { skewGallery } from "./components/Animate"
function Gallery() {
  let skewImage = useRef(null);
  useEffect(() => {
    skewGallery(skewImage)
  }, []);
  return (
    <div ref={(el) => (skewImage = el)}>
      <Image/>
    </div>
  )
}

....

Here, we imported skewGalley from ./components/Animate, created a skewImage ref that targets the image element. Inside the useEffect hook, we called the skewGallery function and passed the skewImage ref as a param. Lastly, we passed the skewImage to the ref to attribute.

You’d agree with me it was such a pretty cool journey thus far. Here’s the preview on CodeSanbox 👇

The supporting repo for this article is available on Github.

Conclusion

We’ve explored the potency of GSAP in a React project, we only scratched the surface in this article, there’s no limit to what you can do with GSAP as it concerns animation. GSAP’s official website offers additional tips to help you gain a thorough understanding of methods and plugins. There’s a lot of demos that would blow your mind away with what people have done with GSAP. I’d love to hear your experience with GSAP in the comment section.

Resources

  1. GSAP Documentation — GSAP
  2. The Beginner’s Guide to the GreenSock Animation Platform — Freecodecamp
  3. An introduction to animations with Greensock Animation API (GSAP) — Zellwk

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.

Creating a Menu Image Animation on Hover

At Codrops, we love experimenting with playful hover effects. Back in 2018, we explored a set of fun hover animations for links. We called that Image Reveal Hover Effects and it shows how to make images appear with a fancy animation when hovering items of a menu. After seeing the fantastic portfolio of Marvin Schwaibold, I wanted to try this effect again on a larger menu and add that beautiful swing effect when moving the mouse. Using some filters, this can also be made more dramatic.

If you are interested in other similar effect, have a look at these:

So, today we’ll have a look at how to create this juicy image hover reveal animation:

Some Markup and Styling

We’ll use a nested structure for each menu item because we’ll have several text elements that will appear on page load and hover.

But we’ll not go into the text animation on load or the hover effect so what we are interested in here is how we’ll make the image appear for each item. The first thing I do when I want to make a certain effect is to write up the structure that I need using no JavaScript. So let’s take a look at that:

<a class="menu__item">
    <span class="menu__item-text">
        <span class="menu__item-textinner">Maria Costa</span>
    </span>
    <span class="menu__item-sub">Style Reset 66 Berlin</span>
    <!-- Markup for the image, inserted with JS -->
    <div class="hover-reveal">
        <div class="hover-reveal__inner">
            <div class="hover-reveal__img" style="background-image: url(img/1.jpg);"></div>
        </div>
    </div>
</a>

In order to construct this markup for the image, we need to save the source somewhere. We’ll use a data attribute on the menu__item, e.g. data-img="img/1.jpg". We’ll go into more detail later on.

Next, we’ll have some styling for it:

.hover-reveal {
    position: absolute;
    z-index: -1;
    width: 220px;
    height: 320px;
    top: 0;
    left: 0;
    pointer-events: none;
    opacity: 0;
}

.hover-reveal__inner {
    overflow: hidden;
}

.hover-reveal__inner,
.hover-reveal__img {
    width: 100%;
    height: 100%;
    position: relative;
}

.hover-reveal__img {
    background-size: cover;
    background-position: 50% 50%;
}

Any other styles that are specific to our effect (like the transforms) we’ll add dynamically.

Let’s take a look at the JavaScript.

The JavaScript

We’ll use GSAP and besides our hover animation, we’ll also use a custom cursor and smooth scrolling. For that we’ll use the smooth scroll library from the amazing folks of Locomotive, the Agency of the year. Since those are both optional and out of the scope of the menu effect we want to showcase, we’ll not be covering it here.

First things first: let’s preload all the images. For the purpose of this demo we are doing this on page load, but that’s optional.

Once that’s done, we can initialize the smooth scroll instance, the custom cursor and our Menu instance.

Here’s how the entry JavaScript file (index.js) looks like:

import Cursor from './cursor';
import {preloader} from './preloader';
import LocomotiveScroll from 'locomotive-scroll';
import Menu from './menu';

const menuEl = document.querySelector('.menu');

preloader('.menu__item').then(() => {
    const scroll = new LocomotiveScroll({el: menuEl, smooth: true});
    const cursor = new Cursor(document.querySelector('.cursor'));
    new Menu(menuEl);
});

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

import {gsap} from 'gsap';
import MenuItem from './menuItem';

export default class Menu {
    constructor(el) {
        this.DOM = {el: el};
        this.DOM.menuItems = this.DOM.el.querySelectorAll('.menu__item');
        this.menuItems = [];
        [...this.DOM.menuItems].forEach((item, pos) => this.menuItems.push(new MenuItem(item, pos, this.animatableProperties)));

        ...
    }
    ...
}

So far we have a reference to the main element (the menu <nav>
element) and the menu item elements. We’ll also create an array of our MenuItem instances. But let’s cover that bit in a moment.

What we’ll want to do now is to update the transform (both, X and Y translate) value as we move the mouse over the menu items. But we might as well want to update other properties. In our case we will additionally be updating the rotation and the CSS filter value (brightness). For that, let’s create an object that stores this configuration:

constructor(el) {
    ...

    this.animatableProperties = {
        tx: {previous: 0, current: 0, amt: 0.08},
        ty: {previous: 0, current: 0, amt: 0.08},
        rotation: {previous: 0, current: 0, amt: 0.08},
        brightness: {previous: 1, current: 1, amt: 0.08}
    };
}

With interpolation, we can achieve the 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.animatableProperties.tx.previous = MathUtils.lerp(this.animatableProperties.tx.previous, this.animatableProperties.tx.current, this.animatableProperties.tx.amt);

Finally, we can show the menu items, which are hidden by default. This was just a little extra, and totally optional, but it’s definitely a nice add-on to reveal each item with a delay on page load.

constructor(el) {
    ...

    this.showMenuItems();
}
showMenuItems() {
    gsap.to(this.menuItems.map(item => item.DOM.textInner), {
        duration: 1.2,
        ease: 'Expo.easeOut',
        startAt: {y: '100%'},
        y: 0,
        delay: pos => pos*0.06
    });
}

That’s it for the Menu class. What we’ll be looking into next is how to create the MenuItem class together with some helper variables and functions.

So, let’s start by importing the GSAP library (which we will use to show and hide the images), some helper functions and the images inside our images folder.

Next, we need to get access to the mouse position at any given time, since the image will follow along its movement. We can update this value on “mousemove” . We will also cache its position so we can calculate its speed and movement direction for both, the X and Y axis.

Hence, that’s what we’ll have so far in the menuItem.js file:

import {gsap} from 'gsap';
import { map, lerp, clamp, getMousePos } from './utils';
const images = Object.entries(require('../img/*.jpg'));

let mousepos = {x: 0, y: 0};
let mousePosCache = mousepos;
let direction = {x: mousePosCache.x-mousepos.x, y: mousePosCache.y-mousepos.y};

window.addEventListener('mousemove', ev => mousepos = getMousePos(ev));

export default class MenuItem {
    constructor(el, inMenuPosition, animatableProperties) {
        ...
    }
    ...
}

An item will be passed its position/index in the menu (inMenuPosition) and the animatableProperties object described before. The fact that the “animatable” property values are shared and updated among the different menu items will make the movement and rotation of the images continuous.

Now, in order to be possible to show and hide the menu item image in a fancy way, we need to create that specific markup we’ve shown in the beginning and append it to the item. Remember, our menu item is this by default:

<a class="menu__item" data-img="img/3.jpg">
    <span class="menu__item-text"><span class="menu__item-textinner">Franklin Roth</span></span>
    <span class="menu__item-sub">Amber Convention London</span>
</a>

Let’s append the following structure to the item:

<div class="hover-reveal">
    <div class="hover-reveal__inner" style="overflow: hidden;">
        <div class="hover-reveal__img" style="background-image: url(pathToImage);">
        </div>
    </div>
</div>

The hover-reveal element will be the one moving as we move the mouse.
The hover-reveal__inner element together with the hover-reveal__img (the one with the background image) will be the ones that we can animate together to create fancy animations like reveal/unreveal effects.

layout() {
    this.DOM.reveal = document.createElement('div');
    this.DOM.reveal.className = 'hover-reveal';
    this.DOM.revealInner = document.createElement('div');
    this.DOM.revealInner.className = 'hover-reveal__inner';
    this.DOM.revealImage = document.createElement('div');
    this.DOM.revealImage.className = 'hover-reveal__img';
    this.DOM.revealImage.style.backgroundImage = `url(${images[this.inMenuPosition][1]})`;
    this.DOM.revealInner.appendChild(this.DOM.revealImage);
    this.DOM.reveal.appendChild(this.DOM.revealInner);
    this.DOM.el.appendChild(this.DOM.reveal);
}

And the MenuItem constructor completed:

constructor(el, inMenuPosition, animatableProperties) {
    this.DOM = {el: el};
    this.inMenuPosition = inMenuPosition;
    this.animatableProperties = animatableProperties;
    this.DOM.textInner = this.DOM.el.querySelector('.menu__item-textinner');
    this.layout();
    this.initEvents();
}

The last step is to initialize some events. We need to show the image when hovering the item and hide it when leaving the item.

Also, when hovering it we need to update the animatableProperties object properties, and make the image move, rotate and change its brightness as the mouse moves:

initEvents() {
    this.mouseenterFn = (ev) => {
        this.showImage();
        this.firstRAFCycle = true;
        this.loopRender();
    };
    this.mouseleaveFn = () => {
        this.stopRendering();
        this.hideImage();
    };
    
    this.DOM.el.addEventListener('mouseenter', this.mouseenterFn);
    this.DOM.el.addEventListener('mouseleave', this.mouseleaveFn);
}

Let’s now code the showImage and hideImage functions.

We can create a GSAP timeline for this. Let’s start by setting the opacity to 1 for the reveal element (the top element of that structure we’ve just created). Also, in order to make the image appear on top of all other menu items, let’s set the item’s z-index to a high value.

Next, we can animate the appearance of the image. Let’s do it like this: the image gets revealed to the right or left, depending on the mouse x-axis movement direction (which we have in direction.x). For this to happen, the image element (revealImage) needs to animate its translationX value to the opposite side of its parent element (revealInner element).
That’s basically it:

showImage() {
    gsap.killTweensOf(this.DOM.revealInner);
    gsap.killTweensOf(this.DOM.revealImage);
    
    this.tl = gsap.timeline({
        onStart: () => {
            this.DOM.reveal.style.opacity = this.DOM.revealInner.style.opacity = 1;
            gsap.set(this.DOM.el, {zIndex: images.length});
        }
    })
    // animate the image wrap
    .to(this.DOM.revealInner, 0.2, {
        ease: 'Sine.easeOut',
        startAt: {x: direction.x < 0 ? '-100%' : '100%'},
        x: '0%'
    })
    // animate the image element
    .to(this.DOM.revealImage, 0.2, {
        ease: 'Sine.easeOut',
        startAt: {x: direction.x < 0 ? '100%': '-100%'},
        x: '0%'
    }, 0);
}

To hide the image we just need to reverse this logic:

hideImage() {
    gsap.killTweensOf(this.DOM.revealInner);
    gsap.killTweensOf(this.DOM.revealImage);

    this.tl = gsap.timeline({
        onStart: () => {
            gsap.set(this.DOM.el, {zIndex: 1});
        },
        onComplete: () => {
            gsap.set(this.DOM.reveal, {opacity: 0});
        }
    })
    .to(this.DOM.revealInner, 0.2, {
        ease: 'Sine.easeOut',
        x: direction.x < 0 ? '100%' : '-100%'
    })
    .to(this.DOM.revealImage, 0.2, {
        ease: 'Sine.easeOut',
        x: direction.x < 0 ? '-100%' : '100%'
    }, 0);
}

Now we just need to update the animatableProperties object properties so the image can move around, rotate and change its brightness smoothly. We do this inside a requestAnimationFrame loop. In every cycle we interpolate the previous and current values so things happen with an easing.

We want to rotate the image and change its brightness depending on the x-axis speed (or distance traveled from the previous cycle) of the mouse. Therefore we need to calculate that distance for every cycle which we can get by subtracting the mouse position from the cached mouse position.

We also want to know in which direction we move the mouse since the rotation will be dependent on it. When moving to the left the image rotates negatively, and when moving to the right, positively.

Next, we want to update the animatableProperties values. For the translationX and translationY, we want the center of the image to be positioned where the mouse is. Note that the original position of the image element is on the left side of the menu item.

The rotation can go from -60 to 60 degrees depending on the speed/distance of the mouse and its direction. Finally the brightness can go from 1 to 4, also depending on the speed/distance of the mouse.

In the end, we take these values together with the previous cycle values and use interpolation to set up a final value that will then give us that smooth feeling when animating the element.

This is how the render function looks like:

render() {
    this.requestId = undefined;
    
    if ( this.firstRAFCycle ) {
        this.calcBounds();
    }

    const mouseDistanceX = clamp(Math.abs(mousePosCache.x - mousepos.x), 0, 100);
    direction = {x: mousePosCache.x-mousepos.x, y: mousePosCache.y-mousepos.y};
    mousePosCache = {x: mousepos.x, y: mousepos.y};

    this.animatableProperties.tx.current = Math.abs(mousepos.x - this.bounds.el.left) - this.bounds.reveal.width/2;
    this.animatableProperties.ty.current = Math.abs(mousepos.y - this.bounds.el.top) - this.bounds.reveal.height/2;
    this.animatableProperties.rotation.current = this.firstRAFCycle ? 0 : map(mouseDistanceX,0,100,0,direction.x < 0 ? 60 : -60);
    this.animatableProperties.brightness.current = this.firstRAFCycle ? 1 : map(mouseDistanceX,0,100,1,4);

    this.animatableProperties.tx.previous = this.firstRAFCycle ? this.animatableProperties.tx.current : lerp(this.animatableProperties.tx.previous, this.animatableProperties.tx.current, this.animatableProperties.tx.amt);
    this.animatableProperties.ty.previous = this.firstRAFCycle ? this.animatableProperties.ty.current : lerp(this.animatableProperties.ty.previous, this.animatableProperties.ty.current, this.animatableProperties.ty.amt);
    this.animatableProperties.rotation.previous = this.firstRAFCycle ? this.animatableProperties.rotation.current : lerp(this.animatableProperties.rotation.previous, this.animatableProperties.rotation.current, this.animatableProperties.rotation.amt);
    this.animatableProperties.brightness.previous = this.firstRAFCycle ? this.animatableProperties.brightness.current : lerp(this.animatableProperties.brightness.previous, this.animatableProperties.brightness.current, this.animatableProperties.brightness.amt);
    
    gsap.set(this.DOM.reveal, {
        x: this.animatableProperties.tx.previous,
        y: this.animatableProperties.ty.previous,
        rotation: this.animatableProperties.rotation.previous,
        filter: `brightness(${this.animatableProperties.brightness.previous})`
    });

    this.firstRAFCycle = false;
    this.loopRender();
}

I hope this has been not 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 images used in the demo are by Andrey Yakovlev and Lili Aleeva. All images used are licensed under CC BY-NC-ND 4.0

The post Creating a Menu Image Animation on Hover appeared first on Codrops.

Awesome Demos Roundup #16

With the fantastic ScrollTrigger plugin that the great folks of GSAP released some time ago, we’ve been blessed with many awesome scroll demos that are nothing short of creativity and that show what cool things are possible by simply scrolling a page. Take a look at these and other playful and inspiring demos; we hope you enjoy this special selection!

Interactive Password Validator

by Eren Guldas

Pure CSS solar system animation in 2D and 3D

by Yan Zhu

T-Shirt Cannon Button

by Jhey Tompkins

Responsive CSS Grid – Books

by Andy Barefoot

Password Reveal

by Mikael Ainalem

deconstructed

by Gerard Ferrandez

Who can get my blood?

by Romina

Balancing Moods

by Chris Gannon

The Oscars 2020

by Robert Borghesi and Valentino Borghesi

3D button

by Robin Delaporte

switch.css

by Sarah Fossheim

Animal Crossing: Isabelle’s Day Off ??(Pure CSS)

by Tee Diang

Painter

by Ricardo Oliva Alonso

Ghost at the Disco – by Anna the Scavenger

by Anna the Scavenger

PureCSS Gaze

By Diana Smith

Single div CSS “Six”

by Lynn Fisher

Water

by Kasper De Bruyne

boids

by Marius Ballot

sphere

by Giulian Drimba

ScrollTrigger Demo

by Mariusz Dabrowski

(v2) Paper plane button

by Aaron Iker

CSS Grid: Newspaper Layout

by Olivia Ng

Bouncing tab bar

by Aaron Iker

CSS Night Train Ride Animation

by Aysenur Turk

Bedroom Construction with threejs + GSAP

by Yiting Liu

Airplanes.

by Steve Gardner

Text Refraction

by Juan Fuentes

GSAP ScrollTrigger on Variable Fonts Perspective

by Pete Barr

Voyage Slider

by Sikriti Dakua

Checkboxes waves

by Louis Hoebregts

CasioPT-1.css

by Sarah Fossheim

2020.06.15

by Ikeda Ryou

slices

by Atrahasis

Little book of GSAP w/ ScrollTrigger

by Jhey Tompkins

ScrollTrigger – Highlight Text

by Ryan Mulligan

Book Store UI

by Aysenur Turk

The post Awesome Demos Roundup #16 appeared first on Codrops.

Easing Animations in Canvas

The <canvas> element in HTML and Canvas API in JavaScript combine to form one of the main raster graphics and animation possibilities on the web. A common canvas use-case is programmatically generating images for websites, particularly games. That’s exactly what I’ve done in a website I built for playing Solitaire. The cards, including all their movement, is all done in canvas.

In this article, let’s look specifically at animation in canvas and techniques to make them look smoother. We’ll look specifically at easing transitions — like “ease-in” and “ease-out” — that do not come for free in canvas like they do in CSS.

Let’s start with a static canvas. I’ve drawn to the canvas a single playing card that I grabbed out of the DOM:

Let’s start with a basic animation: moving that playing card on the canvas. Even for fairly basic things like requires working from scratch in canvas, so we’ll have to start building out functions we can use.

First, we’ll create functions to help calculate X and Y coordinates:

function getX(params) {
  let distance = params.xTo - params.xFrom;
  let steps = params.frames;
  let progress = params.frame;
  return distance / steps * progress;
}


function getY(params) {
  let distance = params.yTo - params.yFrom;
  let steps = params.frames;
  let progress = params.frame;
  return distance / steps * progress;
}

This will help us update the position values as the image gets animated. Then we’ll keep re-rendering the canvas until the animation is complete. We do this by adding the following code to our addImage() method.

if (params.frame < params.frames) {
  params.frame = params.frame + 1;
  window.requestAnimationFrame(drawCanvas);
  window.requestAnimationFrame(addImage.bind(null, params))
}

Now we have animation! We’re steadily incrementing by 1 unit each time, which we call a linear animation.

You can see how the shape moves from point A to point B in a linear fashion, maintaining the same consistent speed between points. It’s functional, but lacks realism. The start and stop is jarring.

What we want is for the object to accelerate (ease-in) and decelerate (ease-out), so it mimics what a real-world object would do when things like friction and gravity come into play.

A JavaScript easing function

We’ll achieve this with a “cubic” ease-in and ease-out transition. We’ve modified one of the equations found in Robert Penner’s Flash easing functions, to be suitable for what we want to do here.

function getEase(currentProgress, start, distance, steps) {
  currentProgress /= steps/2;
  if (currentProgress < 1) {
    return (distance/2)*(Math.pow(currentProgress, 3)) + start;
  }
  currentProgress -= 2;
  return distance/2*(Math.pow(currentProgress, 3)+ 2) + start;
}

Inserting this into our code, which is a cubic ease, we get a much smoother result. Notice how the card speeds towards the center of the space, then slows down as it reaches the end.

Advanced easing with JavaScript

We can get a slower acceleration with either a quadratic or sinusoidal ease.

function getQuadraticEase(currentProgress, start, distance, steps) {
  currentProgress /= steps/2;
  if (currentProgress <= 1) {
    return (distance/2)*currentProgress*currentProgress + start;
  }
  currentProgress--;
  return -1*(distance/2) * (currentProgress*(currentProgress-2) - 1) + start;
}
function sineEaseInOut(currentProgress, start, distance, steps) {
  return -distance/2 * (Math.cos(Math.PI*currentProgress/steps) - 1) + start;
};

For a faster acceleration, go with a quintic or exponential ease:

function getQuinticEase(currentProgress, start, distance, steps) {
  currentProgress /= steps/2;
  if (currentProgress < 1) {
    return (distance/2)*(Math.pow(currentProgress, 5)) + start;
  }
  currentProgress -= 2;
  return distance/2*(Math.pow(currentProgress, 5) + 2) + start;
}

function expEaseInOut(currentProgress, start, distance, steps) {
  currentProgress /= steps/2;
  if (currentProgress < 1) return distance/2 * Math.pow( 2, 10 * (currentProgress - 1) ) + start;
 currentProgress--;
  return distance/2 * ( -Math.pow( 2, -10 * currentProgress) + 2 ) + start;
};

More sophisticated animations with GSAP

Rolling your own easing functions can be fun, but what if you want more power and flexibility? You could continue writing custom code, or you could consider a more powerful library. Let’s turn to the GreenSock Animation Platform (GSAP) for that.

Animation becomes a lot easier to implement with GSAP. Take this example, where the card bounces at the end. Note that the GSAP library is included in the demo.

The key function is moveCard:

function moveCard() {
  gsap.to(position, {
    duration: 2,
    ease: "bounce.out",
    x: position.xMax, 
    y: position.yMax, 
    onUpdate: function() {
      draw();
    },
    onComplete: function() {
      position.x = position.origX;
      position.y = position.origY;
    }
  });
}

The gsap.to method is where all the magic happens. During the two-second duration, the position object is updated and, with every update, onUpdate is called triggering the canvas to be redrawn.

And we’re not just talking about bounces. There are tons of different easing options to choose from.

Putting it all together

Still unsure about which animation style and method you should be using in canvas when it comes to easing? Here’s a Pen showing different easing animations, including what’s offered in GSAP.

Check out my Solitaire card game  to see a live demo of the non-GSAP animations. In this case, I’ve added animations so that the cards in the game ease-out and ease-in when they move between piles.

In addition to creating motions, easing functions can be applied to any other attribute that has a from and to state, like changes in opacity, rotations, and scaling. I hope you’ll find many ways to use easing functions to make your application or game look smoother.

The post Easing Animations in Canvas appeared first on CSS-Tricks.

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.

How to Create a Motion Hover Effect for a Background Image Grid

If you follow our UI Interactions & Animations Roundups, you might have spotted this beautiful grid designed by the folks of tubik:

Previously, Zhenya Rynzhuk also designed this wonderful layout with a similar interaction:

It’s not too complicated to implement this. I wanted to try it and in the following I’ll walk you through the relevant markup and code.

The markup and style for the grid

The markup is simply a grid of items that have background images. I like to use this structure because it allows me to control the sizes of the images by setting their position in the grid.

<div class="grid">
    <div class="grid__item pos-1">
        <div class="grid__item-img" style="background-image:url(img/1.jpg);"></div>
    </div>
    <div class="grid__item pos-2">
        <div class="grid__item-img" style="background-image:url(img/2.jpg);"></div>
    </div>
    <div class="grid__item pos-3">
        <div class="grid__item-img" style="background-image:url(img/3.jpg);"></div>
    </div>
    ...
</div>

The grid is stretched to be a bit bigger than its parent because we want to move the items and create the illusion of an infinite plane of images.

.grid {
	pointer-events: none;
	position: absolute;
	width: 110%;
	height: 110%;
	top: -5%;
	left: -5%;
	display: grid;
	grid-template-columns: repeat(50,2%);
	grid-template-rows: repeat(50,2%);
}

.grid__item {
	position: relative;
}

.grid__item-img {
	position: relative;
	width: 100%;
	height: 100%;
	background-size: cover;
	background-position: 50% 50%;
}

The grid is divided into 50 cells for the rows and columns. With this layout density, the position of each image element can be set precisely.

/* Shorthand grid-area: grid-row-start / grid-column-start / grid-row-end / grid-column-end */

.pos-1 {
	grid-area: 10 / 1 / 26 / 7;
}

.pos-2 {
	grid-area: 1 / 18 / 9 / 27;
}

.pos-3 {
	grid-area: 1 / 36 / 14 / 42;
}

...

Note that I use the double division structure for the possibility of moving the inner element with the background image to create the motion effect seen in demo 3. For that case, I define some extra styles:

/* If we want to move the inner image */

.grid--img .grid__item {
	overflow: hidden;
	display: flex;
	align-items: center;
	justify-content: center;
	will-change: transform;
}

.grid--img .grid__item-img {
	flex: none;
	width: calc(100% + 100px);
	height: calc(100% + 100px);
	will-change: transform;
}

The JavaScript

Now, let’s have a look at the JavaScript part. I’m using GSAP by GreenSock. We start by creating a Grid class to represent the grid of pictures:

export default class Grid {
    constructor(el) {
        this.DOM = {el: el};
        this.gridItems = [];
        this.items = [...this.DOM.el.querySelectorAll('.grid__item')];
        this.items.forEach(item => this.gridItems.push(new GridItem(item)));
       
        this.showItems();
    }
    ...
}

const grid = new Grid(document.querySelector('.grid'));

There should be an initial animation where the grid items scale up and fade in. We can add a method to the class for that. We also want the items to start at different times and for that we use the GSAP stagger option. The items will start animating from the center of the grid:

showItems() {
    gsap.timeline()
    .set(this.items, {scale: 0.7, opacity: 0}, 0)
    .to(this.items, {
        duration: 2,
        ease: 'Expo.easeOut',
        scale: 1,
        stagger: {amount: 0.6, grid: 'auto', from: 'center'}
    }, 0)
    .to(this.items, {
        duration: 3,
        ease: 'Power1.easeOut',
        opacity: 0.4,
        stagger: {amount: 0.6, grid: 'auto', from: 'center'}
    }, 0);
}

Now, let’s make the items move as we move the mouse around. Each grid item will be represented by a GridItem class:

class GridItem {
    constructor(el) {
        this.DOM = {el: el};
        this.move();
    }
    ...
}

The position of each item in both axes should be mapped with the mouse position. So, the mouse can move from position 0 to the width or height of the window. As for the item, it’ll move in a range of [start, end] that we need to specify. We’ll be assigning random values for the start/end value so that each item moves differently from each other.

Let’s add the move method to the GridItem class:

move() {
    // amount to move in each axis
    let translationVals = {tx: 0, ty: 0};
    // get random start and end movement boundaries
    const xstart = getRandomNumber(15,60);
    const ystart = getRandomNumber(15,60);
   
    // infinite loop
    const render = () => {
        // Calculate the amount to move.
        // Using linear interpolation to smooth things out.
        // Translation values will be in the range of [-start, start] for a cursor movement from 0 to the window's width/height
        translationVals.tx = lerp(translationVals.tx, map(mousepos.x, 0, winsize.width, -xstart, xstart), 0.07);
        translationVals.ty = lerp(translationVals.ty, map(mousepos.y, 0, winsize.height, -ystart, ystart), 0.07);
       
        gsap.set(this.DOM.el, {x: translationVals.tx, y: translationVals.ty});  
        requestAnimationFrame(render);
    }
    requestAnimationFrame(render);
}

And that’s it!

I hope you find this helpful and please let me know your feedback via @codrops. Thank you for reading!

The post How to Create a Motion Hover Effect for a Background Image Grid appeared first on Codrops.

Tips for Writing Animation Code Efficiently

I’ve been coding web animations and helping others do the same for years now. However, I have yet to see a concise list of tips focused on how to efficiently build animations, so here you go!

I will be using the GreenSock Animation Platform (GSAP). It provides a simple, readable API and solves cross-browser inconsistencies so that you can focus on animating. The code and concepts should be understandable even if you’ve never used GSAP. If you’d like to familiarize yourself with the basics of GSAP first so that you can get the most out of this article, the best place to begin is GSAP’s getting started page (includes a video). 

Tip #1: Use an animation library

Some developers think that using an animation library is wasteful because they can just use native browser technologies like CSS transitions, CSS animations or the Web Animations API (WAAPI) to accomplish the same thing without loading a library. In some cases, that’s true. However, here are a few other factors to consider:

  • Browser bugs, inconsistencies, and compatibility: An animation library, like GSAP, solves these for you and is universally compatible. You can even use motion paths in IE9! There are many problematic areas when it comes to cross-browser issues, including handling transform-origin on SVG elements, path stroke measurements, 3D origins in Safari, and many more that we don’t have the space to list.
  • Animation workflow: Building even moderately complex animations is much faster and more fun with a tool like GSAP. You can modularize animations, nest them as deeply as you want, and have their timing adjusted automatically. This makes it so much easier to experiment. Trust me: once you try building an animation sequence in CSS and then in GSAP, you’ll see what I mean. Night and day! Future edits are faster too.
  • Animate beyond the DOM: Canvas, WebGL, generic objects, and complex strings can’t be animated with native technologies. Using one consistent tool for all your animations is much cleaner. 
  • Runtime control: Using a good animation library can enable you to pause, resume, reverse, seek through, or even gradually change the speed of an entire animation sequence. You can control each transform component independently (rotation, scale, x, y, skew, etc.). You can also retrieve those values at any time as well. JavaScript animations give you ultimate flexibility.
  • Easing options (bounce, elastic, etc.): CSS only gives you two control points for eases. GSAP’s CustomEase lets you literally create any ease you can imagine. 
  • Lag smoothing: GSAP can prioritize absolute timing or adjust things on the fly to avoid jumps if the CPU gets bogged down.
  • Advanced capabilities: Using GSAP, it’s easy to morph SVGs, add physics/inertia, edit motion paths directly in the browser, use position-aware staggers, and more.

Most of the top animators in the industry use a tool like GSAP because they’ve learned these same things over the years. Once you get beyond very basic animations, a JavaScript library will make your life much, much easier and open up entirely new possibilities. 

Tip #2: Use timelines

A good animation library will provide some way of creating individual animations (called tweens) and a way to sequence animations in a timeline. Think of a timeline like a container for your tweens where you position them in relation to one another. 

const tl = gsap.timeline(); 
tl.to(".box", { duration: 1, x: 100 })
  .to(".box", { duration: 1, backgroundColor: "#f38630" }, "+=0.5") 
  .to(".box", { duration: 1, x: 0, rotation: -360 }, "+=0.5")

By default in GSAP, tweens added to a timeline will wait for the previous tweens to complete before running. The +=0.5 adds an additional offset or delay of a half-second as well, so the second tween will start 0.5 seconds after the first tween finishes no matter how long the first tween’s duration is.

To increase the amount of time between the tween to 1 second, all you need to do is change the +=0.5 to +=1! Super easy. With this approach, you can iterate on your animations quickly without worrying about doing the math to combine previous durations.

Tip #3: Use relative values

By “relative values” I mean three things:

  1. Animate values relative to their current value. GSAP recognizes += and -= prefixes for this. So x: "+=200" will add 200 units (usually pixels) to the current x. And x: "-=200" will subtract 200 from the current value. This is also useful in GSAP’s position parameter when positioning tweens relative to one another.
  2. Use relative units (like vw, vh and, in some cases, %) when values need to be responsive to viewport size changes.
  3. Use methods like .to() and .from() (instead of .fromTo()) whenever possible so that the start or end values are dynamically populated from their current values. That way, you don’t need to declare start and end values in every tween. Yay, less typing! For example, if you had a bunch of differently-colored elements, you could animate them all to black like gsap.to(".class", {backgroundColor: "black" }).

Tip #4: Use keyframes

If you find yourself animating the same target over and over in a row, that’s a perfect time to use keyframes! You do so like this:

gsap.to(".box", { keyframes: [
  { duration: 1, x: 100 },
  { duration: 1, backgroundColor: "#f38630", delay: 0.5 }, 
  { duration: 1, x: 0, rotation: -360, delay: 0.5 }
]});

No timeline necessary! To space out the tweens we just use the delay property in each keyframe. (It can be negative to create overlaps.)

Tip #5: Use smart defaults

GSAP has default values for properties like ease ("power1.out") and duration (0.5 seconds). So, the following is a valid tween that will animate for half a second.

gsap.to(".box", { color: "black" })

To change GSAP’s global defaults, use gsap.defaults():

// Use a linear ease and a duration of 1 instead
gsap.defaults({ ease: "none", duration: 1 });

This can be handy, but it’s more common to set defaults for a particular timeline so that it affects only its children. For example, we can avoid typing duration: 1 for each of the sub-tweens by setting a default on the parent timeline:

const tl = gsap.timeline({ defaults: { duration: 1 } }); 
tl.to(".box", { x: 100 })
  .to(".box", { backgroundColor: "#f38630" }, "+=0.5") 
  .to(".box", { x: 0, rotation: -360 }, "+=0.5")

Tip #6: Animate multiple elements at once

We mentioned this briefly in the third tip, but it deserves its own tip.

If you have multiple elements that share the same class of .box, the code above will animate all of the elements at the same time!

You can also select multiple elements with different selectors by using a more complex selector string:

gsap.to(".box, .circle", { ... });

Or you can pass an array of variable references as long as the elements are of the same type (selector string, variable reference, generic object, etc.):

var box = document.querySelector(".box");
var circle = document.querySelector(".circle");

// some time later…
gsap.to([box, circle], { ... });

Tip #7: Use function-based values, staggers, and/or loops

Function-based values

Use a function instead of a number/string for almost any property, and GSAP will call that function once for each target when it first renders the tween. Plus, it’ll use whatever gets returned by the function as the property value! This can be really handy for creating a bunch of different animations using a single tween and for adding variance.

GSAP will pass the following parameters into the function:

  1. The index
  2. The specific element being affected
  3. An array of all of the elements affected by the tween

For example, you could set the movement direction based on the index:

Or you could choose items from an array:

Staggers

Make your animations look more dynamic and interesting by offsetting the start times with a stagger. For simple staggered offsets in a single tween, just use stagger: 0.2 to add 0.2 seconds between the start time of each animation.

You can also pass in an object to get more complex stagger effects, including ones that emanate outward from the center of a grid or randomize the timings:

For more information about GSAP’s staggers, check out the stagger documentation.

Loops

It can be helpful to loop through a list of elements to create or apply animations, particularly when they are based on some event, like a user’s interaction (which I’ll discuss later on). 

To loop through a list of items, it’s easiest to use .forEach(). But since this isn’t supported on elements selected with .querySelectorAll() in IE, you can use GSAP’s utils.toArray() function instead.

In the example below, we are looping through each container to add animations to its children that are scoped to that container.

Tip #8: Modularize your animations

Modularization is one of the key principles of programming. It allows you to build small, easy-to-understand chunks that you can combine into larger creations while still keeping things clean, reusable, and easy to modify. It also lets you to use parameters and function scope, increasing the re-usability of your code.

Functions

Use functions to return tweens or timelines and then insert those into a master timeline:

function doAnimation() {
  // do something, like calculations, maybe using arguments passed into the function
  
  // return a tween, maybe using the calculations above
  return gsap.to(".myElem", { duration: 1, color: "red"});
}

tl.add( doAnimation() );

Nesting timelines can truly revolutionize the way you animate. It lets you sequence really complex animations with ease while keeping your code modular and readable.

function doAnimation() {
  const tl = gsap.timeline();
  tl.to(...);
  tl.to(...);
  // ...as many animations as you’d like!

  // When you’re all done, return the timeline
  return tl;
}

const master = gsap.timeline();
master.add( doAnimation() );
master.add( doAnotherAnimation() );
// Add even more timelines!

Here’s a real-world use case modified from Carl Schooff’s “Writing Smarter Animation Code” post.

Here’s a more complex demo showing the same technique using a Star Wars theme by Craig Roblewsky:

Wrapping your animation-building routines inside functions also makes recreating animations (say, on resize) a breeze!

var tl; // keep an accessible reference to our timeline

function buildAnimation() {
  var time = tl ? tl.time() : 0; // save the old time if it exists

  // kill off the old timeline if it exists
  if (tl) {
    tl.kill();
  }

  // create a new timeline
  tl = gsap.timeline();
  tl.to(...)
    .to(...); // do your animation
  tl.time(time); // set the playhead to match where it was
}

buildAnimation(); //kick things off

window.addEventListener("resize", buildAnimation); // handle resize

Effects

With effects, you can turn a custom animation into a named effect that can be called anytime with new targets and configurations. This is especially helpful when you have standards for your animations or if you are going to be calling the same animation from different contexts.

Here’s a super-simple “fade” effect to show the concept:

// register the effect with GSAP:
gsap.registerEffect({
    name: "fade",
    defaults: {duration: 2}, //defaults get applied to the "config" object passed to the effect below
    effect: (targets, config) => {
        return gsap.to(targets, {duration: config.duration, opacity:0});
    }
});

// now we can use it like this:
gsap.effects.fade(".box");
// Or override the defaults:
gsap.effects.fade(".box", {duration: 1});

Tip #9: Use control methods

GSAP provides many methods to control the state of a tween or timeline. They include .play(), .pause(), .reverse(), .progress(), .seek(), .restart(), .timeScale(), and several others. 

Using control methods can make transitions between animations more fluid (such as being able to reverse part way through) and more performant (by reusing the same tween/timeline instead of creating new instances each time). And by giving you finer control over the state of the animation, it can help with debugging as well.

Here’s a simple example:

One amazing use case is tweening the timeScale of a timeline!

Use case: interaction events that trigger animations

Inside of event listeners for user interaction events, we can use control methods to have fine control over our animation’s play state.

In the example below, we are creating a timeline for each element (so that it doesn’t fire the same animation on all instances), attaching a reference for that timeline to the element itself, and then playing the relevant timeline when the element is hovered, reversing it when the mouse leaves.

Use case: Animating between multiple states of a timeline

You may want a set of animations to affect the same properties of the same elements, but only in certain sequences (e.g. active/inactive states, each with mouseover/mouseout states). It may get tricky to manage. We can simplify it by using states of a timeline and control events. 

Use case: Animating based on the scroll position

We can easily fire animations based on the scroll position by using control methods. For example, this demo plays a full animation once a scroll position has been reached:

You can also attach the progress of an animation to the scroll position for more fancy scroll effects!

But if you’re going to do this, it’s best to throttle the scroll listener for performance reasons:

Hot tip: GreenSock is working on a plugin to make scroll-based animations even easier! You’re in for quite a treat. Keep your eyes peeled for news.

Bonus tip: Use GSAP’s plugins, utility methods, and helper functions

GSAP plugins add extra capabilities to GSAP’s core. Some plugins make it easier to work with rendering libraries, like PixiJS or EaselJS, while other plugins add superpowers like morphing SVG, drag and drop functionality, etc. This keeps the GSAP core relatively small and lets you add features when you need them.

Plugins

MorphSVG morphs between any two SVG shapes, no matter the number of points, and gives you fine control over how the shapes are morphed.

DrawSVG progressively reveals (or hides) the stroke of an SVG element, making it look like it’s being drawn. It works around various browser bugs that affect typical stroke-dashoffset animations.

MotionPath animates anything (SVG, DOM, canvas, generic objects, whatever) along a motion path in any browser. You can even edit the path in-browser using MotionPathHelper!

GSDevTools gives you a visual UI for interacting with and debugging GSAP animations, complete with advanced playback controls, keyboard shortcuts, global synchronization and more.

Draggable provides a surprisingly simple way to make virtually any DOM element draggable, spinnable, tossable, or even flick-scrollable using mouse or touch events. Draggable integrates beautifully (and optionally) with InertiaPlugin so the user can flick and have the motion decelerate smoothly based on momentum.

CustomEase (along with CustomBounce and CustomWiggle) add to GSAP’s already extensive easing capabilities by enabling you to register any ease that you’d like.

SplitText is an easy to use JavaScript utility that allows you to split HTML text into characters, words and lines. It’s easy to use, extremely flexible, works all the way back to IE9, and handles special characters for you.

ScrambleText scrambles the text in a DOM element with randomized characters, refreshing new randomized characters at regular intervals, while gradually revealing your new text (or the original) over the course of the tween. Visually, it looks like a computer decoding a string of text.

Physics2D lets you tween the position of elements based on velocity and acceleration as opposed to going to specific values. PhysicsProps is similar but works with any property, not just 2D coordinates.

Utility methods

GSAP has built-in utility methods that can make some common tasks easier. Most are focused on manipulating values in a particular way, which can be especially helpful when generating or modifying animation values. The ones that I use most often are .wrap(), .random, .interpolate(), .distribute(), .pipe(), and .unitize(), but there are many others you might find helpful.

Helper functions

In a similar light, but not built into GSAP’s core, are some helper functions GreenSock has created over the years to deal with specific use cases. These functions make it easy to FLIP your animations, return a random number based on an ease curve, blend two ease curves, and much more. I highly recommend checking them out!

Conclusion

You’ve made it to the end! Hopefully, you’ve learned a thing or two along the way and this article will continue to be a resource for you in the years to come.

As always, if you have any questions about GSAP, feel free to drop by the GreenSock forums. They’re incredibly helpful and welcoming! As an employee of GreenSock, that’s where I hang out often; I love helping people with animation-related challenges!

The post Tips for Writing Animation Code Efficiently appeared first on CSS-Tricks.

Rapid Image Layers Animation

A while back, I came across this tweet by Twitter Marketing, which shows a video with a really nice intro animation. So I made a quick demo, trying to implement that effect. The idea is to animate some fullscreen images rapidly, like a sequence of covering layers. It’s a nice idea for an intro splash or even a page transition.

The way this can be achieved is by using the reveal trick described in the tutorial How to Create and Animate Rotated Overlays.

Basically, we have a parent wrap that is set to overflow hidden that we translate up (or down), while we translate its child in the opposite direction. It looks the same as if animating a clip-path that cuts off the image. (We could also do that, but clip-path is not a very performant thing to animate like transforms.)

When doing that animation on a set of layers, we get that really nice effect seen in the video.

For this demo, I created an initial (dummy) menu layout where you can click on the middle item to trigger the animation.

By setting a fitting animation duration and delay (I’m using GSAP here), the effect can be adjusted to look as smooth or as fast as needed. Have a look at the result:

The custom cursor animation can be found here: Animated Custom Cursor Effects

I really hope you find this little demo useful!

Credits

Rapid Image Layers Animation was written by Mary Lou and published on Codrops.

How to Animate on the Web With Greensock

There are truly thousands of ways to animate on the web. We’ve covered a comparison of different animation technologies here before. Today, we’re going to dive into a step-by-step guide of one of my favorite ways to get it done: using GreenSock.

(They don’t pay me or anything, I just really enjoy using them.)

Why do I prefer Greensock to other methods? Technically speaking, it's often the best tool for the job. Its usage is extremely straightforward, even for complex movement. Here are a few more reasons why I prefer using it:

  • You can use them on DOM elements as well as within WebGL/Canvas/Three.js contexts.
  • The easing is very sophisticated. CSS animations are limited to two bezier handles for ease, meaning if you want, say, a bounce effect, you would have to make keyframes up and down and up and down for each pass. GreenSock allows multiple bezier handles to create advanced effects. A bounce is a single line of code. You can see what I mean by checking out their ease visualizer.
  • You can sequence movement on a timeline. CSS animations can get a little spaghetti when you have to coordinate several things at once. GreenSock remains very legible and allows you to control the timeline itself. You can even animate your animations! 🤯
  • It will perform some calculations under the hood to prevent strange cross-browser behavior as well as things that the spec dictates should be true aren't — like the way that stacking transforms work.
  • It offers a lot of advanced functionality, in the form of plugins, that you can use if you’d like to take your work a step further — things like Morphing SVG shapes, drawing SVG paths, dragging and dropping, inertia, and more.

People sometimes ask me why I'd use this particular library over all of the other choices. It’s further along than most others — they’ve been around since the Flash was still a thing. This demo reel is pretty inspiring, and also gets the point across that serious web animators do tend to reach for this tool:

What follows is a breakdown of how to create movement on the web, distilled down to the smallest units I could make them. Let’s get started!

Animating a DOM element

Consider a ball that we make with a

that's styled with a border-radius value of 50%. Here’s how we would scale and move it with Greensock:

 

gsap.to('.ball', {
  duration: 1,
  x: 200,
  scale: 2
})

See the Pen
Greensock Tutorial 1
by Sarah Drasner (@sdras)
on CodePen.

In this case, we’re telling GreenSock (gsap) to take the element with the class of .ball move it .to() a few different properties. We’ve shortened the CSS properties of transform: translateX(200px) to a streamlined x: 200 (note there's no need for the units, but you can pass them as a string). We’re also not writing transform: scale(2). Here's a reference of the transforms you might want to use with animations, and their corresponding CSS syntax:

x: 100 // transform: translateX(100px)
y: 100 // transform: translateY(100px)
z: 100 // transform: translateZ(100px)
// you do not need the null transform hack or hardware acceleration, it comes baked in with
// force3d:true. If you want to unset this, force3d:false
scale: 2 // transform: scale(2)
scaleX: 2 // transform: scaleX(2)
scaleY: 2 // transform: scaleY(2)
scaleZ: 2 // transform: scaleZ(2)
skew: 15 // transform: skew(15deg)
skewX: 15 // transform: skewX(15deg)
skewY: 15 // transform: skewY(15deg)
rotation: 180 // transform: rotate(180deg)
rotationX: 180 // transform: rotateX(180deg)
rotationY: 180 // transform: rotateY(180deg)
rotationZ: 180 // transform: rotateZ(180deg)
perspective: 1000 // transform: perspective(1000px)
transformOrigin: '50% 50%' // transform-origin: 50% 50%

Duration is what you might think it is: it’s a one-second stretch of time.

So, OK, how would we animate, say, an SVG? Let’s consider the same code above as an SVG:

  
gsap.to('.ball', {
  duration: 1,
  x: 200,
  scale: 2
})

See the Pen
Greensock Tutorial 2
by Sarah Drasner (@sdras)
on CodePen.

From an animation perspective, it’s actually exactly the same. It’s grabbing the element with the class .ball on it, and translating those properties. Since SVGs are literally DOM elements, we can slap a class on any one of them and animate them just the same way!

Great! We’re cooking with gas.

Eases

I mentioned before that eases are one of my favorite features, let’s take a look at how we’d use them.

Let’s take the original ball. Maybe we want to try one of those more unique bounce eases. It would go like this:

gsap.to('.ball', {
  duration: 1.5,
  x: 200,
  scale: 2,
  ease: 'bounce'
})

See the Pen
Greensock Tutorial 3
by Sarah Drasner (@sdras)
on CodePen.

That’s it! This version of GreenSock assumes that you want to use ease-out timing (which is better for entrances), so it applies that as a default. All you have to do is specify "bounce" as a string and you’re off to the races.

You might have noticed we also lengthened the duration a bit as well. That’s because the ball has to do more "work" in between the initial and end states. A one-second duration, though lovely for linear or sine easing, is a little too quick for a bounce or an elastic ease.

Delays and Timelines

I mentioned that the default ease-out timing function is good for entrances. What about an ease-in or ease-in-out exit? Let’s do that as well.

gsap.to('.ball', {
  duration: 1.5,
  x: 200,
  scale: 2,
  ease: 'bounce'
})

gsap.to('.ball', {
  duration: 1.5,
  delay: 1.5,
  x: 0,
  scale: 1,
  ease: 'back.inOut(3)'
})

You might have noticed a few things happening. For example, we didn't use bounce.in on the second-to-last line (ease: 'back.inOut(3)'). Instead, we used another ease called back for ease-in-out. We also passed in a configuration option because, as you can see with Greensock's ease visualizer tool, we’re not limited to just the default configuration for that ease. We can adjust it to our needs. Neat!

You may have also noticed that we chained the animations using a delay. We took the length of the duration of the first animation and made sure the next one has a delay that matches. Now, that works here, but that’s pretty brittle. What if we want to change the length of the first one? Well, now we’ve got to go back through and change the delay that follows. And what if we have another animation after that? And another one after that? Well, we’d have to go back through and calculate all the other delays down the line. That's a lot of manual work.

We can offload that work to the computer. Some of my more complex animations are hundreds of chained animations! If I finish my work and want to adjust something in the beginning, I don’t want to have to go back through everything. Enter timelines:

gsap
  .timeline()
  .to(‘.ball’, {
    duration: 1.5,
    x: 200,
    scale: 2,
    ease: "bounce"
  })
  .to(‘.ball’, {
    duration: 1.5,
    x: 0,
    scale: 1,
    ease: "back.inOut(3)"
  });

See the Pen
Greensock Tutorial 4
by Sarah Drasner (@sdras)
on CodePen.

This instantiates a timeline and then chains the two animations off of it.

But we still have a bit of repetition where we keep using the same duration in each animation. Let’s create a default for that as an option passed to the timeline.

gsap
  .timeline({
    defaults: {
      duration: 1.5
    }
  })
  .to('.ball', {
    x: 200,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball', {
    x: 0,
    scale: 1,
    ease: "back.inOut(3)"
  });

See the Pen
Greensock Tutorial 4
by Sarah Drasner (@sdras)
on CodePen.

That’s pretty cool! Alright, you are probably starting to see how things are built this way. While it might not be a big deal in an animation this simple, defaults and timelines in really complex animations can truly keep code maintainable.

Now, what if we want to mirror this motion in the other direction with the ball, and just... keep it going? In other words, what if we want a loop? That’s when we add repeat: -1, which can be applied either to a single animation or to the entire timeline.

gsap
  .timeline({
    repeat: -1,
    defaults: {
      duration: 1.5
    }
  })
  .to('.ball', {
    x: 200,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball', {
    x: 0,
    scale: 1,
    ease: "back.inOut(3)"
  })
  .to('.ball', {
    x: -200,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball', {
    x: 0,
    scale: 1,
    ease: "back.inOut(3)"
  });

See the Pen
Greensock Tutorial 5
by Sarah Drasner (@sdras)
on CodePen.

We could also not only make it repeat but repeat and playback and forth, like a yoyo. That’s why we call this yoyo: true. To make the point clear, we’ll show this with just the first animation. You can see it plays forward, and then it plays in reverse.

gsap
  .timeline({
    repeat: -1,
    yoyo: true,
    defaults: {
      duration: 1.5
    }
  })
  .to('.ball', {
    x: 200,
    scale: 2,
    ease: "bounce"
  })

See the Pen
Greensock Tutorial 6
by Sarah Drasner (@sdras)
on CodePen.

Overlaps and Labels

It’s great that we can chain animations with ease, but real-life motion doesn’t exactly work this way. If you walk across the room to get a cup of water, you don’t walk. Then stop. Then pick up the water. Then drink it. You’re more likely to do things in one continuous movement. So let’s talk briefly about how to overlap movement and make things fire at once.

If we want to be sure things fire a little before and after each other on a timeline, we can use an incrementer or decrementer. If we take the following example that shows three balls animating one after another, it feels a little stiff.

gsap
  .timeline({
    defaults: {
      duration: 1.5
    }
  })
  .to('.ball', {
    x: 300,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball2', {
    x: 300,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball3', {
    x: 300,
    scale: 2,
    ease: "bounce"
  })

See the Pen
Greensock Tutorial 8
by Sarah Drasner (@sdras)
on CodePen.

Things get smoother if we overlap the movement just slightly using those decrementers passed as strings:

gsap
  .timeline({
    defaults: {
      duration: 1.5
    }
  })
  .to('.ball', {
    x: 300,
    scale: 2,
    ease: "bounce"
  })
  .to('.ball2', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, '-=1')
  .to('.ball3', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, '-=1')

See the Pen
Greensock Tutorial 9
by Sarah Drasner (@sdras)
on CodePen.

Another way we can do this is to use something called a label. Labels make sure things fire off at a particular point in time in the playhead of the animation. It looks like this:.add('labelName')

gsap
  .timeline({
    defaults: {
      duration: 1.5
    }
  })
  .add('start')
  .to('.ball', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start')
  .to('.ball2', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start')
  .to('.ball3', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start')

See the Pen
Greensock Tutorial 10
by Sarah Drasner (@sdras)
on CodePen.

We can even increment and decrement from the label. I actually do this a lot in my animations. It looks like this: 'start+=0.25'

gsap
  .timeline({
    defaults: {
      duration: 1.5
    }
  })
  .add('start')
  .to('.ball', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start')
  .to('.ball2', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start+=0.25')
  .to('.ball3', {
    x: 300,
    scale: 2,
    ease: "bounce"
  }, 'start+=0.5')

Whew! We’re able to do so much with this! Here’s an example of an animation that puts a lot of these premises together, with a bit of interaction using vanilla JavaScript. Be sure to click on the bell.

See the Pen
phone interaction upgrade
by Sarah Drasner (@sdras)
on CodePen.

If you're looking more for framework-based animation with GreenSock, here's an article I wrote that covers this in Vue, and a talk I gave that addresses React — it's a couple of years old but the base premises still apply.

But there’s still so much we haven’t cover, including staggers, morphing SVG, drawing SVG, throwing things around the screen, moving things along a path, animating text... you name it! I'd suggest heading over to GreenSock’s documentation for those details. I also have a course on Frontend Masters that covers all of these in much more depth and the materials are open source on my GitHub. I also have a lot of Pens that are open source for you to fork and play with.

I hope this gets you started working with animation on the web! I can’t wait to see what you make!

The post How to Animate on the Web With Greensock appeared first on CSS-Tricks.