Gradient Animation
A smooth and elegant background gradient animation that changes the gradient position over time.
Gradients X Animations
<script lang="ts">
import GradientAnimation from './GradientAnimation.svelte';
</script>
<GradientAnimation>
<div
class="pointer-events-none absolute inset-0 z-50 flex items-center justify-center px-4 text-center text-3xl font-bold text-white md:text-4xl lg:text-7xl"
>
<p
class="bg-gradient-to-b from-white/80 to-white/20 bg-clip-text text-transparent drop-shadow-2xl"
>
Gradients X Animations
</p>
</div>
</GradientAnimation>
Installation
Install Dependencies
npm i svelte-motion clsx tailwind-merge
Add util file
src/lib/utils/cn.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
Add these animations and keyframes to your tailwind.config.ts
file
tailwind.config.ts
const config = {
// ... other properties
theme: {
extend: {
animation: {
// ...other animations
first: 'moveVertical 30s ease infinite',
second: 'moveInCircle 20s reverse infinite',
third: 'moveInCircle 40s linear infinite',
fourth: 'moveHorizontal 40s ease infinite',
fifth: 'moveInCircle 20s ease infinite'
},
keyframes: {
// ... other keyframes
moveHorizontal: {
'0%': {
transform: 'translateX(-50%) translateY(-10%)'
},
'50%': {
transform: 'translateX(50%) translateY(10%)'
},
'100%': {
transform: 'translateX(-50%) translateY(-10%)'
}
},
moveInCircle: {
'0%': {
transform: 'rotate(0deg)'
},
'50%': {
transform: 'rotate(180deg)'
},
'100%': {
transform: 'rotate(360deg)'
}
},
moveVertical: {
'0%': {
transform: 'translateY(-50%)'
},
'50%': {
transform: 'translateY(50%)'
},
'100%': {
transform: 'translateY(-50%)'
}
}
}
}
}
};
Copy the source code
src/lib/components/ui/GradientAnimation/GradientAnimation.svelte
<script lang="ts">
import { cn } from '$lib/utils';
import { onMount } from 'svelte';
export let gradientBackgroundStart: string | null = 'rgb(108, 0, 162)';
export let gradientBackgroundEnd: string | null = 'rgb(0, 17, 82)';
export let firstColor: string | null = '18, 113, 255';
export let secondColor: string | null = '221, 74, 255';
export let thirdColor: string | null = '100, 220, 255';
export let fourthColor: string | null = '200, 50, 50';
export let fifthColor: string | null = '180, 180, 50';
export let pointerColor: string | null = '140, 100, 255';
export let size: string | null = '80%';
export let blendingValue: string | null = 'hard-light';
export let className: string | undefined = undefined;
export let interactive: boolean | undefined = true;
export let containerClassName: string | undefined = undefined;
let interactiveRef: HTMLDivElement;
let curX = 0;
let curY = 0;
let tgX = 0;
let tgY = 0;
$: tgX || tgY, updateGradient();
onMount(() => {
document.body.style.setProperty('--gradient-background-start', gradientBackgroundStart);
document.body.style.setProperty('--gradient-background-end', gradientBackgroundEnd);
document.body.style.setProperty('--first-color', firstColor);
document.body.style.setProperty('--second-color', secondColor);
document.body.style.setProperty('--third-color', thirdColor);
document.body.style.setProperty('--fourth-color', fourthColor);
document.body.style.setProperty('--fifth-color', fifthColor);
document.body.style.setProperty('--pointer-color', pointerColor);
document.body.style.setProperty('--size', size);
document.body.style.setProperty('--blending-value', blendingValue);
});
function updateGradient() {
if (!interactiveRef) {
return;
}
curX = curX + (tgX - curX) / 20;
curY = curY + (tgY - curY) / 20;
interactiveRef.style.transform = `translate(${Math.round(curX)}px, ${Math.round(curY)}px)`;
}
const handleMouseMove = (event: MouseEvent) => {
if (interactiveRef) {
const rect = interactiveRef.getBoundingClientRect();
tgX = event.clientX - rect.left;
tgY = event.clientY - rect.top;
}
};
</script>
<div
class={cn(
'relative left-0 top-0 h-96 w-[50vw] overflow-hidden bg-[linear-gradient(40deg,var(--gradient-background-start),var(--gradient-background-end))]',
containerClassName
)}
>
<svg class="hidden">
<defs>
<filter id="blurMe">
<feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" />
<feColorMatrix
in="blur"
mode="matrix"
values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -8"
result="goo"
/>
<feBlend in="SourceGraphic" in2="goo" />
</filter>
</defs>
</svg>
<div class={cn('', className)}><slot /></div>
<div class="gradients-container h-full w-full [filter:url(#blurMe)_blur(40px)]">
<div
class={cn(
`absolute [background:radial-gradient(circle_at_center,_var(--first-color)_0,_var(--first-color)_50%)_no-repeat]`,
`left-[calc(50%-var(--size)/2)] top-[calc(50%-var(--size)/2)] h-[var(--size)] w-[var(--size)] [mix-blend-mode:var(--blending-value)]`,
`[transform-origin:center_center]`,
`animate-first`,
`opacity-100`
)}
></div>
<div
class={cn(
`absolute [background:radial-gradient(circle_at_center,_rgba(var(--second-color),_0.8)_0,_rgba(var(--second-color),_0)_50%)_no-repeat]`,
`left-[calc(50%-var(--size)/2)] top-[calc(50%-var(--size)/2)] h-[var(--size)] w-[var(--size)] [mix-blend-mode:var(--blending-value)]`,
`[transform-origin:calc(50%-400px)]`,
`animate-second`,
`opacity-100`
)}
></div>
<div
class={cn(
`absolute [background:radial-gradient(circle_at_center,_rgba(var(--third-color),_0.8)_0,_rgba(var(--third-color),_0)_50%)_no-repeat]`,
`left-[calc(50%-var(--size)/2)] top-[calc(50%-var(--size)/2)] h-[var(--size)] w-[var(--size)] [mix-blend-mode:var(--blending-value)]`,
`[transform-origin:calc(50%+400px)]`,
`animate-third`,
`opacity-100`
)}
></div>
<div
class={cn(
`absolute [background:radial-gradient(circle_at_center,_rgba(var(--fourth-color),_0.8)_0,_rgba(var(--fourth-color),_0)_50%)_no-repeat]`,
`left-[calc(50%-var(--size)/2)] top-[calc(50%-var(--size)/2)] h-[var(--size)] w-[var(--size)] [mix-blend-mode:var(--blending-value)]`,
`[transform-origin:calc(50%-200px)]`,
`animate-fourth`,
`opacity-70`
)}
></div>
<div
class={cn(
`absolute [background:radial-gradient(circle_at_center,_rgba(var(--fifth-color),_0.8)_0,_rgba(var(--fifth-color),_0)_50%)_no-repeat]`,
`left-[calc(50%-var(--size)/2)] top-[calc(50%-var(--size)/2)] h-[var(--size)] w-[var(--size)] [mix-blend-mode:var(--blending-value)]`,
`[transform-origin:calc(50%-800px)_calc(50%+800px)]`,
`animate-fifth`,
`opacity-100`
)}
></div>
{#if interactive}
<div
bind:this={interactiveRef}
on:mousemove={handleMouseMove}
class={cn(
`absolute [background:radial-gradient(circle_at_center,_rgba(var(--pointer-color),_0.8)_0,_rgba(var(--pointer-color),_0)_50%)_no-repeat]`,
`-left-1/2 -top-1/2 h-full w-full [mix-blend-mode:var(--blending-value)]`,
`opacity-70`
)}
></div>
{/if}
</div>
</div>
src/lib/components/ui/GradientAnimation/index.ts
import GradientAnimation from './GradientAnimation.svelte';
export { GradientAnimation };
Props
GradientAnimation
Prop | Type | Description |
---|---|---|
gradientBackgroundStart | string | null | Default: rgb(108, 0, 162). The starting color of the background gradient, specified as an RGB value. |
gradientBackgroundEnd | string | null | Default: rgb(0, 17, 82). The ending color of the background gradient, specified as an RGB value. |
firstColor | string | null | Default: 18, 113, 255. The first color used in the animation, specified as an RGB value without the rgb tag. |
secondColor | string | null | Default: 221, 74, 255. The second color used in the animation, specified as an RGB value without the rgb tag. |
thirdColor | string | null | Default: 100, 220, 255. The third color used in the animation, specified as an RGB value without the rgb tag. |
fourthColor | string | null | Default: 200, 50, 50. The fourth color used in the animation, specified as an RGB value without the rgb tag. |
fifthColor | string | null | Default: 180, 180, 50. The fifth color used in the animation, specified as an RGB value without the rgb tag. |
pointerColor | string | null | Default: 140, 100, 255. The color of the pointer, specified as an RGB value without the rgb tag. |
size | string | null | Default: 80%. The size of the animated elements. |
blendingValue | string | null | Default: hard-light. The blending mode used for the animated elements. |
className | string | undefined | Additional CSS class for styling. |
interactive | boolean | undefined | Default: true. Determines if the animation is interactive or not. |
containerClassName | string | undefined | Additional CSS class for the container. |