< BACK TO COMPONENTS

Celebrate Holi: HTML, CSS, and JavaScript Animation Tutorial (Happy Holi)

Faraz

By Faraz -

Learn how to bring the vibrant spirit of Holi festival to your website with this HTML, CSS, and JavaScript animation tutorial.


Happy Holi HTML, CSS, and JavaScript Animation Tutorial.webp

Welcome to our tutorial on creating a captivating Holi festival animation using HTML, CSS, and JavaScript! Holi is a vibrant celebration known for its colorful splendor and joyous atmosphere. In this tutorial, we'll show you how to infuse your website with the essence of this festive occasion through a dynamic and visually appealing animation.


With HTML providing the structure, CSS adding the style, and JavaScript bringing the animation to life, you'll learn step-by-step how to create an engaging Holi festival animation that will delight your website visitors. Whether you're a beginner or an experienced web developer, this tutorial will guide you through the process, helping you add a touch of celebration to your online presence.


So, let's dive in and discover how to create a mesmerizing Holi festival animation that will captivate your audience and spread the joy of this colorful festival!

Source Code

Step 1 (HTML Code):

To begin, create the basic structure of your animation using HTML. Start with a canvas element and add the necessary elements to represent the various components of your animation.


Here's an explanation of each part:


1. <!DOCTYPE html>: This declaration specifies the document type and version of HTML being used, which is HTML5 in this case.


2. <html lang="en">: This opening tag marks the beginning of the HTML document. The lang attribute indicates the language of the document, which is English in this case.


3. <head>: This section contains meta-information about the document, such as character encoding, viewport settings, and the page title.


  • <meta charset="UTF-8">: Defines the character encoding of the document as UTF-8, which supports a wide range of characters.
  • <meta http-equiv="X-UA-Compatible" content="IE=edge">: This meta tag ensures compatibility with Internet Explorer by specifying the latest rendering mode.
  • <meta name="viewport" content="width=device-width, initial-scale=1.0">: Sets the viewport properties for responsive design, ensuring the page displays properly on various devices.
  • <title>Holi Animation</title>: Sets the title of the webpage displayed in the browser tab.
  • <link href="https://fonts.googleapis.com/css?family=Great+Vibes" rel="stylesheet">: Links to an external stylesheet hosted on Google Fonts, allowing the webpage to use the "Great Vibes" font.
  • <link rel="stylesheet" href="styles.css">: Links to an external CSS file named "styles.css" for additional styling of the webpage.

4. <body>: This section contains the visible content of the webpage.


  • <canvas></canvas>: This HTML5 element defines an area where graphics, animations, and other visual content can be drawn dynamically using JavaScript.
  • <div>Happy Holi, Mahima</div>: Displays the text "Happy Holi, Mahima" within a <div> element.
  • <p>( WAIT FOR 5 SECOND MOVE YOUR FINGER ON BLANK SPACES AND TAPE ON SCREEN TO CHANGE GULAL COLOR )</p>: Displays a message within a <p> (paragraph) element, providing instructions to the user.
  • <script src="script.js"></script>: Links to an external JavaScript file named "script.js" for scripting functionalities and interactivity on the webpage.

Step 2 (CSS Code):

Next, style your HTML elements using CSS to give them the vibrant colors and visual appeal associated with the Holi festival. Here's an explanation of each part:


1. html, body: Select both the <html> and <body> elements.


  • overflow: hidden;: Hides any content that overflows the viewport, preventing scrolling.
  • padding: 50px;: Adds 50 pixels of padding around the content inside the <body> element.
  • color: #fff;: Sets the text color to white for all content inside the <html> and <body> elements.

2. body: Select only the <body> element.


  • background: Sets a background image for the webpage, with the URL provided. The image is set to be centered, fixed in place, and cover the entire viewport.
  • -webkit-background-size, -moz-background-size, -o-background-size, background-size: Specifies the background size to cover the entire viewport, ensuring the background image fills the entire screen.

