Script not being re-initated on every new run?

I’ve added a confetti script which is run on a keyframe, this works great!

However, every time it is re-run, the animation is speed up, since it’s calculating every set variable double. I’m doing a new initiation of the classes every time, but something is still causing it to double physics values on every execution.

Maybe someone can spot what I can’t, this is the script:

    // init global elements
    const konfWrap = document.getElementById('konfWrap')
    const button = document.getElementById('konfTarget')
    var disabled = false
    const canvas = document.getElementById('konfCanvas')
    const ctx = canvas.getContext('2d')
    canvas.width = konfWrap.offsetWidth
    canvas.height = konfWrap.offsetHeight
    const cx = ctx.canvas.width / 2
    const cy = ctx.canvas.height / 2

    // add Confetto/Sequin objects to arrays to draw them
    let confetti = []
    let sequins = []

    // ammount to add on each button press
    const confettiCount = 50
    const sequinCount = 30

    // "physics" variables
    const gravityConfetti = 0.3
    const gravitySequins = 0.35
    const dragConfetti = 0.075
    const dragSequins = 0.02
    const terminalVelocity = 4

    // colors, back side is darker for confetti flipping
    const colors = [
      { front : '#7b5cff', back: '#6245e0' }, // Purple
      { front : '#b3c7ff', back: '#8fa5e5' }, // Light Blue
      { front : '#5c86ff', back: '#345dd1' }  // Darker Blue
    ]

    // helper function to pick a random number within a range
    randomRange = (min, max) => Math.random() * (max - min) + min

    // helper function to get initial velocities for confetti
    // this weighted spread helps the confetti look more realistic
    initConfettoVelocity = (xRange, yRange) => {
      const x = randomRange(xRange[0], xRange[1])
      const range = yRange[1] - yRange[0] + 1
      let y = yRange[1] - Math.abs(randomRange(0, range) + randomRange(0, range) - range)
      if (y >= yRange[1] - 1) {
        // Occasional confetto goes higher than the max
        y += (Math.random() < .25) ? randomRange(1, 3) : 0
      }
      return {x: x, y: -y}
    }

    // Confetto Class
    function Confetto() {
      this.randomModifier = randomRange(0, 99)
      this.color = colors[Math.floor(randomRange(0, colors.length))]
      this.dimensions = {
        x: randomRange(5, 9),
        y: randomRange(8, 15),
      }
      this.position = {
        x: randomRange(canvas.width/2 - button.offsetWidth/4, canvas.width/2 + button.offsetWidth/4),
        y: randomRange(canvas.height/2 + button.offsetHeight/2 + 8, canvas.height/2 + (1.5 * button.offsetHeight) - 8),
      }
      this.rotation = randomRange(0, 2 * Math.PI)
      this.scale = {
        x: 1,
        y: 1,
      }
      this.velocity = initConfettoVelocity([-9, 9], [6, 11])
    }
    Confetto.prototype.update = function() {
      // apply forces to velocity
      this.velocity.x -= this.velocity.x * dragConfetti
      this.velocity.y = Math.min(this.velocity.y + gravityConfetti, terminalVelocity)
      this.velocity.x += Math.random() > 0.5 ? Math.random() : -Math.random()
      
      // set position
      this.position.x += this.velocity.x
      this.position.y += this.velocity.y

      // spin confetto by scaling y and set the color, .09 just slows cosine frequency
      this.scale.y = Math.cos((this.position.y + this.randomModifier) * 0.09)    
    }

    // Sequin Class
    function Sequin() {
      this.color = colors[Math.floor(randomRange(0, colors.length))].back,
      this.radius = randomRange(1, 2),
      this.position = {
        x: randomRange(canvas.width/2 - button.offsetWidth/3, canvas.width/2 + button.offsetWidth/3),
        y: randomRange(canvas.height/2 + button.offsetHeight/2 + 8, canvas.height/2 + (1.5 * button.offsetHeight) - 8),
      },
      this.velocity = {
        x: randomRange(-6, 6),
        y: randomRange(-8, -12)
      }
    }
    Sequin.prototype.update = function() {
      // apply forces to velocity
      this.velocity.x -= this.velocity.x * dragSequins
      this.velocity.y = this.velocity.y + gravitySequins
      
      // set position
      this.position.x += this.velocity.x
      this.position.y += this.velocity.y   
    }

    // add elements to arrays to be drawn
    initBurst = () => {
      for (let i = 0; i < confettiCount; i++) {
        confetti.push(new Confetto())
      }
      for (let i = 0; i < sequinCount; i++) {
        sequins.push(new Sequin())
      }
    }

    // draws the elements on the canvas
    render = () => {
      ctx.clearRect(0, 0, canvas.width, canvas.height)
      
      confetti.forEach((confetto, index) => {
        let width = (confetto.dimensions.x * confetto.scale.x)
        let height = (confetto.dimensions.y * confetto.scale.y)
        
        // move canvas to position and rotate
        ctx.translate(confetto.position.x, confetto.position.y)
        ctx.rotate(confetto.rotation)

        // update confetto "physics" values
        confetto.update()
        
        // get front or back fill color
        ctx.fillStyle = confetto.scale.y > 0 ? confetto.color.front : confetto.color.back
        
        // draw confetto
        ctx.fillRect(-width / 2, -height / 2, width, height)
        
        // reset transform matrix
        ctx.setTransform(1, 0, 0, 1, 0, 0)

        // clear rectangle where button cuts off
        if (confetto.velocity.y < 0) {
          ctx.clearRect(canvas.width/2 - button.offsetWidth/2, canvas.height/2 + button.offsetHeight/2, button.offsetWidth, button.offsetHeight)
        }
      })

      sequins.forEach((sequin, index) => {  
        // move canvas to position
        ctx.translate(sequin.position.x, sequin.position.y)
        
        // update sequin "physics" values
        sequin.update()
        
        // set the color
        ctx.fillStyle = sequin.color
        
        // draw sequin
        ctx.beginPath()
        ctx.arc(0, 0, sequin.radius, 0, 2 * Math.PI)
        ctx.fill()

        // reset transform matrix
        ctx.setTransform(1, 0, 0, 1, 0, 0)

        // clear rectangle where button cuts off
        if (sequin.velocity.y < 0) {
          ctx.clearRect(canvas.width/2 - button.offsetWidth/2, canvas.height/2 + button.offsetHeight/2, button.offsetWidth, button.offsetHeight)
        }
      })

      // remove confetti and sequins that fall off the screen
      // must be done in seperate loops to avoid noticeable flickering
      confetti.forEach((confetto, index) => {
        if (confetto.position.y >= canvas.height) confetti.splice(index, 1)
      })
      sequins.forEach((sequin, index) => {
        if (sequin.position.y >= canvas.height) sequins.splice(index, 1)
      })

      window.requestAnimationFrame(render)
    }

    // cycle through button states when clicked
    window.initBurst()

    // kick off the render loop
    render()

