Tutorials Books Videos Forums

Change the theme! Search!
Rambo ftw!

Customize Theme


Color

Background


Done

Table of Contents

Advanced Glitch Effect with Sound

by kirupa   |   filed under Coding Exercises

Because the canvas operates at such a low level, it gives us the ability to directly manipulate individual pixels on images. This allows us to create some really interesting effects, like the following glitch effect that also plays a static-sounding hissing noise:

By default, you won't hear the sound when the page loads. That is due to a browser feature to prevent our pages from automatically blaring random stuff into our speakers. To hear the sound, click or tap on the above image. If the sound gets too annoying after a while (like, let's say in 4 seconds), just click or tap the image again to toggle the sound on or off.

Your coding exercise here is to recreate the above effect. It's definitely a bit involved, so a helpful tip would be to break the functionalilty up into smaller sections.

Onwards!

Hint: Some Learning Resources and Getting Help

The following tutorials may provide some helpful tips and techniques to help you with this exercise: Playing Sounds Using the Audio Element, Playing a Randomly Generated Sound, and Drawing Images on the Canvas.

A big part of this effect revolves around manipulating pixels. Take a look at the following example:

Click on the various buttons to change the appearance of the images. The full source code for this example can be seen here, so take a look at that for some inspiration that can help you create the glitch effect.

We want to make this a fun learning activity. If you are stuck and need help, please ask on the forums. Please explain your problem in detail and one of the many helpful forum members will help you out.

Getting Your Badge

Once you have completed this exercise, you have earned the tremendous bragworthy privilege of adding the following badge to your collection:

To claim it, head to the forums and respond in the Advanced Glitch Effect topic. Be sure to include a link to your solution or insert a copy of your HTML/CSS/JS in the body of the message. Once you have created your topic, Kirupa will give you a virtual high-five and ensure this badge is added to your list of assigned badges.

One Possible Solution

As with all coding exercises, there are a billion ways to solve them. Below is the code behind the example we saw at the beginning:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Glitch Image Animation with Sound</title>
  <style> 


    body {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      margin: 0;
      background-color: #000;
    }

    canvas {
      max-width: 500px;
      max-height: 500px;
      box-shadow: 0 0 20px rgba(255, 255, 255, 0.2);
      cursor: pointer;
    }
  </style>