3. canvas: Select <canvas> elements.


  • position: fixed;: Positions the <canvas> element relative to the browser window, so it remains in place even when the user scrolls.
  • top: 0; left: 0;: Positions the <canvas> element at the top-left corner of the viewport.
  • width: 100%; height: 100%;: Sets the width and height of the <canvas> element to cover the entire viewport.
  • z-index: 0.5;: Sets the stacking order of the <canvas> element to be below other elements on the page.
  • opacity: 1;: Sets the opacity of the <canvas> element to fully opaque.

4. div: Select <div> elements.


  • position: relative;: Positions the <div> elements relative to their normal position in the document flow.
  • z-index: 1;: Sets the stacking order of the <div> elements to be above the <canvas> element.
  • font-size: 70px;: Sets the font size to 70 pixels for <div> elements.
  • padding: 0;: Removes any padding around the <div> elements.
  • display: table;: Displays <div> elements as table elements, allowing for vertical centering.
  • margin: auto;: Centers the <div> elements horizontally within their containing element.
  • letter-spacing: 2px;: Sets the letter spacing to 2 pixels for <div> elements.
  • font-family: 'Great Vibes', cursive;: Specifies the font family for <div> elements as 'Great Vibes' or cursive font.

5. p: Select <p> elements.


  • display: table;: Displays <p> elements as table elements, allowing for horizontal centering.
  • margin: 20px auto;: Centers the <p> elements horizontally with a 20-pixel margin from the top and bottom.
  • font-family: Arial, Helvetica, sans-serif;: Specifies the font family for <p> elements as Arial, Helvetica, or sans-serif.
  • text-align: center;: Aligns the text within <p> elements to the center.
  • font-size: 8px;: Sets the font size to 8 pixels for <p> elements.
  • letter-spacing: 2px;: Sets the letter spacing to 2 pixels for <p> elements.
  • position: absolute; bottom: 10px; left: 50%;: Positions the <p> elements 10 pixels from the bottom and horizontally centered.
  • z-index: 1;: Sets the stacking order of the <p> elements to be above the <canvas> element.
  • -webkit-transform, -moz-transform, transform: Applies a horizontal translation to center the <p> elements horizontally.
html, body {
  overflow: hidden;
  padding:50px;
  color:#fff;
}

