Registering event handlers with JS and Canvas - javascript

In my transition from the basics of Python (learned on the Coursera RICE course) to the Javascript ones, I'm trying to register even handlers using the same paradigm people here have been helping me successfully converting.
In this example, both the timer and update handlers aren't running :
//Globals
var message = "test message";
var positionX = 50;
var positionY = 50;
width = 500;
height = 500;
var interval = 2000;
//handler for text box
function update(text) {
message = text;
}
//handler for timer
function tick() {
var x = Math.floor(Math.random() * (width - 0) + 0);
var y = Math.floor(Math.random() * (height - 0) + 0);
positionX = x;
positionY = y;
}
//handler to draw on canvas
function draw(canvas) {
ctx.font="20px Georgia";
ctx.fillText(message,positionX,positionY);
}
//create a frame
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
//register event handlers
setInterval(tick, interval);
draw();
//start the frame and animation
<html>
<head>
</head>
<body>
Enter text here :
<input type="text" onblur="update(this.value)">
<canvas id="myCanvas" style="border:1px solid #000000;"></canvas>
<script>
</script>
</body>
</html>
Adding canvas.addEventListener("draw", draw(), false); and canvas.addEventListener("update", update(), false); didn't change anything.
Though, the only time it "worked" was when I added the draw(); call inside the tick(); function but it was keeping the text and duplicating it on screen randomly as the functions should work.
On a general note, do you guys think the current paradigm :
//Global state
//Handler for text box
//Handler for timer
//Handler to draw on canvas
//Create a frame
//Register event handlers
//Start the frame animation
is worth pursuing with JS and Canvas ?
Again, thanks for your time and help.
K.

The draw function should be inside the tick function. If you want to stop the old text you need to clear the canvas before you draw the text again. SetInterval is ok for time over 100ms or so but if you plan to do full frame animation 60fps use requestAnimationFrame
I am personally not a fan of setInterval and use setTimeout instead. Also note that the times given for set interval and timeout are approximations only. Javascript is single threaded and if the timeout or interval occurs while code is running then the events will wait until the current execution is complete.
"use strict";
//Globals
var message = "test message";
var positionX = 50;
var positionY = 50;
// constants
const width = 500;
const height = 500;
const interval = 2000;
if(typeof inputText !== "undefined"){
inputText.value = message;
}
//handler for text box
function update(text) {
message = text;
}
//handler for timer
function tick() {
setTimeout(tick, interval); // create the next timer event
positionX = Math.floor(Math.random() * width);
positionY = Math.floor(Math.random() * (height-20)); // 20 pixels of height to stop it disappearing
draw();
}
//handler to draw on canvas
function draw() {
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
var textWidth = ctx.measureText(message).width;
ctx.fillText(message,Math.min(width - textWidth, positionX), positionY);
}
//create a context
const canvas = document.getElementById('myCanvas');
// set size befor get context as it is a little more efficient
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// if the same font only need to set it once or after the canvas is resized
ctx.font="20px Georgia";
//start the animation
tick();
<html>
<head>
</head>
<body>
Enter text here :
<input id="inputText" type="text" onblur="update(this.value)">
<canvas id="myCanvas" style="border:1px solid #000000;"></canvas>
<script>
</script>
</body>
</html>

Related

Javascript canvas change drawing color

