Search code examples
htmlhtml5-canvas

i am trying to do an audio visualization with HTML canvas


i did implement the audio visualization but i am not happy with the results. i was hoping for a better solution than what i came up with. like for example is there a better way to draw the bars and also a better way to animate other than what i did which is changing the gradient.

audio track before play

this is the audio track before the play button is clicked

after play

this is the audio track after play button is clicked

here is the code

window.addEventListener('load', init)

const canvas = document.querySelector('canvas')
const btn = document.querySelector('#play')

const ctx = canvas.getContext('2d')

const sample = [...] // this has a random height values between 0 and 24 px
function init() {

        let barLength = 55
    let barWidth = (canvas.width - (barLength - 1)) / barLength 
    let region = new Path2D()
    for(let i = 0; i < sample.length; i++) {
        let halfHeight = sample[i] / 2
        region.rect(i * (barWidth + 1), (24 - halfHeight), barWidth, sample[i])
    }

    var grd = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
    grd.addColorStop(0, "#ddd")     
    grd.addColorStop(1, "#ddd")

    ctx.clip(region)

    ctx.fillStyle = grd
    ctx.fillRect(0, 0, canvas.width, canvas.height)
}


let xPos = 0
function drawRect() {
    ctx.clearRect(0, 0, canvas.width, canvas.height)

    let pos = xPos / canvas.width

        pos = pos > 1.0 ? 1 : pos

        var grd = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
    grd.addColorStop(0, "orange")
    grd.addColorStop(pos, "orange")
    grd.addColorStop(pos, "#ddd")
    grd.addColorStop(1, "#ddd")

    ctx.fillStyle = grd
        ctx.fillRect(0, 0, canvas.width, canvas.height)
}

let prev
let requestId
function draw(sec) {
    if (!prev)
           prev = new Date()

        let now  = new Date()
  
        let difference = now - prev

        prev = now

        xPos += (difference * canvas.width) / (duration * 1000)

        drawRect()

    requestId = requestAnimationFrame(draw)

    if(xPos >= canvas.width) {
        prev = null
        cancelAnimationFrame(requestId)
    }
}

btn.addEventListener('click', () => {
    draw()
})

Solution

  • Your idea is pretty good.

    The only thing I could think of is that you are currently redrawing the full image every frame, but that just changes the orange part.

    What you could do instead is draw "holes" into the picture as to have the white bars be fully transparent. That way, we would see through the waves. The picture would then be in a div and its background would be changed to have the orange highlight.

    This in no way changes the visual, it just reduces the computations needed for the same effect.

    const canvas = document.querySelector('canvas')
    const btn = document.querySelector('#play')
    
    const ctx = canvas.getContext('2d')
    
    const sample = []; // this has a random height values between 0 and 24 px
    for(let i=0;i<55;i++){
    sample.push(Math.random()*24);
    }
    function init() {
        ctx.fillRect(0,0, canvas.width, canvas.height);
            let barLength = 55
        let barWidth = (canvas.width - (barLength - 1)) / barLength 
        let region = new Path2D()
        for(let i = 0; i < sample.length; i++) {
            let halfHeight = sample[i] / 2
            region.rect(i * (barWidth + 1), (24 - halfHeight), barWidth, sample[i])
        }
    
        var grd = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
        grd.addColorStop(0, "#ddd")     
        grd.addColorStop(1, "#ddd")
    
        ctx.clip(region);
        ctx.globalCompositeOperation = 'destination-atop';
        ctx.globalAlpha = 0;
        ctx.fillRect(0, 0, canvas.width, canvas.height)
    }
    
    
    let xPos = 0
    function drawRect() {
    
        ctx.clearRect(0, 0, canvas.width, canvas.height)
    
        let pos = xPos / canvas.width
    
            pos = pos > 1.0 ? 1 : pos
    
            var grd = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
        grd.addColorStop(0, "orange")
        grd.addColorStop(pos, "orange")
        grd.addColorStop(pos, "#ddd")
        grd.addColorStop(1, "#ddd")
    
        ctx.fillStyle = grd
            ctx.fillRect(0, 0, canvas.width, canvas.height)
    }
    init();
    const div = document.querySelector('#div');
    setInterval(function(){
    xPos++;
     const percent = xPos / canvas.width *100;
     const t = `linear-gradient(90deg, rgba(255,100,0,1), rgba(255,100,0,1) ${percent}%, rgba(255,255,255,1) ${percent}%, rgba(255,255,255,1) 100%)`;
    div.style.backgroundImage = t;
    },100);
    div{
        background-color:white;
        display:inline-block;
        height:50px;
    }
    <div id="div"><canvas height="50"></canvas></div>