Drawing on canvas with keyboard events


#1

Hello,first,sorry for my English,i’m not native, nice to meet you all :slight_smile:
I’m working locally (linux/xampp ) on a project where i need to draw many elements on canvas on multiply keyboard events.
Simple example: when i press any of letters: a s d f or their combination i’m drawing light bubble on dark canvas, but i need to clear that bubble on keyup event.
And i need to animate those bubbles so they go up,until half of canvas height,then dissappear
My question is: how can i fire single animation for each keydown event, and let it end it on keyup,so bubble can reach half canvas height?
I know i don’t make any sense,so this is what i’m trying to achive:
find on youtube: liszt campanella 100k subscribers
Guy is playing on the piano, and whenever he press piano keys there are those light effects showing up and smoothly dissapearing.
I can fire animation,but how to make it stop on keyup?


#2

This will take a little bit of work. You’ll need to create objects to represent your animations, track them in a list for each letter that’s being pressed, and drive them in a requestAnimationFrame function.

Here’s some code to get you started. It will show a letter on the screen when its being pressed. You’ll need to replace the character with your bubble drawing and update the onFrame in the KeyAnimation class to animate that bubble. You’ll need an extra property or two to track your height and also a check in there to remove itself from the animations list once that height reaches half the canvas height.

working fiddle:
http://jsfiddle.net/0mnchg27/

code:

// HTML: <canvas id="canvas" width="600" height="100"></canvas>

const keyAnimations = {};
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
context.font = '22px sans-serif';
context.fillColor = 'black';

class KeyAnimation {
  
  constructor(keyCode) {
    this.keyCode = keyCode;
    this.char = String.fromCharCode(keyCode);
  }
  
  onFrame () {
    const x = 10 + (this.keyCode - 65) * 22;
  	const y = 54;
  	context.fillText(this.char, x, y);
  }
}

document.addEventListener('keydown', event => {
  keyAnimations[event.keyCode] = new KeyAnimation(event.keyCode);
});

document.addEventListener('keyup', event => {
  delete keyAnimations[event.keyCode];
});

function onFrame () {

	context.clearRect(0, 0, canvas.width, canvas.height);
  
	for (let keyCode in keyAnimations) {
  	keyAnimations[keyCode].onFrame();
  }
  
	requestAnimationFrame(onFrame);
}

requestAnimationFrame(onFrame);

#3

HI, this is very simplified visual state that i’m trying to achieve:
http://jsfiddle.net/cL53j0q4/

I put my simplified code on the bottom of the reply.
Each of my keys, here divs have about 25% width, so neeed calculate their x,y coordinate,
y is easy,its 100% height of the canvas,but to put a bubble on the middle of the square i need to get correct x position.
Generally,it works fine on keydown, i’m adding correct active class for each div,drawing circle above each square,on the midlle,and now i’m trying to start animation for each buble on keydown and finish it on keyup.
I’m not familiar with let, thanks for trying to help me,i’m beginner in js.

    //// Canvas
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext('2d');

    // Set canvas dimensions
    var canvas_width = canvas.offsetWidth;
    var canvas_height = canvas.offsetHeight;
    canvas.width = canvas_width;
    canvas.height = canvas_height;

    // Get canvas center points
    var canvas_x_center = canvas_width / 2;
    var canvas_y_center = canvas_height / 2;

    //// Keys
    var keys = [
            {
            el: document.getElementById("key-01"),
            id: "key-01"
            key_code: 49,
            key_val: "!",
            shift: true
        },
        {
            el: document.getElementById("key-02"),
            id: "key-02",
            key_code: 53,
            key_val: "%",
            shift: true
        },
        {
            el: document.getElementById("key-03"),
            id: "key-03",
            key_code: 69,
            key_val: "E",
            shift: true
        },
        {
            el: document.getElementById("key-04"),
            id: "key-04",
            key_code: 71,
            key_val: "G",
            shift: true
        }
    ];

    var keys_length = keys.length;


    //// Bubble Object
    var bubbleObjectArray = [];
    function Bubble(bubble_x, bubble_y, bubble_r) {
        "use strict";

        // Properties
        this.bubble_x = (bubble_x === null) ? 0 : bubble_x;
        this.bubble_y = (bubble_y === null) ? 0 : bubble_y;
        this.bubble_r = (bubble_r === null) ? 0 : bubble_r;

        // Velocity
        //this.bubble_dx = bubble_dx;
        //this.bubble_dy = bubble_dy;

        // Make Full Circle
        var bubble_startangle = 0;
        var bubble_endangle = 2 * Math.PI;

        // Draw Filled Bubble
        this.bubble_draw_filled = function (ctx) {

            // Draw filled bubble whenever this is called
            ctx.beginPath();
            ctx.arc(this.bubble_x, this.bubble_y, this.bubble_r, bubble_startangle, bubble_endangle);

            // Fill bubbles with one static color
            ctx.fillStyle = "red";

            ctx.fill();

        }

    }