body {
background: url(http://www.nhembram.com/images/happy_holi.jpg) no-repeat center center fixed;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
}

canvas {
position: fixed;
top:0;
left:0;
width:100%;
height:100%;
z-index:0.5;
opacity:1;
}
div{
  position:relative;
  z-index:1;
  font-size:70px;
  padding:0;
  display: table;
  margin:auto;
  letter-spacing:2px;
  font-family: 'Great Vibes', cursive;
}
P{
  display: table;
  margin:20px auto;
font-family:Arial, Helvetica, sans-serif;
  text-align:center;
  font-size:8px; 
  letter-spacing:2px;
  position:absolute;
  bottom:10px;
  left:50%;   
  z-index:1; 
  -webit-transform:translateX(-50%);
  -moz-transform:translateX(-50%);
  transform:translateX(-50%);
} 

Step 3 (JavaScript Code):

Now, it's time to bring your animation to life with JavaScript. Use JavaScript to trigger animations, control timing, and add dynamic effects to your Holi festival animation.


Let's break down the code into sections to understand its functionality:


1. Canvas Initialization:


  • The code starts by getting a reference to a canvas element from the HTML document.
  • It sets the canvas width and height to match its client width and height, ensuring it fills its container.

2. WebGL Context Initialization:


  • It initializes WebGL context on the canvas, first trying to get a WebGL2 context and falling back to WebGL or experimental WebGL if WebGL2 is not available.
  • Parameters such as alpha, depth, stencil, and antialiasing are set for the WebGL context.
  • The clear color for the WebGL context is set to black.

3. Shader Compilation:


  • Several shader programs are defined, including vertex and fragment shaders. These shaders are written in GLSL (OpenGL Shading Language) and compiled for later use in WebGL rendering.

4. Framebuffer Initialization:


  • Framebuffers are created for various purposes, such as storing density, velocity, divergence, pressure, etc. These framebuffers are used to store intermediate results of the fluid simulation.

5. Pointer Handling:


  • Functions for handling mouse and touch events on the canvas are defined. These functions update the state of pointers (representing user interactions) including position, movement, and color.

6. Simulation Update:


  • The Update function is the heart of the fluid simulation. It's called recursively using requestAnimationFrame to continuously update the simulation.
  • Within the Update function, various steps of the fluid simulation are performed, including advection, vorticity, divergence, pressure, and gradient subtraction.
  • The simulation steps involve binding appropriate shaders, setting uniforms, performing computations on textures, and rendering to framebuffers.

7. Splatting:


  • The splat function is used to add splats (or droplets) to the simulation. These splats represent user interactions with the fluid simulation and can change the density and velocity fields.

8. Canvas Resize Handling:


  • The resizeCanvas function ensures that the canvas dimensions are updated when the window is resized. It also updates framebuffer sizes accordingly.

9. Event Listeners:


  • Event listeners are set up to handle mouse and touch interactions on the canvas, triggering splatting and updating pointer states.
'use strict';

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var canvas = document.getElementsByTagName('canvas')[0];
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;

var params = { alpha: false, depth: false, stencil: false, antialias: false };
var gl = canvas.getContext('webgl2', params);
var isWebGL2 = !!gl;
if (!isWebGL2) {
    gl = canvas.getContext('webgl', params) || canvas.getContext('experimental-webgl', params);
}
gl.clearColor(0.0, 0.0, 0.0, 1.0);

var halfFloat = gl.getExtension('OES_texture_half_float');
var support_linear_float = gl.getExtension('OES_texture_half_float_linear');
if (isWebGL2) {
    gl.getExtension('EXT_color_buffer_float');
    support_linear_float = gl.getExtension('OES_texture_float_linear');
}

var TEXTURE_DOWNSAMPLE = 1;
var DENSITY_DISSIPATION = 0.98;
var VELOCITY_DISSIPATION = 0.99;
var SPLAT_RADIUS = 0.005;
var CURL = 30;
var PRESSURE_ITERATIONS = 25;

var GLProgram = function () {
    function GLProgram(vertexShader, fragmentShader) {
        _classCallCheck(this, GLProgram);

        this.uniforms = {};
        this.program = gl.createProgram();

        gl.attachShader(this.program, vertexShader);
        gl.attachShader(this.program, fragmentShader);
        gl.linkProgram(this.program);

        if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) throw gl.getProgramInfoLog(this.program);

        var uniformCount = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
        for (var i = 0; i < uniformCount; i++) {
            var uniformName = gl.getActiveUniform(this.program, i).name;
            this.uniforms[uniformName] = gl.getUniformLocation(this.program, uniformName);
        }
    }

    GLProgram.prototype.bind = function bind() {
        gl.useProgram(this.program);
    };

    return GLProgram;
}();

function compileShader(type, source) {
    var shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);

    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw gl.getShaderInfoLog(shader);

    return shader;
};

var baseVertexShader = compileShader(gl.VERTEX_SHADER, '\n    precision highp float;\n    precision mediump sampler2D;\n\n    attribute vec2 aPosition;\n    varying vec2 vUv;\n    varying vec2 vL;\n    varying vec2 vR;\n    varying vec2 vT;\n    varying vec2 vB;\n    uniform vec2 texelSize;\n\n    void main () {\n        vUv = aPosition * 0.5 + 0.5;\n        vL = vUv - vec2(texelSize.x, 0.0);\n        vR = vUv + vec2(texelSize.x, 0.0);\n        vT = vUv + vec2(0.0, texelSize.y);\n        vB = vUv - vec2(0.0, texelSize.y);\n        gl_Position = vec4(aPosition, 0.0, 1.0);\n    }\n');