</head>
<body>
  <canvas id="glitchCanvas"></canvas>

  <script>
    // Class definition for creating the advanced glitch animation
    class AdvancedGlitchAnimation {
      /**
       * Constructor for AdvancedGlitchAnimation.
       * @param {string} imageUrl - The URL of the image to glitch.
       * @param {string} canvasId - The ID of the canvas element.
       */
      constructor(imageUrl, canvasId) {
        // Get the canvas element from the DOM
        this.canvas = document.getElementById(canvasId);
        // Get the 2D rendering context for the canvas
        this.ctx = this.canvas.getContext('2d');
        // Create a new Image object
        this.image = new Image();
        // Set the source of the image to the provided URL
        this.image.src = imageUrl;

        // Set up the AudioContext for creating and controlling sound
        this.audioContext = new (window.AudioContext || window.webkitAudioContext)();

        // Define the channels (colors) to be affected by glitching
        this.glitchChannels = ['red', 'green', 'blue'];
        // Define the types of glitch effects that can occur
        this.glitchEffects = ['shift', 'slice', 'distort'];

        /**
         * @type {number}
         * @description Glitch intensity, a value between 0 and 1.
         */
        this.glitchIntensity = 0.5;
        /**
         * @type {number}
         * @description Glitch frequency, a value between 0 and 1, where higher is more frequent.
         */
        this.glitchFrequency = 0.1;

        // When the image has loaded, set the canvas dimensions and start the animation
        this.image.onload = () => {
          this.canvas.width = this.image.width;
          this.canvas.height = this.image.height;
          this.startAnimation();
        };
      }

      /**
       * Creates a static glitch sound effect.
       * @param {number} intensity - The intensity of the glitch effect.
       */
      createStaticGlitch(intensity) {
        // Calculate the buffer size needed for 100 milliseconds of static sound
        const bufferSize = this.audioContext.sampleRate * 0.1; // 0.1 seconds (100 milliseconds) of static
        // Create an audio buffer with one channel, calculated buffer size, and audio context's sample rate
        const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
        // Get the raw audio data for the buffer to manipulate
        const data = buffer.getChannelData(0);

        // Loop through each sample in the buffer and fill it with random noise values
        for (let i = 0; i < bufferSize; i++) {
          // Generate a random number between -1 and 1
          // This creates white noise, a random signal with equal intensity at different frequencies
          data[i] = Math.random() * 2 - 1;
        }

        // Create a buffer source node to play the audio buffer
        const noiseSource = this.audioContext.createBufferSource();
        // Assign the buffer to the noise source
        noiseSource.buffer = buffer;

        // Create a gain node to control the volume
        const gainNode = this.audioContext.createGain();
        // Immediately set the gain to 0 to start silently
        gainNode.gain.setValueAtTime(0, this.audioContext.currentTime);

        // Generate a random volume variation between 0.5 and 1 for dynamic volume
        const randomMultiplier = (Math.random() * 0.5) + 0.5;
        // Calculate the gain value based on intensity and the random multiplier
        // Reduced intensity for static sound for a subtler effect
        const gainValue = intensity * 0.05 * randomMultiplier;
        // Immediately set the gain to the calculated gainValue to control the initial volume
        gainNode.gain.setValueAtTime(gainValue, this.audioContext.currentTime);
        // Fade out the static sound by setting the gain back to 0 after 100 milliseconds
        gainNode.gain.setValueAtTime(0, this.audioContext.currentTime + 0.1); // Fade out after 100 milliseconds

        // Connect the noise source to the gain node and then to the audio destination
        noiseSource.connect(gainNode);
        // Connect the gain node to the audio destination, which is the device's speakers
        gainNode.connect(this.audioContext.destination);

        // Start playing the noise
        noiseSource.start();
        // Stop playing after 100ms, matching the duration of the buffer
        noiseSource.stop(this.audioContext.currentTime + 0.1);
      }

      /**
       * Applies a random glitch effect to the image.
       */
      randomGlitch() {
        // Get local references to the canvas and image objects
        const canvas = this.canvas;
        const img = this.image;

        // Clear the canvas before drawing the next frame
        this.ctx.clearRect(0, 0, canvas.width, canvas.height);
        // Draw the original image onto the canvas as a base
        this.ctx.drawImage(img, 0, 0);

        // Determine whether to apply a glitch effect based on the glitch frequency
        if (Math.random() < this.glitchFrequency) {
          // Store the current glitch intensity for use in effects
          const intensity = this.glitchIntensity;
          // Select a random effect from the list of available glitch effects
          const effect = this.glitchEffects[Math.floor(Math.random() * this.glitchEffects.length)];

          // Create a static glitch sound effect based on the current intensity
          this.createStaticGlitch(intensity);

          // Apply the selected glitch effect based on the 'effect' variable
          switch (effect) {
            case 'shift':
              this.colorShiftGlitch(intensity);
              break;
            case 'slice':
              this.imageSliceGlitch(intensity);
              break;
            case 'distort':
              this.pixelDistortGlitch(intensity);
              break;
          }
        } else {
          // If no glitch is applied, redraw the original image without changes
          this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
          this.ctx.drawImage(this.image, 0, 0);
        }
      }

      /**
       * Applies a color shift glitch effect by randomly shifting color channels.
       * @param {number} intensity - The intensity of the color shift.
       */
      colorShiftGlitch(intensity) {
        const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
        const data = imageData.data;

        const maxOffset = Math.floor(100 * intensity);

        for (let i = 0; i < data.length; i += 4) {
          if (Math.random() < intensity) {
            const channel = this.glitchChannels[Math.floor(Math.random() * this.glitchChannels.length)];
            const offset = Math.floor(Math.random() * maxOffset * 2 - maxOffset);

            switch (channel) {
              case 'red':
                // Adjust the red color channel value within the valid range
                data[i] = Math.max(0, Math.min(255, data[i] + offset));
                break;
              case 'green':
                // Adjust the green color channel value within the valid range
                data[i + 1] = Math.max(0, Math.min(255, data[i + 1] + offset));
                break;
              case 'blue':
                // Adjust the blue color channel value within the valid range
                data[i + 2] = Math.max(0, Math.min(255, data[i + 2] + offset));
                break;
            }
          }
        }
        // Put the modified image data back onto the canvas
        this.ctx.putImageData(imageData, 0, 0);
      }
      
      // Method to apply an image slice glitch effect
      // This method randomly slices and repositions portions of the image
      imageSliceGlitch(intensity) {
        const sliceCount = Math.floor(Math.random() * (10 * intensity) + 3);
        const maxOffset = Math.floor(50 * intensity);

        for (let i = 0; i < sliceCount; i++) {
          const isVertical = Math.random() < 0.1;

          if (isVertical) {
            const sliceWidth = Math.floor(Math.random() * this.canvas.width * intensity / 4);
            const sourceX = Math.floor(Math.random() * this.canvas.width);
            const destX = Math.floor(Math.random() * this.canvas.width);
            const offsetY = Math.floor(Math.random() * maxOffset * 2 - maxOffset);

            this.ctx.drawImage(
              this.canvas,
              sourceX, 0, sliceWidth, this.canvas.height,
              destX, offsetY, sliceWidth, this.canvas.height
            );
          } else {
            const sliceHeight = Math.floor(Math.random() * this.canvas.height * intensity / 4);
            const sourceY = Math.floor(Math.random() * this.canvas.height);
            const destY = Math.floor(Math.random() * this.canvas.height);
            const offsetX = Math.floor(Math.random() * maxOffset * 2 - maxOffset);

            this.ctx.drawImage(
              this.canvas,
              0, sourceY, this.canvas.width, sliceHeight,
              offsetX, destY, this.canvas.width, sliceHeight
            );
          }
        }
      }

      // Method to apply a pixel distort glitch effect
      // This method shifts individual pixels to create a distortion effect
      pixelDistortGlitch(intensity) {
        const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
        const data = imageData.data;

        const maxDistort = Math.floor(20 * intensity);

        for (let i = 0; i < data.length; i += 4) {
          if (Math.random() < intensity) {
            const distortX = Math.floor(Math.random() * maxDistort * 2 - maxDistort);
            const distortY = Math.floor(Math.random() * maxDistort * 2 - maxDistort);

            const targetIndex = i + (distortY * this.canvas.width * 4) + (distortX * 4);

            if (targetIndex >= 0 && targetIndex < data.length) {
              data[i] = data[targetIndex];
              data[i + 1] = data[targetIndex + 1];
              data[i + 2] = data[targetIndex + 2];
            }
          }
        }

        this.ctx.putImageData(imageData, 0, 0);
      }

      /**
       * Starts the animation loop.
       */
      startAnimation() {
        // Begin the animation process
        this.animate();
      }

      /**
       * Handles the animation, drawing the glitch on the canvas.
       */
      animate() {
        // Apply a random glitch effect in each animation frame
        this.randomGlitch();
        // Request the next animation frame, recursively calling this method
        requestAnimationFrame(() => this.animate());
      }
    }

    // Initialize the glitch animation after the DOM content is fully loaded
    document.addEventListener('DOMContentLoaded', () => {
      const glitchAnimation = new AdvancedGlitchAnimation('/codingexercises/images/kirupa.png', 'glitchCanvas'); // Instantiate the AdvancedGlitchAnimation class
      let isSoundEnabled = false;

      // Modify the createStaticGlitch method to respect sound toggle
      glitchAnimation.createStaticGlitch = function(intensity) {
        if (!isSoundEnabled) return;

        const bufferSize = this.audioContext.sampleRate * 0.1;
        const buffer = this.audioContext.createBuffer(1, bufferSize, this.audioContext.sampleRate);
        const data = buffer.getChannelData(0);

        for (let i = 0; i < bufferSize; i++) {
          data[i] = Math.random() * 2 - 1;
        }

        const noiseSource = this.audioContext.createBufferSource();
        noiseSource.buffer = buffer;

        const gainNode = this.audioContext.createGain();
        gainNode.gain.setValueAtTime(0, this.audioContext.currentTime);

        const randomMultiplier = (Math.random() * 0.5) + 0.5;
        const gainValue = intensity * 0.05 * randomMultiplier;
        gainNode.gain.setValueAtTime(gainValue, this.audioContext.currentTime);
        gainNode.gain.setValueAtTime(0, this.audioContext.currentTime + 0.1);

        noiseSource.connect(gainNode);
        gainNode.connect(this.audioContext.destination);

        noiseSource.start();
        noiseSource.stop(this.audioContext.currentTime + 0.1);
      };

      // Add click event to toggle sound and resume audio context
      document.getElementById('glitchCanvas').addEventListener('click', () => {
        if (glitchAnimation.audioContext.state === 'suspended') {
          glitchAnimation.audioContext.resume();
			isSoundEnabled = true;
        } else {
          // Toggle sound
          isSoundEnabled = !isSoundEnabled;
          
          // Optional: Add visual feedback for sound state
          const canvas = document.getElementById('glitchCanvas');
          canvas.style.opacity = isSoundEnabled ? '1' : '0.7';
          canvas.style.filter = isSoundEnabled ? 'none' : 'grayscale(50%)';
        }
        
      });
    });
  </script>
</body>

</html>

If you have some suggestions, alternate ways of solving this, or just have some questions, please do share this in the How to Print this Pyramid Pattern thread!

Just a final word before we wrap up. If you have a question and/or want to be part of a friendly, collaborative community of over 220k other developers like yourself, post on the forums for a quick response!

Kirupa's signature!

The KIRUPA Newsletter

Thought provoking content that lives at the intersection of design 🎨, development 🤖, and business 💰 - delivered weekly to over a bazillion subscribers!

SUBSCRIBE NOW

Creating engaging and entertaining content for designers and developers since 1998.

Follow:

Popular

Loose Ends

:: Copyright KIRUPA 2025 //--