11.7 C
New York
Wednesday, April 24, 2024

Case Research: Gabriel Contassot’s Portfolio — 2024


Gabriel Contassot's portfolio splash screen

Working with Gabriel on his new portfolio has been an excellent expertise. He initiated the challenge with a minimalist but well-conceived design, incorporating animation concepts and sustaining an open-minded method. This flexibility fostered in depth experimentation all through the event course of, which, in my expertise, yields the perfect outcomes.

The core of the web site contains a two-page “loop,” transitioning from a principal gallery on the homepage to an in depth challenge view. The target was to make sure cohesive animations and supply putting, colourful transitions when navigating from the dark-themed homepage to the brighter case research. As it is a portfolio, the first focus was on showcasing the content material successfully.

There’s a commented demo of the principle impact on the finish of this case examine.

Construction / Stack

Each time attainable, I desire to work with vanilla JavaScript and easy instruments, and this challenge introduced the right alternative to make the most of my present favourite stack. I used Astro for static web page era, Taxi to create a single-page-app-like expertise with clean web page transitions, and Gsap Tweens for animation results. Twgl offers WebGL helpers, whereas Lenis handle the scrolling.

All content material is delivered by way of Sanity, with the only real exception of case examine movies, that are hosted on Cloudflare and streamed utilizing Hls.

The web site is statically generated and deployed on Vercel, each by way of CI/CD and from Sanity to rebuild when the content material updates.

The CMS construction is kind of easy, only a assortment for the work, one for pages like /about, and a gaggle for generic information (which on this case is simply contact data). On this occasion the web site is fairly easy and this configuration just isn’t actually wanted, however contemplating the headless nature of this setup this was one of the simplest ways to make sure the content material aspect of issues might outlive the web site, and for a subsequent model we might (or whoever will work on it) doubtlessly construct on high.

The official integration for Astro/Sanity got here proper in the course of the challenge, enhancing the interplay between the 2. We’re additionally leveraging the Vercel Deploy plugin, so who makes use of the CMS can freely deploy a brand new model when wanted.

The entire repo appears to be like one thing like this:

/public
	(fonts and information)

/cms 
	(sanity setup)

/src
	/pages
		house.astro
		[...work].astro
		
	/layouts
		PageLayout.astro

	/elements
		[Website Components as .astro files]

	/js
		/modules (all of the js)
		/gl (all of the webgl)
        app.js (js entrypoint)

Astro + Sanity

On this case we’re utilizing Astro at a ten% of it’s potential, simply with.astro information (no frameworks). Principally as a templating language for static web site era. We’re principally leveraging the element method, that finally ends up being compiled right into a single, statically generated html doc.

For instance, the homepage appears to be like like this. On the high, in between the --- there’s what Astro calls frontmatter, which is solely the server aspect of issues that on this case executes at construct time since we’re not in SSR mode. Right here you may see an instance if this.

<!-- pages/Residence.astro -->

---
import Merchandise from "./Merchandise.astro";
import ScrollToExp from "../ScrollToExp.astro";
import ScrollUp from "../ScrollUp.astro";

import { getWork } from "../content material/sanity.js";

import Nav from "../Nav.astro";

const gadgets = await getWork(true);
const sorted = gadgets.type((a, b) => a.props.order - b.props.order);
---

<Nav />

<div>
  <determine
    data-track="nav"
    class="h-[120vh] flex items-end justify-center pb-[20vh]"
  >
    <ScrollToExp />
  </determine>
  {sorted.map((merchandise) => <Merchandise information={merchandise} />)}
  <determine
    data-track="nav2"
    class="h-[180vh] flex items-end justify-center pb-[3vh]"
  >
    <ScrollUp />
  </determine>
</div>

You possibly can try my starters right here, the place you’ll discover each the Astro and Sanity starters that I used to spin up this challenge.

Code

I exploit a single entry level for all my javascript (app.js) at a structure degree as a element, and the attention-grabbing half begins from there.

In my entry level I initialise all the principal elements of of the app.

<!-- elements/Canvas.js  -->

<canvas data-gl="c"></canvas>
<script>
  import "../js/app.js";