window.addEventListener("keydown", drawOnKeyDown, false);

            function drawOnKeyDown(e) {

                var e = e || window.event;
                var keyCode = e.keyCode;

                // Don't repeat drawing when pressing and holding keys
                var repeat = e.repeat;

                if (!e.altKey && !e.ctrlKey && !repeat) {

                    for (k = 0; k < keys_length; k++) {
                        // Get current active key by passing its ID
                        var this_key_id = keys[k].id;
                        var this_key = document.getElementById(this_key_id);
                        // Get position and dimension for our Object(Bubble)
                        var object_x_start = this_key.getBoundingClientRect().left;
                        var object_x_end = this_key.getBoundingClientRect().right;
                        var object_width = object_x_end - object_x_start;
                        var object_radius = object_width * 2;
                        var object_height = object_width;

                        var x = object_x_start + object_width / 2;
                        var y = height;
                        var r = object_radius;

                        if (!e.shiftKey && keys[k].shift === false) {
                            if ((e.which || keyCode) === keys[k].key_code) {
                                for (var b = 0; b < 1; b++) {
                                    var bubble = new Bubble(x, y, r);
                                    bubbleObjectArray.push(new Bubble(x, y, r));
                                    bubble.bubble_draw_filled(ctx);

                                    //// HOW TO ANIMATE THIS INSIDE LOOP ??
                                }
                            }

                        }


                    }

                }
            }

#4

let and const are just more strict versions of var. They’re newer additions to JavaScript, but I can stick to the older stuff to make it easier to understand.

Scanning the code it looks like you’re assuming the animations would be processed in a for loop. This is not the case. In JavaScript, normal for loops must run to completion before the browser screen can even refresh changes set in JavaScript. This is why I used requestAnimationFrame in my example. This runs a function every time the browser does a redraw making it easier to create JavaScript-driven animations. The continuous re-calling of this function basically represents your loop.

In fact, your inner most loop is completely unnecessary since it explicitly loops one time. If you’re only looping exactly once, the loop is unnecessary. Just run your code without the loop :wink:

I’ve updated my example to get rid of the newer syntax and add some animations using the requestAnimationFrame calls:

http://jsfiddle.net/u1nghpco/


#5

Hi,i used second inner loop,because i’m not sure yet if i want to produce only one bubble on each keydown,or more and make them looks like exploding fountain/fireworks shooting from one static point,but different for each pressed key.
Thank you for your code,i will try it as soon as i get back from work,can’t wait for it, i will send respond back then :slight_smile:


#6

Quick notice before i go to work: your code works fine when you pressing different keys randomly,but if you want to produce 2 or more F letters going up after each other animation for this letter will reset itself so you can have random letters but not ff, dddd or sss.
I will try to make sound wave/shoot effect with this later, if pressing one letter is like making one sound,then pressing this letter/sound shouldn’t care anymore if another sound appears after him,even if this is the same sound,shooted in the same starting position.
Sorry,i can’t explain myself properly in English ;/
I will try this code more tonight, thanks again for your help :slight_smile:


