Snowflake Effect

Documentation
Webflow
Code
Setup: External Scripts
External Scripts in Webflow
Make sure to always put the External Scripts before the Javascript step of the resource.
In this video you learn where to put these in your Webflow project? Or how to include a paid GSAP Club plugin in your project?
HTML
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.1/dist/gsap.min.js"></script>Step 1: Copy structure to Webflow
Copy structure to Webflow
In the video below we described how you can copy + paste the structure of this resource to your Webflow project.
Copy to Webflow
Webflow structure is not required for this resource.
Step 1: Add HTML
HTML
<div data-snowflake-container data-strength="4" data-infinite="true" class="snowflake-container">
<div data-snowflake class="snowflake-el hidden"></div>
</div>HTML structure is not required for this resource.
Step 2: Add CSS
CSS
.snowflake-container {
z-index: 100;
pointer-events: none;
width: 100%;
height: 100vh;
position: fixed;
inset: 0%;
overflow: hidden;
}
.snowflake-el {
aspect-ratio: 1 / 1.15;
background-image: url('https://cdn.prod.website-files.com/6941599afc835c41f83ca9ca/69416a9de9533b6332c72b9e_snowflake.avif');
background-position: 50%;
background-repeat: no-repeat;
background-size: contain;
width: 1.5em;
position: absolute;
}
.snowflake-el.hidden {
opacity: 0;
}Step 2: Add custom Javascript
Custom Javascript in Webflow
In this video, Ilja gives you some guidance about using JavaScript in Webflow:
Step 2: Add Javascript
Step 3: Add Javascript
Javascript
function initSnowflakeEffect() {
const container = document.querySelector("[data-snowflake-container]");
if (!container) return;
// Prevent double init
if (container.dataset.snowRunning === "true") return;
container.dataset.snowRunning = "true";
const templates = Array.from(container.querySelectorAll("[data-snowflake]"));
if (!templates.length) {
console.warn("initSnowflakeEffect: No [data-snowflake] element found");
container.dataset.snowRunning = "false";
return;
}
const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
const strength = clamp(parseInt(container.dataset.strength ?? "4", 10) || 0, 0, 10);
const infinite = (container.dataset.infinite ?? "true") !== "false";
// Configuration
const durationMin = 8;
const durationMax = 12;
const scaleMin = 0.3;
const scaleMax = 1.2;
const opacityMin = 0.2;
const opacityMax = 1.0;
// Strength affects how many + how fast
const spawnRate = gsap.utils.mapRange(0, 10, 0.15, 5.0, strength); // flakes/sec
const maxOnScreen = Math.round(gsap.utils.mapRange(0, 10, 12, 180, strength));
const burstCount = Math.round(gsap.utils.mapRange(0, 10, 10, 160, strength)); // if infinite=false
let running = true;
let activeCount = 0;
let scheduledCall = null;
let burstSpawned = 0;
const getHeight = () => container.clientHeight || window.innerHeight;
function stop(removeExisting = true) {
running = false;
container.dataset.snowRunning = "false";
if (scheduledCall) scheduledCall.kill();
if (removeExisting) {
container.querySelectorAll(".snowflake-el.is-spawned").forEach(el => el.remove());
activeCount = 0;
}
}
function cleanupFlake(flake, tweens) {
tweens.forEach(t => t && t.kill());
flake.remove();
activeCount--;
// In one-burst mode: when everything spawned AND are all done, stop.
if (!infinite && burstSpawned >= burstCount && activeCount <= 0) {
stop(false);
}
}
function spawnOne() {
if (!running) return;
if (activeCount >= maxOnScreen) return;
const tpl = templates[Math.floor(Math.random() * templates.length)];
const flake = tpl.cloneNode(true);
flake.classList.remove("hidden");
flake.classList.add("is-spawned");
flake.style.willChange = "transform, opacity";
const scale = gsap.utils.random(scaleMin, scaleMax, 0.001);
const duration = gsap.utils.random(durationMin, durationMax, 0.001);
// Wave-ish drift
const baseSway = gsap.utils.random(12, 60, 0.1);
const sway = baseSway * (0.6 + strength / 20);
// Choose left so that drifting doesn't bias distribution (keep within bounds)
const containerWidth = container.clientWidth || window.innerWidth;
const swayPct = (sway / containerWidth) * 100; // sway in % of container width
const padPct = Math.min(20, Math.max(0, swayPct)); // clamp padding
flake.style.left = `${gsap.utils.random(padPct, 100 - padPct, 0.1)}%`;
flake.style.opacity = gsap.utils.random(opacityMin, opacityMax, 0.001);
container.appendChild(flake);
activeCount++;
const h = getHeight();
const startY = -gsap.utils.random(30, Math.min(180, h * 0.25), 1);
const endY = h + gsap.utils.random(30, Math.min(220, h * 0.35), 1);
const xStart = gsap.utils.random(-sway, sway, 0.1);
const xEnd = -xStart;
const swayDur = gsap.utils.random(1.6, 3.8, 0.001);
// Gentle rotation wobble
const rotStart = gsap.utils.random(-12, 12, 0.1);
const rotEnd = gsap.utils.random(-28, 28, 0.1);
const rotDur = gsap.utils.random(2.2, 5.0, 0.001);
let fallTween, swayTween, rotTween, fadeTween;
fallTween = gsap.fromTo(
flake,
{ y: startY, xPercent: -50, scale, rotate: rotStart },
{
y: endY,
xPercent: -50,
ease: "none",
duration,
onComplete: () => cleanupFlake(flake, [fallTween, swayTween, rotTween, fadeTween]),
}
);
const swayRepeats = Math.max(1, Math.floor(duration / swayDur));
swayTween = gsap.fromTo(
flake,
{ x: xStart },
{ x: xEnd, ease: "sine.inOut", duration: swayDur, repeat: swayRepeats, yoyo: true }
);
const rotRepeats = Math.max(1, Math.floor(duration / rotDur));
rotTween = gsap.fromTo(
flake,
{ rotate: rotStart },
{ rotate: rotEnd, ease: "sine.inOut", duration: rotDur, repeat: rotRepeats, yoyo: true }
);
fadeTween = gsap.to(flake, {
opacity: 0,
duration: 1,
ease: "power1.out",
delay: Math.max(0, duration - 1),
});
}
function scheduleNext() {
if (!running) return;
const avgGap = 1 / spawnRate;
const nextIn = gsap.utils.random(avgGap * 0.6, avgGap * 1.4, 0.001);
scheduledCall = gsap.delayedCall(nextIn, () => {
spawnOne();
scheduleNext();
});
}
if (infinite) {
const seedCount = Math.round(gsap.utils.mapRange(0, 10, 6, 60, strength));
for (let i = 0; i < seedCount; i++) {
gsap.delayedCall(gsap.utils.random(0, 1.2, 0.001), spawnOne);
}
scheduleNext();
} else {
for (let i = 0; i < burstCount; i++) {
burstSpawned++;
gsap.delayedCall(gsap.utils.random(0, 2.0, 0.001), spawnOne);
}
}
}
document.addEventListener("DOMContentLoaded", () => {
initSnowflakeEffect();
});Step 3: Add custom CSS
Step 2: Add custom CSS
Custom CSS in Webflow
Curious about where to put custom CSS in Webflow? Ilja explains it in the below video:
CSS
Documentation
Container
Add a wrapper using [data-snowflake-container] to define the bounds where snowflakes spawn and animate.
Snowflake
Place a single 'template' element using [data-snowflake] inside the container so the script can clone it for each spawned flake.
Strength
Use [data-strength="4"] (default 4, clamped between 0–10) to control overall intensity by scaling spawn rate, on-screen limit, and seeding.
Infinite
Set [data-infinite="true"] (default true) to keep spawning continuously, or set it to false to spawn one burst and stop once all flakes have finished.
Duration
Adjust durationMin and durationMax in the script to define how long each snowflake takes to fall from top to bottom, with values randomly chosen per flake.
Scale
Adjust scaleMin and scaleMax in the script to control the random size range applied to each snowflake as it spawns.
Opacity
Adjust opacityMin and opacityMax in the script to control the random starting opacity range of each snowflake.
Spawn Rate
The internal spawnRate value maps [data-strength] to flakes-per-second, determining how frequently new snowflakes are spawned.
Max On Screen
The internal maxOnScreen limit maps [data-strength] to a cap on how many active snowflakes can exist at once.
Burst Count
The internal burstCount maps [data-strength] to the total number of flakes spawned when [data-infinite="false"] is used.
Sway
The internal sway calculation increases horizontal drift based on [data-strength], creating wider left-right motion at higher values.
Seed
When [data-infinite="true"] is enabled, the internal seed count spawns an initial batch of snowflakes on load, scaled by [data-strength].
Resource details
Last updated
December 16, 2025
Category
Gimmicks
Need help?
Join Slack





































































































































