Draggable Menu with Image Grid Previews

After our little draggable image strip experiment, we wanted to explore using the dragging functionality on a menu. The idea is to show a large inline menu with some scattered thumbnails. The menu can be dragged and while doing so, the thumbnails get moved with an animation. Each menu item also changes the letters to show an outlined version. When clicking on the “explore” link under a menu item, the thumbnails move and enlarge to form a grid.

The animations are powered by TweenMax and we use Dave DeSandro’s Draggabilly.

Attention: Note that the demo is experimental and that we use modern CSS properties that might not be supported in older browsers.

The initial view looks as follows:

DraggableMenu_01

When clicking on the explore link, we animate all thumbnails to their place in a custom grid.

DraggableMenu_02

You can see it in action here:

DraggableMenu.2019-06-19 10_45_16_optimized

We hope you enjoy this menu and find it useful!

References and Credits

Draggable Menu with Image Grid Previews was written by Mary Lou and published on Codrops.

Crossroads Slideshow

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

The animations are powered by TweenMax.

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

The initial view of the slideshow looks as follows:

CrossroadsSlideshow_01

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

CrossroadsSlideshow_02

We also have a dark mode option:

CrossroadsSlideshow_03

CrossroadsSlideshow_04

Here’s how the animations look:

We hope you enjoy this slideshow and find it useful!

References and Credits

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

Custom Cursor Effects




Custom cursors certainly were a big trend in web development in 2018. In the following tutorial we’ll take a look at how to create a magnetic noisy circle cursor for navigation elements as shown in Demo 4. We’ll be using Paper.js with Simplex Noise.

The custom cursor effect we’re going to build in this tutorial
The custom cursor effect we’re going to build in this tutorial

The Cursor Markup

The markup for the cursor will be split up into two elements. A simple <div> for the small white dot and a <Canvas> element to draw the red noisy circle using Paper.js.


<body class="tutorial">
  <main class="page">
    <div class="page__inner">
      
      <!-- The cursor elements --> 
      <div class="cursor cursor--small"></div>
      <canvas class="cursor cursor--canvas" resize></canvas>
      
    </div>
  </main>
</body>

Basic Colors and Layout

To give our demo some color and layout we’re defining some basic styles.

body.tutorial {
  --color-text: #fff;
  --color-bg: #171717;
  --color-link: #ff0000;
  background-color: var(--color-bg);
}
.page {
  position: absolute;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.page__inner {
  display: flex;
  justify-content: center;
  width: 100%;
}

The Basic Cursor Styles

Basically both cursor elements have a fixed position. To be exactly at the tip of the mouse pointer, we adjust left and top of the small cursor. The canvas will simply fill the whole viewport.

.cursor {
  position: fixed;
  left: 0;
  top: 0;
  pointer-events: none;
}
.cursor--small {
  width: 5px;
  height: 5px;
  left: -2.5px;
  top: -2.5px;
  border-radius: 50%;
  z-index: 11000;
  background: var(--color-text);
}
.cursor--canvas {
  width: 100vw;
  height: 100vh;
  z-index: 12000;
}

The Link Element(s)

For the sake of simplicity we will just take one link element which contains an SVG icon that we can then animate on hover.

<nav class="nav">
 <a href="#" class="link">
  <svg class="settings-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
   <g class="settings-icon__group settings-icon__group--1">
     <line class="settings-icon__line" x1="79.69" y1="16.2" x2="79.69" y2="83.8"/>
     <rect class="settings-icon__rect" x="73.59" y="31.88" width="12.19" height="12.19"/>
    </g>
   <g class="settings-icon__group settings-icon__group--2">
     <line class="settings-icon__line" x1="50.41" y1="16.2" x2="50.41" y2="83.8"/>
     <rect class="settings-icon__rect" x="44.31" y="54.33" width="12.19" height="12.19"/>
   </g>
   <g class="settings-icon__group settings-icon__group--3">
     <line class="settings-icon__line" x1="20.31" y1="16.2" x2="20.31" y2="83.8"/>
     <rect class="settings-icon__rect" x="14.22" y="26.97" width="12.19" height="12.19"/>
   </g>
  </svg>
 </a>
 <!-- you can add more links here -->
</nav>

The Navigation and Link Styles

Here we’re defining some styles for the navigation, its items and hover transitions.

.nav {
  display: flex;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);  
}
.link {
  display: flex;
  width: 75px;
  height: 75px;
  margin: 0 5px;
  justify-content: center;
  align-items: center;
}
.settings-icon {
  display: block;
  width: 40px;
  height: 40px;
}
.settings-icon__line {
  stroke: var(--color-text);
  stroke-width: 5px;
  transition: all 0.2s ease 0.05s;
}
.settings-icon__rect {
  stroke: var(--color-text);
  fill: var(--color-bg);
  stroke-width: 5px;
  transition: all 0.2s ease 0.05s;
}
.link:hover .settings-icon__line,
.link:hover .settings-icon__rect {
  stroke: var(--color-link);
  transition: all 0.2s ease 0.05s;
}
.link:hover .settings-icon__group--1 .settings-icon__rect {
  transform: translateY(20px);
}
.link:hover .settings-icon__group--2 .settings-icon__rect {
  transform: translateY(-20px);
}
.link:hover .settings-icon__group--3 .settings-icon__rect {
  transform: translateY(25px);
} 
This is what the result should look like by now.
This is what the result should look like by now.