#7

But you said you wanted the bubbles to disappear on key up:

If it goes away on keyup, then you can’t have more than one at a time since to press the key again, you need to release it.

If you don’t want to clear on keyup, then you’d track the bubbles in an array, being able to add multiple bubbles of the same keyCode in that array. Something else would have to be responsible for removing them then (such as reaching a certain height).


#8

Sorry,my mistake, i just don’t know if i should kill animation for each bubble when bubble reach top of the canvas,or just clear that bubble with clearRect.
I changed KeyAnimations function to:
function KeyAnimation(keyCode, x, y, r)
And now i’m drawing bubbles with correct x,y position and radius passed by another function:

window.addEventListener(“keydown”, drawOnKeyDown, false);

Here i’m calculating x,y,r and so on, also detecting if user pressed shift/alt/ctrl/return,
and here i putted here yours:

keyAnimations[e.keyCode] = new KeyAnimation(e.keyCode, x, y, r);

Also,i commented out deleting animation frames on keyup
But now when i’m drawing with small letter: l and press shift + l,
big letter L deletes my small letter l bubble.
Lets say that i have a gun, and i’m shooting in one direction.
Where i stand - its my static x,y position,
and i have magic gun,so i have infinity number of bullets,
and i can shoot more than one bullet/1 time.
When i shoot i can’t really stop that bullet, its need to reach some destinatiion,
i can shoot next bullet after first,but i can’t stop or destroy that bullet in the fly.
So when that first bullet reach its destination its stops,or being auto-destroyed.
I guess i’m trying to put on canvas many shooting guys, on static positions, so they can’t move, and they can shoot one/many bullet whenever they want(so keyboard repeat should be true?)
I already putted shooters on my canvas,in their positions,thanks to your code, but if have user named l and user named L only one user can shoot…
And first bullets shouldn’t dissapear when the same shooter shoot again…


#9

I can also “destroy” bubbles when i reduce theirs radius while moving up on canvas, but i’m starting to worried that i want to much, its to heavy for performance,and something will brake or will be unusable ;/

         function KeyAnimation(keyCode, x, y, r) { 
         this.keyCode = keyCode;

        this.onFrame = function () {

            // Move our bubbles
            //x = x;
            y -= 1;// go up
            r = r - (r / 64);// destroy bubble by reducing radius

            // Draw One | Many Bubbles using Bubble Object
            for (var b = 0; b < 2; b++) {

                var bubble = new Bubble(x, y, r);
                // bubbleObjectArray will push new Bubble each time this loop is run through:
                bubbleObjectArray.push(new Bubble(x, y, r));
                // Colorize Bubbles
                bubble.bubble_draw_gradient(eq_ctx);
            }

        }

    }

#10

You don’t want to do this if you want more than one bubble per key stroke

keyAnimations[e.keyCode] = new KeyAnimation(e.keyCode, x, y, r);

This explicitly limits that by having one object per keyCode value.

Instead you’d use an array, as I was mentioning before.

keyAnimations = [];
// ...
keyAnimations.push(new KeyAnimation(e.keyCode, x, y, r));

Then you’d use a normal array loop to access each one of those. When the animation is complete, it needs to be removed from that array. BUT, you have to be careful with this because if you remove an array element while IN an array loop, it could mess up your loop. To get around this, you can copy your loop first, or loop through it backwards. A copy approach would look something like…

var animations = keyAnimations.slice(); // creates copy
for (var i = 0; i < animations.length; i++) {
    var animation = animations[i];
    // ...
    // if animation removes itself from keyAnimations in this loop
    // it won't affect the loop since the loop is going through `animations`
}

#17

I’ve end up with re-writing my code, so its more object-oriented, i wonder if i should remove arc()s and draw with images intead,for better performance, and instead of using 1 canvas use more, placed on top of each other.