var displayShader = compileShader(gl.FRAGMENT_SHADER, '\n    precision highp float;\n    precision mediump sampler2D;\n\n    varying vec2 vUv;\n    varying vec2 vL;\n    varying vec2 vR;\n    varying vec2 vT;\n    varying vec2 vB;\n    uniform sampler2D uTexture;\n\n    void main () {\n        gl_FragColor = texture2D(uTexture, vUv);\n    }\n');

var splatShader = compileShader(gl.FRAGMENT_SHADER, '\n    precision highp float;\n    precision mediump sampler2D;\n\n    varying vec2 vUv;\n    uniform sampler2D uTarget;\n    uniform float aspectRatio;\n    uniform vec3 color;\n    uniform vec2 point;\n    uniform float radius;\n\n    void main () {\n        vec2 p = vUv - point.xy;\n        p.x *= aspectRatio;\n        vec3 splat = exp(-dot(p, p) / radius) * color;\n        vec3 base = texture2D(uTarget, vUv).xyz;\n        gl_FragColor = vec4(base + splat, 1.0);\n    }\n');

var advectionManualFilteringShader = compileShader(gl.FRAGMENT_SHADER, '\n    precision highp float;\n    precision mediump sampler2D;\n\n    varying vec2 vUv;\n    uniform sampler2D uVelocity;\n    uniform sampler2D uSource;\n    uniform vec2 texelSize;\n    uniform float dt;\n    uniform float dissipation;\n\n    vec4 bilerp (in sampler2D sam, in vec2 p) {\n        vec4 st;\n        st.xy = floor(p - 0.5) + 0.5;\n        st.zw = st.xy + 1.0;\n        vec4 uv = st * texelSize.xyxy;\n        vec4 a = texture2D(sam, uv.xy);\n        vec4 b = texture2D(sam, uv.zy);\n        vec4 c = texture2D(sam, uv.xw);\n        vec4 d = texture2D(sam, uv.zw);\n        vec2 f = p - st.xy;\n        return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);\n    }\n\n    void main () {\n        vec2 coord = gl_FragCoord.xy - dt * texture2D(uVelocity, vUv).xy;\n        gl_FragColor = dissipation * bilerp(uSource, coord);\n        gl_FragColor.a = 1.0;\n    }\n');

var advectionShader = compileShader(gl.FRAGMENT_SHADER, '\n    precision highp float;\n    precision mediump sampler2D;\n\n    varying vec2 vUv;\n    uniform sampler2D uVelocity;\n    uniform sampler2D uSource;\n    uniform vec2 texelSize;\n    uniform float dt;\n    uniform float dissipation;\n\n    void main () {\n        vec2 coord = vUv - dt * texture2D(uVelocity, vUv).xy * texelSize;\n        gl_FragColor = dissipation * texture2D(uSource, coord);\n    }\n');

var divergenceShader = compileShader(gl.FRAGMENT_SHADER, '\n    precision highp float;\n    precision mediump sampler2D;\n\n    varying vec2 vUv;\n    varying vec2 vL;\n    varying vec2 vR;\n    varying vec2 vT;\n    varying vec2 vB;\n    uniform sampler2D uVelocity;\n\n    vec2 sampleVelocity (in vec2 uv) {\n        vec2 multiplier = vec2(1.0, 1.0);\n        if (uv.x < 0.0) { uv.x = 0.0; multiplier.x = -1.0; }\n        if (uv.x > 1.0) { uv.x = 1.0; multiplier.x = -1.0; }\n        if (uv.y < 0.0) { uv.y = 0.0; multiplier.y = -1.0; }\n        if (uv.y > 1.0) { uv.y = 1.0; multiplier.y = -1.0; }\n        return multiplier * texture2D(uVelocity, uv).xy;\n    }\n\n    void main () {\n        float L = sampleVelocity(vL).x;\n        float R = sampleVelocity(vR).x;\n        float T = sampleVelocity(vT).y;\n        float B = sampleVelocity(vB).y;\n        float div = 0.5 * (R - L + T - B);\n        gl_FragColor = vec4(div, 0.0, 0.0, 1.0);\n    }\n');