I am making a simple drawing board with HTML and JavaScript (Node as server side), but I don't know how I can make a colorpicker where I change the color of the paint. I know how to hard code it, but that is not what I want.
I want something like buttons, if you click a button it will turn into a specific color. Something like 4 buttons.
This is my method:
//Function for when the mouse button is clicked.
canvas.onmousedown = function (e) {
//The mouse button is clicked (true).
mouse.click = true;
context.strokeStyle = "red";
};
As you can see, I have hardcoded the color to red.
This is my full JavaScript code. The HTML document only consist of an element "canvas".
//"DOMContetLoaded tells the browser then the HTML page has finished loading.
document.addEventListener("DOMContentLoaded", function () {
//Add standard mouse functions.
var mouse = {
click: false,
move: false,
pos: { x: 0, y: 0 },
pos_prev: false
};
//Get the CanvasWhiteboard elements by it's ID from the HTML page. We need this to be able to draw.
var canvas = document.getElementById('whiteboard');
//The whiteboard is in 2D with the width and height being the dimensions of the window.
var context = canvas.getContext('2d');
var width = window.innerWidth;
var height = window.innerHeight;
//The client connects to the server.
var socket = io.connect();
//The width and height of the whiteboard equals the window width and height.
canvas.width = width;
canvas.height = height;
// Function for when the mouse button is pushed down.
canvas.onmousedown = function (e) {
// Set mouse click to true.
mouse.click = true;
context.strokeStyle = "red";
};
// Function for when the mouse button is lifted.
canvas.onmouseup = function (e) {
// Set mouse click to false.
mouse.click = false;
};
// Function to check if the mouse is moved.
canvas.onmousemove = function (e) {
//The movement of the mouse at X and Y position is updated everytime the mouse moves.
//The coordinates equals the coordinates relative to the window height and width.
mouse.pos.x = e.clientX / width;
mouse.pos.y = e.clientY / height;
//The mouse is moving (true).
mouse.move = true;
};
//Listen for draws from other clients.
socket.on('draw_data', function (data) {
//The line being drawn equals the data.
var line = data.line;
//Begin from the start of the drawn data.
context.beginPath();
//The thickness of the line.
context.lineWidth = 2;
//Next point to move to from the beginPath.
context.moveTo(line[0].x * width, line[0].y * height);
//End point to move to from the beginPath.
context.lineTo(line[1].x * width, line[1].y * height);
//The data is then drawn on the whiteboard.
context.stroke();
});
//This loop is where our own drawings are sent to the server for the other clients to see.
//It checks every 25ms if the mouse is being clicked and moved.
function mainLoop() {
if (mouse.click && mouse.move && mouse.pos_prev) {
//Send our drawing to the server.
socket.emit('draw_data', { line: [mouse.pos, mouse.pos_prev] });
//The mouse movement is set to false (reset).
mouse.move = false;
}
//The previous position now equals the position we just were at.
mouse.pos_prev = { x: mouse.pos.x, y: mouse.pos.y };
//This is where the 25ms is definend.
setTimeout(mainLoop, 25);
}
//Being called recursively.
mainLoop();
});
You can use CSS to do it too, changing the Canvas to red when click the button
canvas{
background-color:red;
}
Or try this code:
//Function for when the mouse button is clicked.
canvas.onmousedown = function (e) {
//The mouse button is clicked (true).
mouse.click = true;
ctx.fillStyle = 'red';
};
This was my solution:
In HTML I added a drop down box:
<!--Color Picker-->
<select id="colors">
<option value="black">black</option>
<option value="aqua">aqua</option>
<option value="blue">blue</option>
<option value="brown">brown</option>
</select>
In my JavaScript I added this to get the color picker by ID:
//Get the color picker from the HTML page by ID.
var getColorPickerByID = document.getElementById("colors");
And this to get the value of the color picker, i.e. the selected item. This should of course be in a loop that updates every like 10ms so the value gets updated when you pick a new color:
//Get the color picker value, i.e. if the user picks red the value is red.
var getValueOfColorPicker = getColorPickerByID.options[getColorPickerByID.selectedIndex].text;
And at last the strokeStyle itself to set the color of the line being drawn by using the value from the above variable getValueOfColorPicker
//Set the color of the line being drawn by using above variable "getValueOfColorPicker".
drawingArea.strokeStyle = getValueOfColorPicker;
I written sample code you can use it.
function changeColor(color) {
ctx.strokeStyle = color;
};
const c = document.querySelector("#c");
c.height = window.innerHeight / 2;
c.width = window.innerWidth / 2;
const ctx = c.getContext("2d");
let painting = false;
function mousedown(e) {
painting = true;
mousemove(e);
}
function mouseup() {
painting = false;
ctx.beginPath();
}
function mousemove(e) {
if (!painting) return;
ctx.lineWidth = 4;
ctx.lineCap = 'round';
//ctx.strokeStyle = 'black';
ctx.lineTo(e.clientX / 2, e.clientY / 2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(e.clientX / 2, e.clientY / 2);
}
c.addEventListener('mousedown', mousedown);
c.addEventListener('mouseup', mouseup);
c.addEventListener('mousemove', mousemove);
#c {
border: 1px solid black;
}
#container {
text-align: center;
}
.button {
width: 5em;
height: 2em;
text-align: center;
}
<html>
<head>
<meta charset="utf-8" content="Badri,Chorapalli,XML,JavaScript">
<title>Canvas</title>
</head>
<body>
<div id="container">
<canvas id="c" width="400" height="400"></canvas><br>
<button class="button" onclick="changeColor('black')" id="blue">Black</button>
<button class="button" onclick="changeColor('blue')" id="blue">Blue</button>
<button class="button" onclick="changeColor('red')" id="blue">Red</button>
<button class="button" onclick="changeColor('green')" id="blue">Green</button>
</div>
</body>
</html>