Can you please post a zip of your .hype document? It is hard to see this out of context. Thanks!

Sure thing, I’ll move it to a new test doc and send over. Thanks for looking at it :slight_smile:

Here’s the file!

SpeedupBug.hype.zip (12.4 KB)

Odd thing is, it works fine outside of Hype context… :man_shrugging:

the script binds a requestAnimationFrame to the window-object, but does not cancel it when finished … so on every run you’ll collect one more diligent helper :wink:

it’s not hyperelated, the issue would persist everywhere. i guess you just checked for reloads …

so cancelAnimationFrame when finished and you’re fine :slight_smile:

1 Like

I’ve never used AnimationFrame before, here’s my Codepen where it doesn’t happen.

EDIT: Tried cancelAnimationFrame at the end, but when is the end? It’s a physics animation.

sry, i thought you scripted this yourself …

well, it seems to be finished if there’s no more confetti :slight_smile:

i’d also place a check for the script to be running on top …

SpeedupBug.hype_fixed.zip (13.9 KB)

2 Likes

requestAnimationFrame is new to me, I simply used what confetti code i could find and modified it for my needs. Modifying divs using math and drawing at 60 times per second using JS feels crazy in general :crazy_face:

Thanks for the fix!

Update: Seems the last confetti is cut of at half, I wonder why that occurs? Is it because it’s rendered from center and out and thus don’t exist but is still shown?

>= 0 :slight_smile:not > 0 my bad :slight_smile:

1 Like