</script>
  • Pages — which is Taxi setup in a method so it returns guarantees. This manner I can simply await web page animations and make my life a bit simpler with preserving every part in sync (which I find yourself by no means doing correctly and manually syncing values as a result of I get messy and the supply is developing).
  • Scroll — which is solely a small lenis wrapper. Fairly normal tbh, just a few utilities and helper features in addition to the setup code. I even have the logic to subscribe and unsubscrube features from different elements that want the scroll, so I’m positive every part is at all times in sync.
  • Dom — holds all of the DOM associated code, each purposeful and animation associated.
  • Gl — that holds all of the WebGl issues, on this case fairly easy because it’s only a full display quad that I exploit to vary the background color with good and clean values
// app.js

class App {
  constructor() {

	// ...
	this.init()
  }

  init() {
    this.pages = new Pages();
    this.scroll = new Scroll();
    this.dom = new Dom();
    this.gl = new Gl();

    this.gl.initColorTrack();

    this.initEvents();
    this.render();

	// ...
  }

  // ...

In right here there are my principal (and solely) resize() and render() features.
This manner I’m positive I solely name requestAnimationFrame()as soon as render loop and have a single supply of fact for my time worth, and that listening and firing a single occasion for dealing with resize.

Animations

The animation framework depends on two major JavaScript courses: an Observer and a Observe.

An Observer, constructed utilizing the IntersectionObserver API, triggers at any time when a DOM node turns into seen or hidden within the viewport. This class is designed to be versatile, permitting builders to simply lengthen it and add customized performance as wanted.

In the meantime, the Observe class builds upon the Observer. It robotically listens to scroll and resize occasions, calculating a price between 0 and 1 that displays the on-screen place of a component. This class is configurable, permitting you to set the beginning and finish factors of the monitoring—successfully functioning as a bespoke ScrollTrigger. One among its key options is that it solely renders content material when the ingredient is in view, leveraging its foundational Observer structure to optimize efficiency.

// observe.js

export class Observe {
  constructor({ ingredient, config, addClass }) {
    this.ingredient = ingredient;
    this.config =  0,
    ;

    if (addClass !== undefined) this.addClass = addClass;
    this.init();
    this.begin();
  }
  
	// ....
}


// monitor.js

import { clientRect } from "./clientRect.js";
import { map, clamp, lerp, scale } from "./math.js";
import { Observe } from "./observe.js";

export class Observe extends Observe {
	worth = 0;
	inview = true;

	constructor({ ingredient, config }) {
		tremendous({ ingredient, config })
		this.ingredient = ingredient;
		
		this.config = {
		  bounds: [0, 1],
		  high: "backside",
		  backside: "high",
		  ...config,
		};
	
	}

	// ...
}

A sensible demonstration of how these courses perform is obvious on the case examine pages.

On this setup, photographs and movies seem on the display, activated by the Observer class. On the identical time, the scaling results utilized to pictures on the high and backside of the web page are simple transformations pushed by a Observe on the mum or dad ingredient.

The web page transition entails a easy ingredient that modifications coloration based mostly on the hyperlinks clicked. This ingredient then wipes upwards and away, successfully signaling a change within the web page.

Preloader

The preloader on our web site is extra of a stylistic function than a purposeful one—it doesn’t truly monitor loading progress, primarily as a result of there isn’t a lot content material to load. We launched it as a inventive enhancement because of the simplicity of the positioning.

Functionally, it consists of a textual content block that shows altering numbers. This textual content block is animated throughout the display utilizing a transformX property. The motion is managed by a setInterval perform, which triggers at progressively shorter intervals to simulate the loading course of.

// loader.js

import Tween from "gsap";

export class Loader {
  constructor({ ingredient }) {
    this.el = ingredient;
    this.quantity = this.el.youngsters[0];
  }

  animate() {
    let rely = 0;

    const totalDuration = 2.8;
    const values = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 23, 30, 50, 70, 80, 100];
    const splitDuration = totalDuration / values.size;

    return new Promise((resolve) => {
      const destroy = () => {
        Tween.to(this.el, {
          autoAlpha: 0,
          period: 0.8,
          ease: "sluggish.in",
          onComplete: () => {
            setTimeout(() => {
              this.el.take away();
              resolve();
            }, 800);
          },
        });
      };

      let interval = setInterval(() => {
        this.step(values[count++]);
        if (values[count] === undefined) {
          clearInterval(interval);
          destroy();
        }
      }, splitDuration * 1000);
    });
  }

  step(val) {
    let v = val;
    if (v === 100 && window.mobileCheck()) {
      v = 95;
    }
    this.quantity.textContent = val;
    this.quantity.model.rework = `translateX(${v}%)`;
  }
}