Image in canvas leaves a tiled trail when panned

I am trying to create a pannable image viewer which also allows magnification. If the zoom factor or the image size is such that the image no longer paints over the entire canvas then I wish to have the area of the canvas which does not contain the image painted with a specified background color.
My current implementation allows for zooming and panning but with the unwanted effect that the image leaves a tiled trail after it during a pan operation (much like the cards in windows Solitaire when you win a game). How do I clean up my canvas such that the image does not leave a trail and my background rectangle properly renders in my canvas?
To recreate the unwanted effect set magnification to some level at which you see the dark gray background show and then pan the image with the mouse (mouse down and drag).
Code snippet added below and Plnkr link for those who wish to muck about there.
http://plnkr.co/edit/Cl4T4d13AgPpaDFzhsq1
<!DOCTYPE html>
<html>
<head>
<style>
canvas{
border:solid 5px #333;
}
</style>
</head>
<body>
<button onclick="changeScale(0.10)">+</button>
<button onclick="changeScale(-0.10)">-</button>
<div id="container">
<canvas width="700" height="500" id ="canvas1"></canvas>
</div>
<script>
var canvas = document.getElementById('canvas1');
var context = canvas.getContext("2d");
var imageDimensions ={width:0,height:0};
var photo = new Image();
var isDown = false;
var startCoords = [];
var last = [0, 0];
var windowWidth = canvas.width;
var windowHeight = canvas.height;
var scale=1;
photo.addEventListener('load', eventPhotoLoaded , false);
photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
function eventPhotoLoaded(e) {
imageDimensions.width = photo.width;
imageDimensions.height = photo.height;
drawScreen();
}
function changeScale(delta){
scale += delta;
drawScreen();
}
function drawScreen(){
context.fillRect(0,0, windowWidth, windowHeight);
context.fillStyle="#333333";
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
}
canvas.onmousedown = function(e) {
isDown = true;
startCoords = [
e.offsetX - last[0],
e.offsetY - last[1]
];
};
canvas.onmouseup = function(e) {
isDown = false;
last = [
e.offsetX - startCoords[0], // set last coordinates
e.offsetY - startCoords[1]
];
};
canvas.onmousemove = function(e)
{
if(!isDown) return;
var x = e.offsetX;
var y = e.offsetY;
context.setTransform(1, 0, 0, 1,
x - startCoords[0], y - startCoords[1]);
drawScreen();
}
</script>
</body>
</html>
You need to reset the transform.
Add context.setTransform(1,0,0,1,0,0); just before you clear the canvas and that will fix your problem. It sets the current transform to the default value. Then befor the image is draw set the transform for the image.
UPDATE:
When interacting with user input such as mouse or touch events it should be handled independently of rendering. The rendering will fire only once per frame and make visual changes for any mouse changes that happened during the previous refresh interval. No rendering is done if not needed.
Dont use save and restore if you don't need to.
var canvas = document.getElementById('canvas1');
var ctx = canvas.getContext("2d");
var photo = new Image();
var mouse = {}
mouse.lastY = mouse.lastX = mouse.y = mouse.x = 0;
mouse.down = false;
var changed = true;
var scale = 1;
var imageX = 0;
var imageY = 0;
photo.src = "http://www.html5rocks.com/static/images/cors_server_flowchart.png";
function changeScale(delta){
scale += delta;
changed = true;
}
// Turns mouse button of when moving out to prevent mouse button locking if you have other mouse event handlers.
function mouseEvents(event){ // do it all in one function
if(event.type === "mouseup" || event.type === "mouseout"){
mouse.down = false;
changed = true;
}else
if(event.type === "mousedown"){
mouse.down = true;
}
mouse.x = event.offsetX;
mouse.y = event.offsetY;
if(mouse.down) {
changed = true;
}
}
canvas.addEventListener("mousemove",mouseEvents);
canvas.addEventListener("mouseup",mouseEvents);
canvas.addEventListener("mouseout",mouseEvents);
canvas.addEventListener("mousedown",mouseEvents);
function update(){
requestAnimationFrame(update);
if(photo.complete && changed){
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle="#333";
ctx.fillRect(0,0, canvas.width, canvas.height);
if(mouse.down){
imageX += mouse.x - mouse.lastX;
imageY += mouse.y - mouse.lastY;
}
ctx.setTransform(scale, 0, 0, scale, imageX,imageY);
ctx.drawImage(photo,0,0);
changed = false;
}
mouse.lastX = mouse.x
mouse.lastY = mouse.y
}
requestAnimationFrame(update);
canvas{
border:solid 5px #333;
}
<button onclick="changeScale(0.10)">+</button><button onclick="changeScale(-0.10)">-</button>
<canvas width="700" height="500" id ="canvas1"></canvas>
Nice Code ;)
You are seeing the 'tiled' effect in your demonstration because you are painting the scaled image to the canvas on top of itself each time the drawScreen() function is called while dragging. You can rectify this in two simple steps.
First, you need to clear the canvas between calls to drawScreen() and second, you need to use the canvas context.save() and context.restore() methods to cleanly reset the canvas transform matrix between calls to drawScreen().
Given your code as is stands:
Create a function to clear the canvas. e.g.
function clearCanvas() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
In the canavs.onmousemove() function, call clearCanvas() and invoke context.save() before redefining the transform matrix...
canvas.onmousemove = function(e) {
if(!isDown) return;
var x = e.offsetX;
var y = e.offsetY;
/* !!! */
clearCanvas();
context.save();
context.setTransform(
1, 0, 0, 1,
x - startCoords[0], y - startCoords[1]
);
drawScreen();
}
... then conditionally invoke context.restore() at the end of drawScreen() ...
function drawScreen() {
context.fillRect(0,0, windowWidth, windowHeight);
context.fillStyle="#333333";
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
/* !!! */
if (isDown) context.restore();
}
Additionally, you may want to call clearCanvas() before rescaling the image, and the canvas background could be styled with CSS rather than .fillRect() (in drawScreen()) - which could give a performance gain on low spec devices.
Edited in light of comments from Blindman67 below
See Also
Canvas.context.save : https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save
Canvas.context.restore : https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/restore
requestAnimationFrame : https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame
Paul Irish, requestAnimationFrame polyfill : http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
Call context.save to save the transformation matrix before you call context.fillRect.
Then whenever you need to draw your image, call context.restore to restore the matrix.
For example:
function drawScreen(){
context.save();
context.fillStyle="#333333";
context.fillRect(0,0, windowWidth, windowHeight);
context.restore();
context.drawImage(photo,0,0,imageDimensions.width*scale,imageDimensions.height*scale);
}
Also, to further optimize, you only need to set fillStyle once until you change the size of canvas.

