• About Us
  • Privacy Policy
  • Disclaimer
  • Contact Us
AimactGrow
  • Home
  • Technology
  • AI
  • SEO
  • Coding
  • Gaming
  • Cybersecurity
  • Digital marketing
No Result
View All Result
  • Home
  • Technology
  • AI
  • SEO
  • Coding
  • Gaming
  • Cybersecurity
  • Digital marketing
No Result
View All Result
AimactGrow
No Result
View All Result

Arnaud Rocca’s Portfolio: From a GSAP-Powered Movement System to Fluid WebGL

Admin by Admin
April 1, 2026
Home Coding
Share on FacebookShare on Twitter



Each designer and developer finally faces the identical problem whereas designing their private portfolio: How do you construct a showcase that doesn’t simply checklist what you do, however truly exhibits it?

For this portfolio, my aim was to seek out the appropriate steadiness between showcasing my work and constructing one thing reflective of what I do as a artistic developer. With that in thoughts, I began designing my portfolio with the purpose to create one thing minimalist but revolutionary, a portfolio that might let my work converse for itself whereas showcasing my creativity.

Design & Inspirations

Not like most net initiatives, and since I used to be engaged on each the design and growth myself, I didn’t watch for the design to be completed earlier than kicking off growth. Each moved ahead in parallel, which allowed me to completely discover my creativity, with concepts coming from either side.

Residence web page

The house web page was probably the most tough to design. I went by a number of levels, examined many layouts and developed totally different navigation choices earlier than arising with the ultimate end result as a result of I actually needed the house web page to be impactful whereas serving its function as the web site’s entry level.

Its navigation is considerably impressed by Instagram tales, with autoplaying slides. However to make it extra user-friendly, I additionally linked the slider to a number of inputs: up/down scroll, left/proper keyboard keys, bullet clicks, and eventually the one I’ve performed probably the most with, dragging over the fluid impact.

Venture pages

Together with the house web page, the undertaking pages are a very powerful a part of a portfolio. With these, I needed to showcase my initiatives in the absolute best means, particularly since a few of them are not obtainable on-line.

That’s why I intentionally went for a minimalist and uncluttered format with beneficiant white area and impartial colours, though I enhanced this shade palette with an accent shade that’s totally different for every undertaking, giving every case research its personal distinctive temper.

Technical stack

  • Nuxt: Frontend framework
  • GSAP: Animation library
  • Lenis: Easy scroll library
  • OGL: Minimal WebGL library
  • Prismic: Headless CMS
  • Figma: Design software

Whatever the frameworks and libraries used, one among my pointers as a developer is at all times to provide clear reusable code. This utilized to each piece of code on this undertaking and has influenced my technical selections whereas creating elements, composables, utility capabilities, animations, WebGL results…

UI animations

GSAP Results: Creating reusable animations

With reusability as a core precept, many of the animations have been constructed utilizing GSAP results. All results have been positioned in a devoted folder and adopted a constant naming conference, permitting me to mechanically register them because of Vite’s Glob Import characteristic.

// ~/results/index.ts

export default import.meta.glob('~/results/*.impact.{js,ts}', {
	keen: true,
	import: 'default',
	base: '/results/'
});
// ~/plugins/gsap.shopper.ts

import results from '~/results';

export default defineNuxtPlugin(() => {
	// Register plugins
	// [...]

	// Register results
	Object.values(results).forEach((impact) => gsap.registerEffect(impact));
});

I began by creating some fundamental results that wrap a single tween on a given goal: fade, colorize, scale…

If I’m being fully sincere, many of the fundamental results had already been created for earlier initiatives earlier than this one even began, however I assume that’s the purpose of writing reusable code.

const colorizeEffect: GsapEffect = {
	title: 'colorize',
	impact: (goal, { autoClear, ...config } = { shade: '' }) => {
		if (autoClear) {
			config.clearProps = ['color', config.clearProps].filter(Boolean).be a part of(',');
		}

		return gsap.to(goal, { ...config });
	},
	defaults: { length: 0.2, ease: 'sine.out' },
	extendTimeline: true
};