Scrambled Textual content

The textual content animation function relies on GSAP’s ScrambleText plugin, enhanced with extra utilities for higher management and stability.

Initially, we tried to recreate the performance from scratch to attenuate textual content motion—given the massive measurement of the textual content—however this proved difficult. We managed to stabilize the scrambling impact considerably by reusing the unique characters of every phrase completely, minimizing variations throughout every shuffle.

We additionally refined the interactive components, akin to making certain that the hover impact doesn’t activate throughout an ongoing animation. This was significantly vital as some unintended mixtures generated inappropriate phrases in French in the course of the scrambles.

For the homepage, we changed the hover-trigger with an onload activation for the menu/navigation centerpiece. We hardcoded the durations to synchronize completely with the specified timing of the visible results.

Moreover, we built-in CSS animations to handle the visibility of components, setting {merchandise}.model.animationDelay immediately in JavaScript. A Observe object was employed to dynamically alter the size of components based mostly on their scroll place, enhancing the interactive visible expertise.

// nav.js

this.values = {
  period: [1.2, 1.5, 0.4, 0.2, 1, 0.6, 0.6],
  del: [0, 0.4, 1.3, 1.4, 1.5, 1.6, 2.1],
  lined: [0, 0.3, 1.1, 1.5],
};

// ...

animateIn() {
    this.el.classList.add("anim");
    gsap.to(this.texts, { autoAlpha: 1, period: 1 });

    this.texts.forEach((line, i) => {
      line.model.fontKerning = "none"; // probs doesnt do something
      gsap.to(line, {
        period: this.values.period[i],
        delay: this.values.del[i],
        ease: "expo.out",
        scrambleText: {
          textual content: "{unique}",
          chars: [
            "GABRELCONTASO",
            "FRELANCDSGNOR",
            "1824",
            "-_",
            "SELCTDWORK",
            "NFOI",
            "CONTA",
          ][i],
          revealDelay: this.values.period[i] * 0.5,
          pace: 1,
        },
      });
    });
}

Homepage photographs impact

That is most likely probably the most attention-grabbing piece of it, and I wanted a few tries to grasp methods to make it work, earlier than realising which are actually simply absolute positioned photographs with a clip-path inset mixed with a Observe to sync it with the scroll that additionally controls the scaling of the internal picture.

// scrollImage.js

constructor() {
    // ...
    this.picture.model.rework = `scale(1)`;
    this.imagWrap.model.clipPath = "inset(100% 0 0 0)";
    // ...
}

 render() {
    if (!this.inView) return;
    this.monitor?.render();

    this.picture.model.rework = `scale(${1.2 + this.monitor.worth*-0.2})`;

    this.imagWrap.model.clipPath = `
      inset(${this.monitor.value2 * 100}% 
        0 
        ${this.monitor.value1 * 100}% 
        0)
    `;
  }

Coloration Change

It’s the one WebGl piece of this complete web site.

Initially, the idea concerned altering colours based mostly on scroll interactions, however this was ultimately moderated attributable to issues about it turning into overly distracting. The implementation now entails a full-screen quad, constructed from a single triangle with remapped UV coordinates, which permits for a extra versatile and responsive visible show.

The colour values are dynamically retrieved from attributes specified within the DOM, which may be freely adjusted by way of the CMS. This setup entails changing coloration values from hexadecimal to vec3 format. Moreover, a few GSAP Tweens are employed to handle the animations for transitioning the colours out and in easily.

This use of WebGL ensures that the colour transitions are usually not solely clean and visually interesting but additionally performant, avoiding the lag and choppiness that may happen with heavy CSS animations.

Demo

This can be a minimal rebuild of the principle homepage impact. Apart from some CSS to make it features, 90% of it occurs within the monitor.js file, whereas every part is initialised from principal.js.

The Observe class is used as the bottom to create the ImageTransform one, which extends the performance and transforms the picture.

There’s a couple of helper features to calculate the bounds on resize and to attempt to maximise efficiency it’s solely known as by lenis when a scroll is going on. Ideally must be wrapped by an Observer so it solely calculates when is in view, however I stored it easier for the demo.

Thanks!

It’s a easy web site, however was a enjoyable and attention-grabbing problem for us nonetheless.
Hit me up on Twitter when you have any questions or need to know extra!

👀



Supply hyperlink

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles