Let’s create 10+ Christmas Tree Animations using HTML, CSS, and JavaScript. This festive project will showcase multiple animated Christmas tree designs with smooth effects and cheerful visuals.
We’ll use:
- HTML to structure the trees and layout
- CSS to style the trees, lights, and animations
- JavaScript to add dynamic effects like blinking lights and movement
This project is perfect for adding a holiday vibe to your website while practicing animations and creativity. Whether you’re a beginner or an experienced developer, these Christmas tree animations are fun to build and great for learning. Let’s spread some holiday cheer with code! 🎄✨
HTML :
This HTML file sets up a basic web page for a generative CSS Christmas tree animation. The <!DOCTYPE html> and <html lang="en"> define a modern HTML5 document, while the <head> includes character encoding, the page title, and links to a normalize CSS file for consistent styling across browsers and a custom style.css for design. The <body> itself has no visible HTML because everything is created using JavaScript, and the script.js file is loaded as a module, meaning it can use modern JS features and generates the entire visual effect dynamically.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>10+ Christmas Tree Animation 🎄 @coding.stella</title> <link rel="stylesheet" href="https://public.codepenassets.com/css/normalize-5.0.0.min.css"> <link rel="stylesheet" href="./style.css"> </head> <body> <script type="module" src="./script.js"></script> </body> </html>
CSS :
This CSS creates a 3D animated Christmas tree made of glowing lights and a rotating star. The body sets a dark background and 3D perspective, while the .tree spins continuously using a rotateY animation. Each .tree__light is a small circular bulb positioned in 3D space using CSS variables, appearing one by one and flashing through different colors with glow effects. The star at the top is drawn with strokes, animates its outline and fill, and rotates smoothly. Overall, CSS variables plus keyframe animations handle positioning, timing, color changes, and 3D rotation to generate the full animated tree without extra HTML.
* {
box-sizing: border-box;
transform-style: preserve-3d;
}
body {
background: #14191f;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
perspective: 100vmin;
transform-style: preserve-3d;
}
.tree {
position: relative;
height: 50vmin;
width: 25vmin;
transform-style: preserve-3d;
-webkit-animation: spin calc(var(--spin-speed, 2) * 1s) infinite linear var(--direction, normal);
animation: spin calc(var(--spin-speed, 2) * 1s) infinite linear var(--direction, normal);
}
.tree__light {
transform-style: preserve-3d;
position: absolute;
aspect-ratio: 1;
width: calc(var(--light-size, 1) * 1vmin);
border-radius: 50%;
-webkit-animation: flash calc(var(--speed) * 1s) calc(var(--delay) * 1s) infinite var(--flash-timing-function, steps(1)), appear calc(var(--appear-speed, 0.1) * 1s) calc((var(--appear) * var(--appear-delay-step, 0.05)) * 1s) both;
animation: flash calc(var(--speed) * 1s) calc(var(--delay) * 1s) infinite var(--flash-timing-function, steps(1)), appear calc(var(--appear-speed, 0.1) * 1s) calc((var(--appear) * var(--appear-delay-step, 0.05)) * 1s) both;
left: 50%;
transform: translate(-50%, 50%) rotateY(calc(var(--rotate, 0) * 1deg)) translate3d(0, 0, calc(var(--radius, 0) * 1vmin));
bottom: calc(var(--y, 0) * 1%);
}
.tree__star {
stroke-width: 1.25vmin;
stroke-linejoin: round;
stroke-linecap: round;
stroke: #f5e0a3;
filter: drop-shadow(0 0 2vmin #fcf1cf);
height: 5vmin;
width: 5vmin;
overflow: visible !important;
bottom: calc(100% + (var(--light-size) * 0.5vmin));
left: 50%;
transform: translate(-50%, 0) rotateY(20deg);
position: absolute;
stroke-dasharray: 1000 1000;
fill: none;
-webkit-animation: stroke 1s calc(((var(--delay) * 0.95) * var(--appear-delay-step, 0.05)) * 1s) both, fill 1s calc(((var(--delay) + 10) * var(--appear-delay-step, 0.05)) * 1s) both, star-spin calc(var(--spin-speed, 2) * 1s) infinite linear var(--star-direction, normal);
animation: stroke 1s calc(((var(--delay) * 0.95) * var(--appear-delay-step, 0.05)) * 1s) both, fill 1s calc(((var(--delay) + 10) * var(--appear-delay-step, 0.05)) * 1s) both, star-spin calc(var(--spin-speed, 2) * 1s) infinite linear var(--star-direction, normal);
}
@-webkit-keyframes star-spin {
to {
transform: translate(-50%, 0) rotateY(calc(20deg + (var(--counter-star, 0) * 360deg)));
}
}
@keyframes star-spin {
to {
transform: translate(-50%, 0) rotateY(calc(20deg + (var(--counter-star, 0) * 360deg)));
}
}
@-webkit-keyframes fill {
to {
fill: hsla(45, 80%, 80%, var(--fill-star, 0));
}
}
@keyframes fill {
to {
fill: hsla(45, 80%, 80%, var(--fill-star, 0));
}
}
@-webkit-keyframes stroke {
from {
stroke-dashoffset: -1000;
}
}
@keyframes stroke {
from {
stroke-dashoffset: -1000;
}
}
@-webkit-keyframes spin {
to {
transform: rotateY(360deg);
}
}
@keyframes spin {
to {
transform: rotateY(360deg);
}
}
@-webkit-keyframes appear {
from {
opacity: 0;
}
}
@keyframes appear {
from {
opacity: 0;
}
}
@-webkit-keyframes flash {
0%,
100% {
background: var(--color-one, #f00);
box-shadow: 0 0 calc(var(--light-size) * 0.5vmin) var(--color-one);
}
20% {
background: var(--color-two, #fff);
box-shadow: 0 0 calc(var(--light-size) * 0.5vmin) var(--color-two);
}
40% {
background: var(--color-three, #f00);
box-shadow: 0 0 calc(var(--light-size) * 0.5vmin) var(--color-three);
}
60% {
background: var(--color-four, #fff);
box-shadow: 0 0 calc(var(--light-size) * 0.5vmin) var(--color-four);
}
80% {
background: var(--color-five, #f00);
box-shadow: 0 0 calc(var(--light-size) * 0.5vmin) var(--color-five);
}
}
@keyframes flash {
0%,
100% {
background: var(--color-one, #f00);
box-shadow: 0 0 calc(var(--light-size) * 0.5vmin) var(--color-one);
}
20% {
background: var(--color-two, #fff);
box-shadow: 0 0 calc(var(--light-size) * 0.5vmin) var(--color-two);
}
40% {
background: var(--color-three, #f00);
box-shadow: 0 0 calc(var(--light-size) * 0.5vmin) var(--color-three);
}
60% {
background: var(--color-four, #fff);
box-shadow: 0 0 calc(var(--light-size) * 0.5vmin) var(--color-four);
}
80% {
background: var(--color-five, #f00);
box-shadow: 0 0 calc(var(--light-size) * 0.5vmin) var(--color-five);
}
}
JavaScript :
This JavaScript builds and controls a fully generative 3D Christmas tree using CSS variables and DOM elements. It randomly creates a configuration for lights, colors, rotation, speed, and effects, then renders the tree by programmatically adding light divs and an SVG star to the page. Each light gets its own position, delay, flash speed, and color, creating the spiral effect. The dat.GUI panel lets you tweak values like light count, size, colors, spin direction, and animation speed in real time, while updates are applied by syncing config values to CSS custom properties.
import Color from 'https://cdn.skypack.dev/color';
import { GUI } from 'https://cdn.skypack.dev/dat.gui';
const CTRL = new GUI();
const ASSIGN_COLORS = CONFIG => {
['one', 'two', 'three', 'four', 'five'].forEach(light => {
CONFIG[`color-${light}`] = Color.rgb(Math.random() * 255, Math.random() * 255, Math.random() * 255).hex();
});
};
const GEN_CONFIG = () => ({
'lights': Math.random() * (100 - 30) + 30,
'light-size': Math.random() * 2 + 0.5,
'rotate': Math.floor(Math.random() * 10 + 5) * 360,
'radius': Math.random() * (25 - 5) + 5,
'height': 50,
'fill-star': Math.random() > 0.5,
'flash-speed-min': Math.random() * 2,
'flash-speed-max': Math.random() * 10 + 2,
'direction': Math.random() > 0.5,
'counter-star': Math.random() > 0.5,
'flash-steps': 1,
'spin-speed': Math.random() * 5 + 1,
'appear-speed': Math.random(),
'appear-delay-step': 0.05,
'color-one': '#ff0000',
'color-two': '#ffffff',
'color-three': '#ff0000',
'color-four': '#ffffff',
'color-five': '#ff0000'
});
let CONFIG = {
random: () => {
ASSIGN_COLORS(CONFIG);
CTRL.updateDisplay();
UPDATE();
},
render: () => RENDER(),
generate: () => {
const NEW_CONFIG = { ...CONFIG, ...GEN_CONFIG() };
for (const key of Object.keys(CONFIG)) {
CONFIG[key] = NEW_CONFIG[key];
}
ASSIGN_COLORS(CONFIG);
CTRL.updateDisplay();
RENDER();
}
};
CONFIG = { ...CONFIG, ...GEN_CONFIG(), 'appear-speed': 0.5, 'appear-delay-step': 0.05 };
const WHITELIST = ['spin-speed', 'counter-star', 'light-size', 'color-one', 'color-two', 'color-three', 'color-four', 'color-five', 'fill-star', 'appear-speed', 'appear-delay-step', 'flash-steps'];
const UPDATE = () => {
for (const key of Object.keys(CONFIG)) {
if (WHITELIST.includes(key))
document.documentElement.style.setProperty(`--${key}`, typeof CONFIG[key] === 'boolean' ? CONFIG[key] ? 1 : 0 : CONFIG[key]);
if (key === 'flash-steps')
if (CONFIG[key] === 0) document.documentElement.style.setProperty('--flash-timing-function', 'ease-in-out'); else
document.documentElement.style.setProperty('--flash-timing-function', `steps(${CONFIG[key]})`);
if (key === 'direction') {
document.documentElement.style.setProperty('--direction', CONFIG[key] ? 'reverse' : 'normal');
document.documentElement.style.setProperty('--star-direction', CONFIG[key] ? 'normal' : 'reverse');
}
}
};
const RENDER = () => {
UPDATE();
if (CONFIG['flash-speed-min'] > CONFIG['flash-speed-max']) {
CONFIG['flash-speed-max'] = CONFIG['flash-speed-min'];
CTRL.updateDisplay();
}
const FRAG = new DocumentFragment();
const TREE = document.createElement('div');
TREE.className = 'tree';
for (let l = 0; l < CONFIG.lights; l++) {
// Create a light and add it to the tree.
const LIGHT = document.createElement('div');
LIGHT.className = 'tree__light';
LIGHT.style.setProperty('--appear', l);
LIGHT.style.setProperty('--delay', -l);
LIGHT.style.setProperty('--speed', Math.random() * (CONFIG['flash-speed-max'] - CONFIG['flash-speed-min']) + CONFIG['flash-speed-min']);
LIGHT.style.setProperty('--y', 100 / CONFIG.lights * l);
LIGHT.style.setProperty('--rotate', CONFIG.rotate / CONFIG.lights * (CONFIG.lights - l));
LIGHT.style.setProperty('--radius', CONFIG.radius / CONFIG.lights * (CONFIG.lights - l));
TREE.appendChild(LIGHT);
}
const STAR = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
STAR.setAttribute('class', 'tree__star');
STAR.setAttribute('viewBox', "0 0 113.32 108.44");
STAR.style.setProperty('--delay', CONFIG.lights);
const STAR_PATH = document.createElementNS(STAR.namespaceURI, 'path');
STAR_PATH.setAttribute('d', 'M90.19 104.33L57.12 87.38 24.4 105l5.91-36.69L3.44 42.65l36.72-5.72 16.1-33.5L73.06 36.6l36.83 4.97-26.35 26.21z');
STAR.appendChild(STAR_PATH);
TREE.appendChild(STAR);
FRAG.appendChild(TREE);
const CURRENT_TREE = document.querySelector('.tree');
if (CURRENT_TREE)
CURRENT_TREE.parentNode.replaceChild(FRAG, CURRENT_TREE); else
document.body.appendChild(FRAG);
};
const CFG = CTRL.addFolder('Tree Config');
CFG.add(CONFIG, 'lights', 10, 200, 1).onChange(RENDER).name('Lights');
CFG.add(CONFIG, 'light-size', 0.5, 3, 0.1).onChange(UPDATE).name('Light Size');
CFG.add(CONFIG, 'rotate', 360, 5400, 10).onChange(RENDER).name('Distribution (deg)');
CFG.add(CONFIG, 'radius', 4, 50, 0.1).onChange(RENDER).name('Radius (vmin)');
CFG.add(CONFIG, 'fill-star').onChange(UPDATE).name('Fill Star');
CFG.add(CONFIG, 'counter-star').onChange(UPDATE).name('Counter Star');
CFG.add(CONFIG, 'direction').onChange(UPDATE).name('Reverse Spin');
CFG.add(CONFIG, 'spin-speed', 0.5, 10, 0.1).onChange(UPDATE).name('Spin Speed');
const CLR = CTRL.addFolder('Light Colors');
CLR.addColor(CONFIG, 'color-one').onChange(UPDATE).name('One');
CLR.addColor(CONFIG, 'color-two').onChange(UPDATE).name('Two');
CLR.addColor(CONFIG, 'color-three').onChange(UPDATE).name('Three');
CLR.addColor(CONFIG, 'color-four').onChange(UPDATE).name('Four');
CLR.addColor(CONFIG, 'color-five').onChange(UPDATE).name('Five');
CLR.add(CONFIG, 'random').name('Randomise');
const FLS = CTRL.addFolder('Light Speed (Flash)');
FLS.add(CONFIG, 'flash-speed-min', 0.1, 20, 0.1).onChange(RENDER).name('Min');
FLS.add(CONFIG, 'flash-speed-max', 0.1, 20, 0.1).onChange(RENDER).name('Max');
FLS.add(CONFIG, 'flash-steps', 0, 10, 1).onChange(UPDATE).name('Steps');
const APP = CTRL.addFolder('Light Speed (Appear)');
APP.add(CONFIG, 'appear-speed', 0.1, 2, 0.01).onChange(RENDER).name('Speed');
APP.add(CONFIG, 'appear-delay-step', 0.01, 0.2, 0.01).onChange(RENDER).name('Delay Step');
CTRL.add(CONFIG, 'generate').name('Generate New');
CTRL.add(CONFIG, 'render').name('Render');
RENDER();
In conclusion, creating 10+ Christmas Tree Animations using HTML, CSS, and JavaScript is a fun and creative way to practice animations and visual effects. With simple code and festive ideas, you can bring holiday magic to your web projects. Keep experimenting and make your designs shine this Christmas! 🎅🎄
If your project has problems, don’t worry. Just click to download the source code and face your coding challenges with excitement. Have fun coding!