After creating the fundamental results, I may add them to timelines or mix them into extra complicated results to create extra superior animations.

For instance, the fadeColor impact is a mix of two fundamental results into one which fades in a goal whereas animating its shade. As soon as created, I may animate texts with this fade/shade impact anyplace throughout the web site.

const fadeColorEffect: GsapEffect = {
	title: 'fadeColor',
	impact: (
		goal,
		{ autoAlpha, length, stagger, ease, onStart, onComplete, onUpdate, autoClear, ...config } = {}
	) => {
		const currentColor = gsap.getProperty(goal, 'shade');
		const shade = config.shade;

		const tl = gsap.timeline({ onStart, onComplete, onUpdate });
		tl.set(goal, { shade }, 0);
		tl.fadeIn(goal, { autoAlpha, length, stagger, ease, autoClear }, 0);
		tl.colorize(goal, { shade: currentColor, length, stagger, ease, autoClear }, config.timeout);
		return tl;
	},
	defaults: { autoAlpha: 1, length: 0.9, timeout: 0.3 },
	extendTimeline: true
};

Thanks to those results, every new element of the web site could possibly be animated shortly with constant, examined conduct, with out having to reinvent the wheel for every new part.

GSAP ScrollTrigger x Vue: A system for scroll-based animations

With the results library in place, I wanted a clear method to set off these animations on scroll. I began by making a Vue equal of GSAP’s useGSAP React hook that wraps all GSAP animations and ScrollTriggers which can be created inside the provided perform, scopes all selector textual content to a specific Ingredient or Ref and helps reactive dependencies.

export perform useGSAP(
	callback: gsap.ContextFunc,
	$scope?: MaybeRefOrGetter,
	dependencies: Array> = [],
	{ revertOnUpdate = false }: GsapParameters = {}
) {
	let context: gsap.Context;

	perform replace() {
		if (revertOnUpdate) context?.revert();
		context?.clear();
		context?.add(callback);
	}

	dependencies.forEach((dependency) => {
		if (isRef(dependency)) watch(dependency, replace);
		else watch(() => toValue(dependency), replace);
	});

	onMounted(() => {
		context = gsap.context(callback, toValue($scope) ?? undefined);
	});

	onUnmounted(() => {
		context?.revert();
	});
}

From there, I constructed useScrollAnimateIn: a higher-level composable particularly designed for scroll-triggered animations. Since I knew I’d be animating loads of break up texts, I additionally added a method to create revert capabilities in my elements and name them from the composable itself.

export perform useScrollAnimateIn(
	animateIn: () => gsap.core.Timeline,
	$scope: MaybeRefOrGetter,
	{ scrollTrigger = {}, dependencies = [], ...params }: ScrollAnimateInParameters = {}
) {
	useGSAP(
		() => {
			const animateInTl = animateIn().pause(0);

			ScrollTrigger.create({
				...scrollTrigger,
				set off: scrollTrigger.set off ?? toValue($scope),
				as soon as: true,
				onEnter: (self: ScrollTrigger) => {
					animateInTl.play(0);
					scrollTrigger.onEnter?.(self);
				}
			});

			return () => {
				animateInTl.information?.revert?.();
			};
		},
		$scope,
		dependencies,
		{ revertOnUpdate: true, ...params }
	);
}

Because of this composable, writing scroll-triggered animations grew to become fully targeted on the animations themselves, permitting me to shortly and simply create animate-in timelines with reversible break up texts.

In observe, it seems like this:

useScrollAnimateIn(
	() => {
		const textual content = new SplitText('.textual content', { sort: 'chars' });

		const tl = gsap.timeline({
			information: {
				revert: () => {
					textual content.revert();
				}
			}
		});

		tl.fadeColor(
			textual content.chars,
			{
				shade: 'var(--color)',
				length: 0.9,
				stagger: { quantity: 0.2 },
				timeout: 0.3,
				autoClear: true,
				onComplete: () => {
					textual content.revert();
				}
			},
			0
		);

		return tl;
	},
	$el,
	{ scrollTrigger: { begin: 'prime middle' } }
);