var curlShader = compileShader(gl.FRAGMENT_SHADER, '\n    precision highp float;\n    precision mediump sampler2D;\n\n    varying vec2 vUv;\n    varying vec2 vL;\n    varying vec2 vR;\n    varying vec2 vT;\n    varying vec2 vB;\n    uniform sampler2D uVelocity;\n\n    void main () {\n        float L = texture2D(uVelocity, vL).y;\n        float R = texture2D(uVelocity, vR).y;\n        float T = texture2D(uVelocity, vT).x;\n        float B = texture2D(uVelocity, vB).x;\n        float vorticity = R - L - T + B;\n        gl_FragColor = vec4(vorticity, 0.0, 0.0, 1.0);\n    }\n');

var vorticityShader = compileShader(gl.FRAGMENT_SHADER, '\n    precision highp float;\n    precision mediump sampler2D;\n\n    varying vec2 vUv;\n    varying vec2 vL;\n    varying vec2 vR;\n    varying vec2 vT;\n    varying vec2 vB;\n    uniform sampler2D uVelocity;\n    uniform sampler2D uCurl;\n    uniform float curl;\n    uniform float dt;\n\n    void main () {\n        float L = texture2D(uCurl, vL).y;\n        float R = texture2D(uCurl, vR).y;\n        float T = texture2D(uCurl, vT).x;\n        float B = texture2D(uCurl, vB).x;\n        float C = texture2D(uCurl, vUv).x;\n        vec2 force = vec2(abs(T) - abs(B), abs(R) - abs(L));\n        force *= 1.0 / length(force + 0.00001) * curl * C;\n        vec2 vel = texture2D(uVelocity, vUv).xy;\n        gl_FragColor = vec4(vel + force * dt, 0.0, 1.0);\n    }\n');

var pressureShader = compileShader(gl.FRAGMENT_SHADER, '\n    precision highp float;\n    precision mediump sampler2D;\n\n    varying vec2 vUv;\n    varying vec2 vL;\n    varying vec2 vR;\n    varying vec2 vT;\n    varying vec2 vB;\n    uniform sampler2D uPressure;\n    uniform sampler2D uDivergence;\n\n    vec2 boundary (in vec2 uv) {\n        uv = min(max(uv, 0.0), 1.0);\n        return uv;\n    }\n\n    void main () {\n        float L = texture2D(uPressure, boundary(vL)).x;\n        float R = texture2D(uPressure, boundary(vR)).x;\n        float T = texture2D(uPressure, boundary(vT)).x;\n        float B = texture2D(uPressure, boundary(vB)).x;\n        float C = texture2D(uPressure, vUv).x;\n        float divergence = texture2D(uDivergence, vUv).x;\n        float pressure = (L + R + B + T - divergence) * 0.25;\n        gl_FragColor = vec4(pressure, 0.0, 0.0, 1.0);\n    }\n');

var gradientSubtractShader = compileShader(gl.FRAGMENT_SHADER, '\n    precision highp float;\n    precision mediump sampler2D;\n\n    varying vec2 vUv;\n    varying vec2 vL;\n    varying vec2 vR;\n    varying vec2 vT;\n    varying vec2 vB;\n    uniform sampler2D uPressure;\n    uniform sampler2D uVelocity;\n\n    vec2 boundary (in vec2 uv) {\n        uv = min(max(uv, 0.0), 1.0);\n        return uv;\n    }\n\n    void main () {\n        float L = texture2D(uPressure, boundary(vL)).x;\n        float R = texture2D(uPressure, boundary(vR)).x;\n        float T = texture2D(uPressure, boundary(vT)).x;\n        float B = texture2D(uPressure, boundary(vB)).x;\n        vec2 velocity = texture2D(uVelocity, vUv).xy;\n        velocity.xy -= vec2(R - L, T - B);\n        gl_FragColor = vec4(velocity, 0.0, 1.0);\n    }\n');

var blit = function () {
    gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, -1, 1, 1, 1, 1, -1]), gl.STATIC_DRAW);
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2, 0, 2, 3]), gl.STATIC_DRAW);
    gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
    gl.enableVertexAttribArray(0);

    return function (destination) {
        gl.bindFramebuffer(gl.FRAMEBUFFER, destination);
        gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
    };
}();

function clear(target) {
    gl.bindFramebuffer(gl.FRAMEBUFFER, target);
    gl.clear(gl.COLOR_BUFFER_BIT);
}

function createFBO(texId, w, h, internalFormat, format, type, param) {
    gl.activeTexture(gl.TEXTURE0 + texId);
    var texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, param);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, param);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, w, h, 0, format, type, null);

    var fbo = gl.createFramebuffer();
    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
    gl.viewport(0, 0, w, h);
    gl.clear(gl.COLOR_BUFFER_BIT);

    return [texture, fbo, texId];
}

function createDoubleFBO(texId, w, h, internalFormat, format, type, param) {
    var fbo1 = createFBO(texId, w, h, internalFormat, format, type, param);
    var fbo2 = createFBO(texId + 1, w, h, internalFormat, format, type, param);

    return {
        get first() {
            return fbo1;
        },
        get second() {
            return fbo2;
        },
        swap: function swap() {
            var temp = fbo1;
            fbo1 = fbo2;
            fbo2 = temp;
        }
    };
}

var textureWidth = undefined;
var textureHeight = undefined;
var density = undefined;
var velocity = undefined;
var divergence = undefined;
var curl = undefined;
var pressure = undefined;

function initFramebuffers() {
    textureWidth = gl.drawingBufferWidth >> TEXTURE_DOWNSAMPLE;
    textureHeight = gl.drawingBufferHeight >> TEXTURE_DOWNSAMPLE;

    var internalFormat = isWebGL2 ? gl.RGBA16F : gl.RGBA;
    var internalFormatRG = isWebGL2 ? gl.RG16F : gl.RGBA;
    var formatRG = isWebGL2 ? gl.RG : gl.RGBA;
    var texType = isWebGL2 ? gl.HALF_FLOAT : halfFloat.HALF_FLOAT_OES;

    density = createDoubleFBO(0, textureWidth, textureHeight, internalFormat, gl.RGBA, texType, support_linear_float ? gl.LINEAR : gl.NEAREST);
    velocity = createDoubleFBO(2, textureWidth, textureHeight, internalFormatRG, formatRG, texType, support_linear_float ? gl.LINEAR : gl.NEAREST);
    divergence = createFBO(4, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST);
    curl = createFBO(5, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST);
    pressure = createDoubleFBO(6, textureWidth, textureHeight, internalFormatRG, formatRG, texType, gl.NEAREST);
}

initFramebuffers();

var displayProgram = new GLProgram(baseVertexShader, displayShader);
var splatProgram = new GLProgram(baseVertexShader, splatShader);
var advectionProgram = new GLProgram(baseVertexShader, support_linear_float ? advectionShader : advectionManualFilteringShader);
var divergenceProgram = new GLProgram(baseVertexShader, divergenceShader);
var curlProgram = new GLProgram(baseVertexShader, curlShader);
var vorticityProgram = new GLProgram(baseVertexShader, vorticityShader);
var pressureProgram = new GLProgram(baseVertexShader, pressureShader);
var gradienSubtractProgram = new GLProgram(baseVertexShader, gradientSubtractShader);

function pointerPrototype() {
    this.id = -1;
    this.x = 0;
    this.y = 0;
    this.dx = 0;
    this.dy = 0;
    this.down = false;
    this.moved = false;
    this.color = [30, 0, 300];
}

var pointers = [];
pointers.push(new pointerPrototype());

for (var i = 0; i < 10; i++) {
    var color = [Math.random() * 10, Math.random() * 10, Math.random() * 10];
    var x = canvas.width * Math.random();
    var y = canvas.height * Math.random();
    var dx = 1000 * (Math.random() - 0.5);
    var dy = 1000 * (Math.random() - 0.5);
    splat(x, y, dx, dy, color);
}

var lastTime = Date.now();
Update();

function Update() {
    resizeCanvas();

    var dt = Math.min((Date.now() - lastTime) / 1000, 0.016);
    lastTime = Date.now();

    gl.viewport(0, 0, textureWidth, textureHeight);

    advectionProgram.bind();
    gl.uniform2f(advectionProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
    gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.first[2]);
    gl.uniform1i(advectionProgram.uniforms.uSource, velocity.first[2]);
    gl.uniform1f(advectionProgram.uniforms.dt, dt);
    gl.uniform1f(advectionProgram.uniforms.dissipation, VELOCITY_DISSIPATION);
    blit(velocity.second[1]);
    velocity.swap();

    gl.uniform1i(advectionProgram.uniforms.uVelocity, velocity.first[2]);
    gl.uniform1i(advectionProgram.uniforms.uSource, density.first[2]);
    gl.uniform1f(advectionProgram.uniforms.dissipation, DENSITY_DISSIPATION);
    blit(density.second[1]);
    density.swap();

    for (var i = 0; i < pointers.length; i++) {
        var pointer = pointers[i];
        if (pointer.moved) {
            splat(pointer.x, pointer.y, pointer.dx, pointer.dy, pointer.color);
            pointer.moved = false;
        }
    }

    curlProgram.bind();
    gl.uniform2f(curlProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
    gl.uniform1i(curlProgram.uniforms.uVelocity, velocity.first[2]);
    blit(curl[1]);

    vorticityProgram.bind();
    gl.uniform2f(vorticityProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
    gl.uniform1i(vorticityProgram.uniforms.uVelocity, velocity.first[2]);
    gl.uniform1i(vorticityProgram.uniforms.uCurl, curl[2]);
    gl.uniform1f(vorticityProgram.uniforms.curl, CURL);
    gl.uniform1f(vorticityProgram.uniforms.dt, dt);
    blit(velocity.second[1]);
    velocity.swap();

    divergenceProgram.bind();
    gl.uniform2f(divergenceProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
    gl.uniform1i(divergenceProgram.uniforms.uVelocity, velocity.first[2]);
    blit(divergence[1]);

    clear(pressure.first[1]);
    pressureProgram.bind();
    gl.uniform2f(pressureProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
    gl.uniform1i(pressureProgram.uniforms.uDivergence, divergence[2]);
    for (var i = 0; i < PRESSURE_ITERATIONS; i++) {
        gl.uniform1i(pressureProgram.uniforms.uPressure, pressure.first[2]);
        blit(pressure.second[1]);
        pressure.swap();
    }

    gradienSubtractProgram.bind();
    gl.uniform2f(gradienSubtractProgram.uniforms.texelSize, 1.0 / textureWidth, 1.0 / textureHeight);
    gl.uniform1i(gradienSubtractProgram.uniforms.uPressure, pressure.first[2]);
    gl.uniform1i(gradienSubtractProgram.uniforms.uVelocity, velocity.first[2]);
    blit(velocity.second[1]);
    velocity.swap();

    gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
    displayProgram.bind();
    gl.uniform1i(displayProgram.uniforms.uTexture, density.first[2]);
    blit(null);

    requestAnimationFrame(Update);
}

function splat(x, y, dx, dy, color) {
    splatProgram.bind();
    gl.uniform1i(splatProgram.uniforms.uTarget, velocity.first[2]);
    gl.uniform1f(splatProgram.uniforms.aspectRatio, canvas.width / canvas.height);
    gl.uniform2f(splatProgram.uniforms.point, x / canvas.width, 1.0 - y / canvas.height);
    gl.uniform3f(splatProgram.uniforms.color, dx, -dy, 1.0);
    gl.uniform1f(splatProgram.uniforms.radius, SPLAT_RADIUS);
    blit(velocity.second[1]);
    velocity.swap();

    gl.uniform1i(splatProgram.uniforms.uTarget, density.first[2]);
    gl.uniform3f(splatProgram.uniforms.color, color[0] * 0.3, color[1] * 0.3, color[2] * 0.3);
    blit(density.second[1]);
    density.swap();
}

function resizeCanvas() {
    if (canvas.width != canvas.clientWidth || canvas.height != canvas.clientHeight) {
        canvas.width = canvas.clientWidth;
        canvas.height = canvas.clientHeight;
        initFramebuffers();
    }
}

canvas.addEventListener('mousemove', function (e) {
    pointers[0].moved = pointers[0].down;
    pointers[0].dx = (e.offsetX - pointers[0].x) * 10.0;
    pointers[0].dy = (e.offsetY - pointers[0].y) * 10.0;
    pointers[0].x = e.offsetX;
    pointers[0].y = e.offsetY;
	pointers[0].down = true;
});

canvas.addEventListener('touchmove', function (e) {
    e.preventDefault();
    var touches = e.targetTouches;
    for (var i = 0; i < e.touches.length; i++) {
        var pointer = pointers[i];
        pointer.moved = pointer.down;
        pointer.dx = (touches[i].pageX - pointer.x) * 10.0;
        pointer.dy = (touches[i].pageY - pointer.y) * 10.0;
        pointer.x = touches[i].pageX;
        pointer.y = touches[i].pageY;
    }
}, false);

canvas.addEventListener('mousedown', function () {
    pointers[0].down = true;
    pointers[0].color = [Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2];
});

canvas.addEventListener('touchstart', function (e) {
    var touches = e.targetTouches;
    for (var i = 0; i < touches.length; i++) {
        if (i >= pointers.length) pointers.push(new pointerPrototype());

        pointers[i].id = touches[i].identifier;
        pointers[i].down = true;
        pointers[i].x = touches[i].pageX;
        pointers[i].y = touches[i].pageY;
        pointers[i].color = [Math.random() + 0.2, Math.random() + 0.2, Math.random() + 0.2];
    }
});

window.addEventListener('mouseup', function () {
    pointers[0].down = true;
});

window.addEventListener('touchend', function (e) {
    var touches = e.changedTouches;
    for (var i = 0; i < touches.length; i++) {
        for (var j = 0; j < pointers.length; j++) {
            if (touches[i].identifier == pointers[j].id) pointers[j].down = false;
        }
    }
});

Final Output:

Happy Holi HTML, CSS, and JavaScript Animation Tutorial.gif

Conclusion:

Congratulations on completing our tutorial on creating a stunning Holi festival animation using HTML, CSS, and JavaScript! You've learned how to infuse your website with the vibrant spirit of Holi, bringing color and joy to your online presence.


By following our step-by-step guide, you've mastered the art of structuring HTML elements, styling them with CSS, and animating them with JavaScript. Your newfound skills enable you to craft captivating web animations that engage and delight visitors.


As you continue to explore and experiment with your Holi festival animation, remember to unleash your creativity and tailor the design to reflect your website's unique personality. The possibilities are endless, whether it's adding interactive elements or fine-tuning the visual effects.


Thank you for joining us on this colorful journey! We hope you enjoy showcasing your Holi festival animation and spreading the festive cheer to your audience. Happy coding, and may your website shine bright with the colors of Holi!


Code by: Vikram Kumar

That’s a wrap!

I hope you enjoyed this post. Now, with these examples, you can create your own amazing page.

Did you like it? Let me know in the comments below 🔥 and you can support me by buying me a coffee.

And don’t forget to sign up to our email newsletter so you can get useful content like this sent right to your inbox!

Thanks!
Faraz 😊

End of the article

Subscribe to my Newsletter

Get the latest posts delivered right to your inbox


Latest Post