Including Paper and SimplexNoise

As mentioned before, we need to include Paper.js. To animate the noisy circle we need Simplex Noise in addition to that.

<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>

Hiding the System Cursor

Because we’re building our own cursor, we need to make sure to not show the system’s cursor in its normal state and when hovering links.

.page, .page a {
 cursor: none;
}

Animating the Small Dot Cursor

In order to have smooth performance we use a requestAnimationFrame()-loop.

// set the starting position of the cursor outside of the screen
let clientX = -100;
let clientY = -100;
const innerCursor = document.querySelector(".cursor--small");

const initCursor = () => {
  // add listener to track the current mouse position
  document.addEventListener("mousemove", e => {
    clientX = e.clientX;
    clientY = e.clientY;
  });
  
  // transform the innerCursor to the current mouse position
  // use requestAnimationFrame() for smooth performance
  const render = () => {
    innerCursor.style.transform = `translate(${clientX}px, ${clientY}px)`;
    // if you are already using TweenMax in your project, you might as well
    // use TweenMax.set() instead
    // TweenMax.set(innerCursor, {
    //   x: clientX,
    //   y: clientY
    // });
    
    requestAnimationFrame(render);
  };
  requestAnimationFrame(render);
};

initCursor();

Setting up the Circle on Canvas

The following is the basis for the red circle part of the cursor. In order to move the red circle around we’ll use a technique called linear interpolation.

let lastX = 0;
let lastY = 0;
let isStuck = false;
let showCursor = false;
let group, stuckX, stuckY, fillOuterCursor;