Past being a greatest observe, reverting break up texts grew to become strictly obligatory since useScrollAnimateIn re-runs the callback every time a reactive dependency adjustments. With out reverting first, every re-run would name SplitText on an already-split component, nesting components inside components and breaking each the animation and the DOM. It additionally helps maintain the DOM dimension down, splitting a paragraph into chars can simply generate lots of of additional nodes, so cleansing them up after use is a significant efficiency consideration.

GSAP SplitText x Vue: Implementing reactive textual content animations

Given {that a} vital quantity of the web site’s animations depends on textual content results, I made a decision to create a strong basis for implementing them. So as a substitute of calling GSAP SplitText straight in every element, I created a useSplitText composable and a corresponding element that integrates SplitText’s performance with Vue’s lifecycle and reactivity.

perform useSplitText(
	$component: MaybeRefOrGetter,
	{ autoSplit = true, onSplit: onSplitCallback, onRevert: onRevertCallback, ...params }: SplitText.Vars
) {
	const $splitText = shallowRef(null);

	const $components = ref([]);
	const $chars = ref([]);
	const $phrases = ref([]);
	const $traces = ref([]);
	const $masks = ref([]);

	const isSplit = ref(false);

	perform createSplitText(): SplitTextInstance | null {
		const $el = toValue($component);
		if ($el) {
			return new SplitText($el, { autoSplit, onSplit, onRevert, ...params });
		}
		return null;
	}

	perform onSplit(splitText: SplitTextInstance) {
		$components.worth = splitText.components ?? [];
		$chars.worth = splitText.chars ?? [];
		$phrases.worth = splitText.phrases ?? [];
		$traces.worth = splitText.traces ?? [];
		$masks.worth = splitText.masks ?? [];
		isSplit.worth = splitText.isSplit ?? true;
		onSplitCallback?.(splitText);
		return splitText;
	}

	perform onRevert(splitText: SplitTextInstance) {
		$components.worth = splitText.components ?? [];
		$chars.worth = splitText.chars ?? [];
		$phrases.worth = splitText.phrases ?? [];
		$traces.worth = splitText.traces ?? [];
		$masks.worth = splitText.masks ?? [];
		isSplit.worth = splitText.isSplit ?? false;
		onRevertCallback?.(splitText);
		return splitText;
	}

	perform break up() {
		return $splitText.worth?.break up() ?? null;
	}

	perform revert() {
		return $splitText.worth?.revert() ?? null;
	}

	onMounted(() => {
		$splitText.worth = createSplitText();
	});

	return {
		$components: shallowReadonly($components),
		$chars: shallowReadonly($chars),
		$phrases: shallowReadonly($phrases),
		$traces: shallowReadonly($traces),
		$masks: shallowReadonly($masks),
		isSplit: readonly(isSplit),
		break up,
		revert
	};
}

β€œTheΒ Leftovers” textual content transition

The house web page textual content transition is one thing I’ve needed to breed since I watched β€œTheΒ Leftovers”, particularly these few seconds on the finish of the present’s opening title the place characters transition from one title to the following relatively than merely fading in and out.

I broke down the animation as follows:

  1. Determine the characters from the earlier string that additionally seem within the subsequent one (theΒ leftovers)
  2. Hold theΒ leftovers seen
  3. Cover the remaining earlier characters
  4. Animate theΒ leftovers to their corresponding positions within the subsequent string
  5. Present the following characters and conceal the earlier ones as soon as each character is in place

So with the intention to reproduce this textual content transition in a reusable and reactive means (you’re seeing it coming), I created a element that manages the matching algorithm, watches its props to find out which textual content must be at the moment displayed and triggers the transition. Internally, it creates one element per textual content merchandise acquired as props and makes use of them to transition between these textual content on props adjustments.

sort Leftover = {
	nextIndex: quantity;
	previousIndex: quantity;
	char: string;
	delta: quantity;
};

sort Leftovers = Map;

perform getLeftovers(
	$nextText: SplitTextComponentInstance | null,
	$previousText: SplitTextComponentInstance | null
): Leftovers {
	const $nextChars = $nextText?.$chars ?? [];
	const $previousChars = $previousText?.$chars ?? [];
	const nextChars = $nextChars.map(($char) => $char.textContent);
	const previousChars = $previousChars.map(($char) => $char.textContent);

	const leftovers: Leftovers = new Map();

	previousChars.forEach((previousChar, previousIndex) => {
		const match = nextChars
			.map((nextChar, nextIndex) => ({
				index: nextIndex,
				char: nextChar,
				delta: Math.abs(nextIndex - previousIndex)
			}))
			.filter(({ char }) => char === previousChar)
			.type((a, b) => a.delta - b.delta)
			.at(0);

		if (match) {
			const leftover = leftovers.get(match.index);
			if (!leftover || match.delta < leftover.delta) {
				leftovers.set(match.index, {
					nextIndex: match.index,
					previousIndex,
					char: match.char,
					delta: match.delta
				});
			}
		}
	});

	return leftovers;
}

VoilΓ ! After tweaking the durations, delays and eases, I had a reusable and reactiveΒ  element prepared for use.

WebGL results

OGL: A small however efficient WebGL library

OGL describes itself as β€œa small, efficient WebGL library aimed toward builders who like minimal layers of abstraction, and are interested by creating their very own shaders”.

Precisely what I wanted!

WebGL fluid simulation

It wasn’t my first time enjoying with WebGL fluid simulation and it won’t be the final, that’s why I made a decision to speculate a while to create a well-structured, reusable FluidSimulation helper class particularly devoted to this function.

The fluid simulation was constructed ranging from OGL’s Put up Fluid Distortion instance, which I then refactored into the next structure:

webgl/
β”œβ”€β”€ helpers/
β”‚   β”œβ”€β”€ FluidSimulation.ts 	# Helper class to render fluid simulation
β”‚   β”œβ”€β”€ RenderTargets.ts 	# Helper class to create ping-pong RenderTargets
β”œβ”€β”€ utils/
β”‚   β”œβ”€β”€ helps.ts 		# Utility capabilities for bigger system help
β”œβ”€β”€ applications/
β”‚   β”œβ”€β”€ glsl/
β”‚   β”‚   β”œβ”€β”€ base.vert
β”‚   β”‚   β”œβ”€β”€ fluid.vert
β”‚   β”‚   β”œβ”€β”€ advection-manual-filtering.frag
β”‚   β”‚   β”œβ”€β”€ advection.frag
β”‚   β”‚   β”œβ”€β”€ curl.frag
β”‚   β”‚   β”œβ”€β”€ dissipation.frag
β”‚   β”‚   β”œβ”€β”€ divergence.frag
β”‚   β”‚   β”œβ”€β”€ gradient-subtract.frag
β”‚   β”‚   β”œβ”€β”€ strain.frag
β”‚   β”‚   β”œβ”€β”€ splat.frag
β”‚   β”‚   β”œβ”€β”€ vorticity.frag
β”‚   β”œβ”€β”€ AdvectionProgram.ts
β”‚   β”œβ”€β”€ CurlProgram.ts
β”‚   β”œβ”€β”€ DissipationProgram.ts
β”‚   β”œβ”€β”€ DivergenceProgram.ts
β”‚   β”œβ”€β”€ GradientSubtractProgram.ts
β”‚   β”œβ”€β”€ PressureProgram.ts
β”‚   β”œβ”€β”€ SplatProgram.ts
β”‚   β”œβ”€β”€ VorticityProgram.ts

Body-rate impartial simulation

One delicate however necessary element concerned the dissipation parameter controlling how a lot information a render goal retains from one body to the following. A dissipation of 0 fully clears the buffer each body, whereas a worth of 1 means the info by no means fades out.

uniform sampler2D inputBuffer;
uniform float dissipation;

various vec2 vUv;

void major() {
	gl_FragColor = dissipation * texture2D(inputBuffer, vUv);
}

In OGL’s instance, the dissipation parameter retains the identical fixed worth each body. Sadly this breaks on high-refresh-rate shows or when body price varies: on a 120fps show, this multiplier is utilized twice as usually as on a 60fps show, leading to a totally totally different feeling on totally different {hardware}.

To repair this subject, I built-in deltaTime into the calculation, normalizing the dissipation to be frame-rate impartial:

// Earlier than
program.uniforms.dissipation.worth = config.dissipation;

// After - Normalize to 60fps so conduct is constant no matter system refresh price
program.uniforms.dissipation.worth = Math.pow(config.dissipation, deltaTime * 60);

Right here’s a demo of how the FluidSimulation helper can be utilized to render all types of results involving fluid simulations:

WebGL fluid slider

As soon as the FluidSimulation helper class was created, I tackled the implementation of the FluidSliderEffect: a category that runs the fluid simulation and makes use of the density render goal into its personal fragment shader, compositing the fluid movement as a distortion + masks impact over the slider textures.

uniform sampler2D densityBuffer; // FluidSimulation density buffer
uniform sampler2D maps[COUNT]; // Slider textures
uniform int foregroundIndex;
uniform int backgroundIndex;
uniform float foregroundProgress;
uniform float backgroundProgress;
uniform vec2 foregroundDisplacement;
uniform vec2 backgroundDisplacement;

various vec2 vUv;

void major() {
	// Retrieve density buffer information
	vec4 density = texture2D(densityBuffer, vUv);
	// Compute distortion vector	
	vec2 distortion = -density.rg;
	// Compute normalized masks worth
	float masks = clamp((abs(density.r) + abs(density.g)) / 2.0, 0.0, 1.0);

	// Retrieve textures information + Apply distortion
	vec4 foregroundMap = texture2D(maps[foregroundIndex], vUv + distortion * foregroundDisplacement);
	vec4 backgroundMap = texture2D(maps[backgroundIndex], vUv + distortion * backgroundDisplacement);

	// Composite textures + Apply masks
	vec4 foreground = combine(backgroundMap, foregroundMap, foregroundProgress);
	vec4 background = combine(foregroundMap, backgroundMap, backgroundProgress);
	vec4 map = combine(foreground, background, masks);

	gl_FragColor.rgb = map.rgb;
}

OGL x Vue: Constructing reusable and reactive WebGL elements

For structuring the WebGL layer, I took direct inspiration from React Three Fiber / TresJS and their strategy of sharing a renderer context by a present/inject sample. This led to 2 core elements: and . Principally, R3F logic delivered to a Vue + OGL context.

The element creates the DOM canvas component whereas the element receives the canvas as a prop, instantiates the OGL renderer, and shares it with any little one element.




	
		
	

Final step, I created a element liable for retrieving the OGL renderer from the , managing a FluidSliderEffect occasion, listening to mouse occasions and reactively updating the impact occasion on props change.

Then, all I needed to do was use this element with the specified parameters inside an .


	

Regardless that they finally didn’t make the reduce, having a reusable element for the WebGL fluid impact allowed me to simply run some assessments, reminiscent of reusing it with totally different parameters within the undertaking pages footer.

Accessibility

Regardless of what some would possibly assume, accessibility and creativity are usually not mutually unique. Listed here are a couple of steps I took to make this portfolio as accessible as potential with out sacrificing any of the artistic work for many customers.

ARIA attributes: Use of an precise display reader

Past making an attempt to setting the appropriate aria-label / aria-hidden attributes wherever I believed they have been wanted, I examined them utilizing an precise display reader relatively than counting on automated accessibility instruments alone. Automated instruments could be helpful however they don’t essentially catch the complete image, so listening to how a display reader truly navigates by the pages helped me establish accessibility points that I may simply repair afterward.

Keyboard navigation: blur / focus handlers

To make sure keyboard customers get the identical expertise and animations as mouse customers, along with set the proper tabindex attributes and binding some keyboard keys, most CTAs mouseenter / mouseleave handlers have been paired with their focus / blur equivalents, making certain a constant expertise for all customers.



Accessible animation: Diminished movement preferences

Regardless that animation is the half I get pleasure from probably the most, I needed to respect customers’ preferences. So I added a listener to the prefers-reduced-motion media question, updating a reducedMotion Ref to maintain observe of this consumer desire.

const reducedMotion = ref(false);
const reducedMotionMediaQuery = '(prefers-reduced-motion: scale back)';

perform onReducedMotionChange() {
	reducedMotion.worth = window.matchMedia(reducedMotionMediaQuery).matches;
}

window.matchMedia(reducedMotionMediaQuery).addEventListener('change', onReducedMotionChange);
onReducedMotionChange();

I then used this Ref to interchange the house web page WebGL impact with a easy cross-fade between thumbnails and wrapped all scroll-based animations in gsap.matchMedia() circumstances utilizing the identical media question.

No JavaScript, No drawback

As a artistic developer, most of my work includes JavaScript/TypeScript code, nevertheless I needed to push accessibility even additional by making certain that the content material remained accessible to customers with JavaScript disabled.

Since all pages have been already served as static recordsdata by way of Nuxt SSG, a couple of CSS guidelines wrapped in

Thanks for studying

To be sincere, scripting this case research wasn’t a straightforward job, I had a tough time deciding which matter to cowl and extracting probably the most fascinating code snippets whereas preserving issues concise and clear. I attempted to make this behind-the-scenes look as fascinating as potential for everybody: from junior builders to senior ones. So I hope you loved studying it!

Tags: ArnaudfluidGSAPPoweredMotionPortfolioRoccasSystemWebGL
Admin

Admin

Next Post
Information transient: Patch vital SAP, Samsung and chat app flaws now

How AI caught a malicious North Korean insider at Exabeam

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recommended.

The Obtain: A brand new residence beneath the ocean, and cloning pets

The Obtain: A brand new residence beneath the ocean, and cloning pets

November 7, 2025
Understanding Totally different Forms of Search Queries in Conventional and AI-Powered Search

Understanding Totally different Forms of Search Queries in Conventional and AI-Powered Search

April 5, 2025

Trending.

Mistral AI Releases Voxtral TTS: A 4B Open-Weight Streaming Speech Mannequin for Low-Latency Multilingual Voice Era

Mistral AI Releases Voxtral TTS: A 4B Open-Weight Streaming Speech Mannequin for Low-Latency Multilingual Voice Era

March 29, 2026
Exporting a Material Simulation from Blender to an Interactive Three.js Scene

Exporting a Material Simulation from Blender to an Interactive Three.js Scene

August 20, 2025
Moonshot AI Releases π‘¨π’•π’•π’†π’π’•π’Šπ’π’ π‘Ήπ’†π’”π’Šπ’…π’–π’‚π’π’” to Exchange Mounted Residual Mixing with Depth-Sensible Consideration for Higher Scaling in Transformers

Moonshot AI Releases π‘¨π’•π’•π’†π’π’•π’Šπ’π’ π‘Ήπ’†π’”π’Šπ’…π’–π’‚π’π’” to Exchange Mounted Residual Mixing with Depth-Sensible Consideration for Higher Scaling in Transformers

March 16, 2026
Efecto: Constructing Actual-Time ASCII and Dithering Results with WebGL Shaders

Efecto: Constructing Actual-Time ASCII and Dithering Results with WebGL Shaders

January 5, 2026
10 tricks to begin getting ready! β€’ Yoast

10 tricks to begin getting ready! β€’ Yoast

July 21, 2025

AimactGrow

Welcome to AimactGrow, your ultimate source for all things technology! Our mission is to provide insightful, up-to-date content on the latest advancements in technology, coding, gaming, digital marketing, SEO, cybersecurity, and artificial intelligence (AI).

Categories

  • AI
  • Coding
  • Cybersecurity
  • Digital marketing
  • Gaming
  • SEO
  • Technology

Recent News

GEO vs website positioning What Actually Issues for AI Period Search

GEO vs website positioning What Actually Issues for AI Period Search

April 1, 2026
Methods to Construct Pages That Rank

Methods to Construct Pages That Rank

April 1, 2026
  • About Us
  • Privacy Policy
  • Disclaimer
  • Contact Us

Β© 2025 https://blog.aimactgrow.com/ - All Rights Reserved

No Result
View All Result
  • Home
  • Technology
  • AI
  • SEO
  • Coding
  • Gaming
  • Cybersecurity
  • Digital marketing

Β© 2025 https://blog.aimactgrow.com/ - All Rights Reserved