javascripthtmlhtml5-canvas2dmotion-blur

Motion effect for standing object


I have trying to achieve motion effect for the standing object.
I assume that my object will be able to go to the right or the left for example.
And I wanna make illusion like that object is still moving. Even if currently is not moving. (Meanwhile background can moving still...)

Ex. what it should look like

'use strict';

const pressedKeys = [];
const canvas = document.querySelector('#game');
const ctx = canvas.getContext('2d');

canvas.width = 300;
canvas.height = 150;

class Player
{
    xPosition = 150;
    yPosition = 50;
    speed = 5;
    isMoving = false;

    update(pressedKeys)
    {
        if (pressedKeys['ArrowLeft']) {
            this.xPosition -= this.speed;
        } else if (pressedKeys['ArrowRight']) {
            this.xPosition += this.speed;
        }
    }

    draw(ctx)
    {
        ctx.fillStyle = '#2976f2';
        ctx.fillRect(this.xPosition, this.yPosition, 30, 30);
    }
}

const player = new Player();

function animate()
{
    window.requestAnimationFrame(animate);
    ctx.clearRect(0, 0, canvas.width, canvas.height)

    if (player.isMoving) {
        player.update(pressedKeys);
    }

    player.draw(ctx);
}

animate();

window.addEventListener('keyup', function (event) {
    delete pressedKeys[event.key];
    player.isMoving = false;
})

window.addEventListener('keydown', function (event) {
    switch (event.key) {
        case 'ArrowLeft':
        case 'ArrowRight':
            pressedKeys[event.key] = true;
            player.isMoving = true;
            break;
    }
})
canvas {
    border: 1px solid blue;
}
<canvas id="game"></canvas>


Solution

  • Usually this kind of effect is done by constantly duplicating the desired object, move it to the exact same screen position and ultimately fade it out over time e.g. within one second.

    In your case though we can simplify things a bit since you want to keep that "motion blurred" look even if it ain't moving.

    So first we need to another property to your Player class oldX. It holds the position of the object before a movement occured. By subtracting oldX from x we can determine if the object is moving to the left or to the right - so we know where to put the trailing duplicates.

    If we know the direction, we can start creating duplicates using a simple for loop like:

    for (var a = 0; a < 7; a++) {
      ctx.fillRect(this.x - (this.x - this.oldX) / this.speed * a * 2, this.y, 30, 30);
    }
    

    this will create seven equal looking squares - so it won't look good yet. The duplicate next to the original should have almost the same color while the last one should almost blend with the background. To do this we can use the globalAlpha property of the canvases context. A value of 1 is opaque while 0 is completely transparent.

    Putting it all together:

    const keys = [];
    const canvas = document.getElementById('game');
    const ctx = canvas.getContext('2d');
    
    canvas.width = 300;
    canvas.height = 150;
    
    class Player {
      x = 150;
      y = 50;
      oldX = 150;
      speed = 5;
      moving = false;
    
      update(keys) {
        this.oldX = this.x;
        if (keys['ArrowLeft']) {
          this.x -= this.speed;
        } else if (keys['ArrowRight']) {
          this.x += this.speed;
        }
      }
    
      draw(ctx) {
        ctx.fillStyle = '#2976f2';
        ctx.fillRect(this.x, this.y, 30, 30);
        ctx.save();
        for (var a = 0; a < 7; a++) {
          ctx.globalAlpha = 0.5 - (a / 7) * 0.5;
          ctx.fillRect(this.x - (this.x - this.oldX) / this.speed * a * 2, this.y, 30, 30);
        }
        ctx.restore();
    
      }
    }
    
    const player = new Player();
    
    function animate() {
      window.requestAnimationFrame(animate);
      ctx.clearRect(0, 0, canvas.width, canvas.height)
    
      if (player.moving) {
        player.update(keys);
      }
    
      player.draw(ctx);
    }
    
    animate();
    
    window.addEventListener('keyup', function(event) {
      delete keys[event.key]
      player.moving = false;
    })
    
    window.addEventListener('keydown', function(event) {
      switch (event.key) {
        case 'ArrowLeft':
        case 'ArrowRight':
          keys[event.key] = true;
          player.moving = true;
          break;
      }
    })
    canvas {
      border: 1px solid blue;
    }
    <canvas id="game"></canvas>