const initCanvas = () => {
  const canvas = document.querySelector(".cursor--canvas");
  const shapeBounds = {
    width: 75,
    height: 75
  };
  paper.setup(canvas);
  const strokeColor = "rgba(255, 0, 0, 0.5)";
  const strokeWidth = 1;
  const segments = 8;
  const radius = 15;
  
  // we'll need these later for the noisy circle
  const noiseScale = 150; // speed
  const noiseRange = 4; // range of distortion
  let isNoisy = false; // state
  
  // the base shape for the noisy circle
  const polygon = new paper.Path.RegularPolygon(
    new paper.Point(0, 0),
    segments,
    radius
  );
  polygon.strokeColor = strokeColor;
  polygon.strokeWidth = strokeWidth;
  polygon.smooth();
  group = new paper.Group([polygon]);
  group.applyMatrix = false;
  
  const noiseObjects = polygon.segments.map(() => new SimplexNoise());
  let bigCoordinates = [];
  
  // function for linear interpolation of values
  const lerp = (a, b, n) => {
    return (1 - n) * a + n * b;
  };
  
  // function to map a value from one range to another range
  const map = (value, in_min, in_max, out_min, out_max) => {
    return (
      ((value - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
    );
  };
  
  // the draw loop of Paper.js 
  // (60fps with requestAnimationFrame under the hood)
  paper.view.onFrame = event => {
    // using linear interpolation, the circle will move 0.2 (20%)
    // of the distance between its current position and the mouse
    // coordinates per Frame
    lastX = lerp(lastX, clientX, 0.2);
    lastY = lerp(lastY, clientY, 0.2);
    group.position = new paper.Point(lastX, lastY);
  }
}

initCanvas();
cursor-move
The custom cursor already flying around on the screen.

Handling the Hover State

const initHovers = () => {

  // find the center of the link element and set stuckX and stuckY
  // these are needed to set the position of the noisy circle
  const handleMouseEnter = e => {
    const navItem = e.currentTarget;
    const navItemBox = navItem.getBoundingClientRect();
    stuckX = Math.round(navItemBox.left + navItemBox.width / 2);
    stuckY = Math.round(navItemBox.top + navItemBox.height / 2);
    isStuck = true;
  };
  
  // reset isStuck on mouseLeave
  const handleMouseLeave = () => {
    isStuck = false;
  };
  
  // add event listeners to all items
  const linkItems = document.querySelectorAll(".link");
  linkItems.forEach(item => {
    item.addEventListener("mouseenter", handleMouseEnter);
    item.addEventListener("mouseleave", handleMouseLeave);
  });
};

initHovers();

Making the Circle “Magnetic” and “Noisy”

The following snipped is the extended version of the above-mentioned paper.view.onFrame method.

// the draw loop of Paper.js
// (60fps with requestAnimationFrame under the hood)
paper.view.onFrame = event => {
  // using linear interpolation, the circle will move 0.2 (20%)
  // of the distance between its current position and the mouse
  // coordinates per Frame
  if (!isStuck) {
    // move circle around normally
    lastX = lerp(lastX, clientX, 0.2);
    lastY = lerp(lastY, clientY, 0.2);
    group.position = new paper.Point(lastX, lastY);
  } else if (isStuck) {
    // fixed position on a nav item
    lastX = lerp(lastX, stuckX, 0.2);
    lastY = lerp(lastY, stuckY, 0.2);
    group.position = new paper.Point(lastX, lastY);
  }
  
  if (isStuck && polygon.bounds.width < shapeBounds.width) { 
    // scale up the shape 
    polygon.scale(1.08);
  } else if (!isStuck && polygon.bounds.width > 30) {
    // remove noise
    if (isNoisy) {
      polygon.segments.forEach((segment, i) => {
        segment.point.set(bigCoordinates[i][0], bigCoordinates[i][1]);
      });
      isNoisy = false;
      bigCoordinates = [];
    }
    // scale down the shape
    const scaleDown = 0.92;
    polygon.scale(scaleDown);
  }
  
  // while stuck and big, apply simplex noise
  if (isStuck && polygon.bounds.width >= shapeBounds.width) {
    isNoisy = true;
    // first get coordinates of large circle
    if (bigCoordinates.length === 0) {
      polygon.segments.forEach((segment, i) => {
        bigCoordinates[i] = [segment.point.x, segment.point.y];
      });
    }
    
    // loop over all points of the polygon
    polygon.segments.forEach((segment, i) => {
      
      // get new noise value
      // we divide event.count by noiseScale to get a very smooth value
      const noiseX = noiseObjects[i].noise2D(event.count / noiseScale, 0);
      const noiseY = noiseObjects[i].noise2D(event.count / noiseScale, 1);
      
      // map the noise value to our defined range
      const distortionX = map(noiseX, -1, 1, -noiseRange, noiseRange);
      const distortionY = map(noiseY, -1, 1, -noiseRange, noiseRange);
      
      // apply distortion to coordinates
      const newX = bigCoordinates[i][0] + distortionX;
      const newY = bigCoordinates[i][1] + distortionY;
      
      // set new (noisy) coodrindate of point
      segment.point.set(newX, newY);
    });
    
  }
  polygon.smooth();
};
hover-perlin
And et voilà, there we have our smooth and noisy circle effect.

General Remarks

I hope you enjoyed this tutorial and have fun playing around with it in your own projects. Of course this is just a starting point and you can go crazier with animations, shapes, colors etc. If you have any questions, please feel free to reach out or shoot me a tweet!

References and Credits

Custom Cursor Effects was written by Stefan Kaltenegger and published on Codrops.

Buildings Wave Animation with Three.js

This tutorial is going to demonstrate how to build a wave animation effect for a grid of building models using three.js and TweenMax (GSAP).

Attention: This tutorial assumes you already have a some understanding of how three.js works.
If you are not familiar, I highly recommend checking out the official documentation and examples .

Inspiration

Source: View
by: Baran Kahyaoglu

Core Concept

The idea is to create a grid of random buildings, that reveal based on their distance towards the camera. The motion we are trying to get is like a wave passing through, and the farthest elements will be fading out in the fog.

side-view

We also modify the scale of each building in order to create some visual randomness.

Getting started

First we have to create the markup for our demo. It’s a very simple boilerplate since all the code will be running inside a canvas element:

<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <meta name="target" content="all">
    <meta http-equiv="cleartype" content="on">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="mobile-web-app-capable" content="yes">
    <title>Buildings Wave</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/100/three.min.js">></script>
    <script src="https://Threejs.org/examples/js//loaders/OBJLoader.js" ></script>
    <script src="https://Threejs.org/examples/js/controls/OrbitControls.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>
  </head>
  <body>
  </body>
</html>

Basic CSS Styles

html, body {
  margin: 0;
  padding: 0;
  background-color: #fff;
  color: #fff;
  box-sizing: border-box;
  overflow: hidden;
}

canvas {
  width: 100%;
  height: 100%;
}

Initial setup of the 3D world

We create a function called init inside our main class. All subsequent methods will be added inside this method.


init() {
  this.group = new THREE.Object3D();
  this.gridSize = 40;
  this.buildings = [];
  this.fogConfig = {
    color: '#fff',
    near: 1,
    far: 138
  };
}

Creating our 3D scene

createScene() {
  this.scene = new THREE.Scene();

  this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  this.renderer.setSize(window.innerWidth, window.innerHeight);

  this.renderer.shadowMap.enabled = true;
  this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;

  document.body.appendChild(this.renderer.domElement);

  // this is the line that will give us the nice foggy effect on the scene
  this.scene.fog = new THREE.Fog(this.fogConfig.color, this.fogConfig.near, this.fogConfig.far);
}

Camera

Let’s add a camera for to scene:

createCamera() {
  const width = window.innerWidth;
  const height = window.innerHeight;

  this.camera = new THREE.PerspectiveCamera(20, width / height, 1, 1000);

  // set the distance our camera will have from the grid
  // this will give us a nice frontal view with a little perspective
  this.camera.position.set(3, 16, 111);

  this.scene.add(this.camera);
}

Ground

Now we need to add a shape to serve as the scene’s ground

addFloor() {
  const width = 200;
  const height = 200;
  const planeGeometry = new THREE.PlaneGeometry(width, height);

  // all materials can be changed according to your taste and needs
  const planeMaterial = new THREE.MeshStandardMaterial({
    color: '#fff',
    metalness: 0,
    emissive: '#000000',
    roughness: 0,
  });

  const plane = new THREE.Mesh(planeGeometry, planeMaterial);

  planeGeometry.rotateX(- Math.PI / 2);

  plane.position.y = 0;

  this.scene.add(plane);
}

Load 3D models

Before we can build the grid, we have to load our models.

models

loadModels(path, onLoadComplete) {
  const loader = new THREE.OBJLoader();

  loader.load(path, onLoadComplete);
}

onLoadModelsComplete(model) {
  // our buildings.obj file contains many models
  // so we have to traverse them to do some initial setup

  this.models = [...model.children].map((model) => {
    // since we don't control how the model was exported
    // we need to scale them down because they are very big

    // scale model down
    const scale = .01;
    model.scale.set(scale, scale, scale);

    // position it under the ground
    model.position.set(0, -14, 0);

    // allow them to emit and receive shadow
    model.receiveShadow = true;
    model.castShadow = true;

    return model;
  });

  // our list of models are now setup
}

Ambient Light

addAmbientLight() {
  const ambientLight = new THREE.AmbientLight('#fff');

  this.scene.add(ambientLight);
}

Grid Setup

Now we are going to place those models in a grid layout.

initialgrid

createGrid() {
  // define general bounding box of the model
  const boxSize = 3;

  // define the min and max values we want to scale
  const max = .009;
  const min = .001;

  const meshParams = {
    color: '#fff',
    metalness: .58,
    emissive: '#000000',
    roughness: .18,
  };

  // create our material outside the loop so it performs better
  const material = new THREE.MeshPhysicalMaterial(meshParams);

  for (let i = 0; i < this.gridSize; i++) {
    for (let j = 0; j < this.gridSize; j++) {

      // for every iteration we pull out a random model from our models list and clone it
      const building = this.getRandomBuiding().clone();

      building.material = material;

      building.scale.y = Math.random() * (max - min + .01);

      building.position.x = (i * boxSize);
      building.position.z = (j * boxSize);

      // add each model inside a group object so we can move them easily
      this.group.add(building);

      // store a reference inside a list so we can reuse it later on
      this.buildings.push(building);
    }
  }

  this.scene.add(this.group);

  // center our group of models in the scene
  this.group.position.set(-this.gridSize - 10, 1, -this.gridSize - 10);
}

Spot Light

We also add a SpotLight to the scene for a nice light effect.

spot-light

addSpotLight() {
  const light = { color: '#fff', x: 100, y: 150, z: 100 };
  const spotLight = new THREE.SpotLight(light.color, 1);

  spotLight.position.set(light.x, light.y, light.z);
  spotLight.castShadow = true;

  this.scene.add(spotLight);
}

Point Lights

Let's add some point lights.

point-lights

addPointLight(params) {
  // sample params
  // {
  //   color: '#00ff00',
  //   intensity: 4,
  //   position: {
  //     x: 18,
  //     y: 22,
  //     z: -9,
  //   }
  // };

  const pointLight = new THREE.PointLight(params.color, params.intensity);

  pointLight.position.set(params.position.x, params.position.y, params.position.z);

  this.scene.add(pointLight);
}

Sort Models

Before we animate the models into the scene, we want to sort them according to their z distance to the camera.

sortBuildingsByDistance() {
  this.buildings.sort((a, b) => {
    if (a.position.z > b.position.z) {
      return 1;
    }

    if (a.position.z < b.position.z) {
      return -1;
    }

    return 0;
  }).reverse();
}

Animate Models

This is the function where we go through our buildings list and animate them. We define the duration and the delay of the animation based on their position in the list.

showBuildings() {
  this.sortBuildingsByDistance();

  this.buildings.map((building, index) => {
    TweenMax.to(building.position, .3 + (index / 350), { y: 1, ease: Power3.easeOut, delay: index / 350 });
  });
}

Here is how a variation with camera rotation looks like:

Credits

Buildings Wave Animation with Three.js was written by Ion D. Filho and published on Codrops.

Layer Motion Slideshow

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

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

The initial view of the slideshow looks as follows:

LayerMotionSlideshow_01

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

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

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

LayerMotionSlideshow_02

We hope you like this slideshow and find it useful!

References and Credits

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

Motion Transition Effect

Today we’d like to share a little speedy motion effect with you. The idea is based on the Dribbble shot Ping Pong Slow Motion by Gal Shir where you can see a ping pong ball being shot from one racket to the other. The motion in the animation is enhanced by squeezing the ball and making some background stripes’ height pulsate. This is exactly what we want to do in a slideshow transition: we’ll squeeze the image and add some background effect. Additionally, we’ll make the letters of the title fly away consecutively.

The animations are powered by TweenMax.

The grain texture for the background was generated using Grained by Sarath Saleem.

Attention: Note that we use modern CSS properties like CSS Grid and CSS Custom Properties that are not supported in older browsers.

The slideshow shows a image in the center of the page:

MotionTransition_01

When clicking on “next” or “previous”, the image will move away, being squeezed to create the illusion of a fast acceleration. The letters will fly away and the background shapes will start “pulsating”.

MotionTransMain

We hope you enjoy this little effect and find it useful!

References and Credits

Motion Transition Effect was written by Mary Lou and published on Codrops.