HTML5 Canvas - drawing fails once shifting the current browser tab

Ok, I've noticed this bug happen not only in the following code(which is just to illustrate), but every code I've written for canvas;
basically, in chrome(I didn't test other browsers),
once you shift your current tab to either a different pc screen, or simply if you have a few tabs in a row and decide to make a new window out of your current tab,
the canvas itself fails to draw. it is stuck and no reason is given in the console.
Here is a GIF of it happening:
http://gifmaker.cc/PlayGIFAnimation.php?folder=2016081601ZEuvXaZnxC2T9fHNNqSW3l&file=output_f72O1H.gif
Just a simple canvas code if you want to try it out:
<canvas id="canvas" width="500" height="400" style="border:1px solid #000000;"></canvas>
<script>
// grab the canvas and context
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// inital coordinates of the black square
var x = 0;
var y = 200;
// speed of the movement in x- and y-direction
// actual no movement in y-direction
var vX = 1;
var vY = 0;
// width and height of the square
var width = 10;
var height = 10;
function animate() {
// clear
// comment this clear function to see the plot of the sine movement
ctx.clearRect(0, 0, canvas.width, canvas.height);
x += vX;
y += vY;
// if the block leaves the canvas on the right side
// bring it back to the left side
if(x>500) {
x = 0;
}
ctx.fillRect(x, y, width, height);
setTimeout(animate, 33);
}
// call the animate function manually for the first time
animate();
</script>
Some sites fixed this bug but I don't know if I am allowed to link them here.

Html5 canvas animation. How to reset rectangle when it hits the end

Hey Im experimenting with some html5 animation and so far I have a square that "falls" whenever I press the button. I was wondering how i could have it go back to the top when it hits the bottom and fall again.
My current code is:
<html>
<head>
</head>
<body>
<canvas id="myCanvas" width="200" height="400" style="border:1px solid black;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
function draw (x,y){
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.save();
var side = 10
var up = 10
ctx.clearRect(0,0,200,400);
ctx.fillStyle = "#FF0000";
ctx.fillRect(x,y,up,side);
ctx.restore();
y += 5;
var loopTimer = setTimeout('draw('+x+','+y+')',30);
}
</script>
<button onclick="draw(0,0)">draw</button>
</body>
</html>
Using your variables y, you can simply check if it is below the height of the canvas height.
if( y > c.height ){ // use the canvas height, not the context height
y = 0;
}
Also, the way you're currently calling the timer is a bit inefficient. Instead of :
var loopTimer = setTimeout('draw('+x+','+y+')',30);
I would recommend
var loopTimer = setTimeout(function(){ draw(x,y); },30);
Here's a working example: http://jsfiddle.net/vwcdpLvv/

html5 canvas context .fillStyle not working

Just giving canvas a go for the first time with the intention of creating a game. I have an image displaying but oddly the fillStyle method doesn't seem to be working. ( At least the canvas background is still white in google chrome.)
Note that in my code the canvas var is actually the canvas elements 2d context, maybe that's where i'm getting myself confused? i can't see the problem, would appreciate if anyone else could.
LD24.js:
const FPS = 30;
var canvasWidth = 0;
var canvasHeight = 0;
var xPos = 0;
var yPos = 0;
var smiley = new Image();
smiley.src = "http://javascript-tutorials.googlecode.com/files/jsplatformer1-smiley.jpg";
var canvas = null;
window.onload = init; //set init function to be called onload
function init(){
canvasWidth = document.getElementById('canvas').width;
canvasHeight = document.getElementById('canvas').height;
canvas = document.getElementById('canvas').getContext('2d');
setInterval(function(){
update();
draw();
}, 1000/FPS);
}
function update(){
}
function draw()
{
canvas.clearRect(0,0,canvasWidth,canvasHeight);
canvas.fillStyle = "#FFAA33"; //orange fill
canvas.drawImage(smiley, xPos, yPos);
}
LD24.html:
<html>
<head>
<script language="javascript" type="text/javascript" src="LD24.js"></script>
</head>
<body>
<canvas id="canvas" width="800" height="600">
<p> Your browser does not support the canvas element needed to play this game :(</p>
</canvas>
</body>
</html>
3 notes:
fillStyle does not cause your canvas to be filled. It means that when you fill a shape it will be filled with that color. Therefore you need to write canvas.fillRect( xPos, yPos, width, height).
Wait until your image actually loads, otherwise the rendering may be inconsistent or buggy.
Careful of cross-domain images used in your canvas - most browsers will throw a security exception and stop executing your code.
Wait till image loads as well:
var img = new Image();
img.onload = function() {
handleLoadedTexture(img);
};
img.src = "image.png";
function handleLoadedTexture(img) {
//call loop etc that uses image
};
Or maybe you were just missing
canvas.fill();
after
canvas.drawImage(smiley, xPos, yPos);

Categories

Resources