Canvas variable not updating when link is clicked - javascript

See link: http://jndgn.com/colortheory/bezold.html
When clicking the 'Bigger Stripes' link in the nav I'm attempting to simply update the number of colored stripes in the canvas below it. The variable 'stripeNum' is set at 100 and when clicked should change to 20 via:
$(function(){
$("#stripes").on("click",function(e){
e.preventDefault();
if(stripeNum == 100) {
stripeNum = 20;
} else {
stripeNum = 100;
}
});
})
...but when clicked, no action occurs. Do I somehow need to get the canvas to update itself? A bit of an HTML5 rookie here. Thanks in advance for any thoughts.
JSFiddle attached for easier code transparency.

You need to re-draw the canvas, as well as clearing it when you do this. You could simply make this adjustment and you'd be good:
First, wrap the canvas drawing you have in a function:
var contextFill = function() {
context.clearRect(0, 0, canvas.width, canvas.height); // Clear on draw
for (var i = 0; i < stripeNum + 1; i++) {
context.beginPath();
context.rect(0, i * canvas.height / stripeNum, canvas.width, canvas.height / (2 * stripeNum));
context.fillStyle = randomColor;
context.fill();
}
}
Then, call contextFill() on document ready. The only addition I made to the above is context.clearRect, to clear the canvas.
Finally, also call it when your bigger stripes is called, e.g.:
...
if (stripeNum == 100) {
stripeNum = 20;
} else {
stripeNum = 100;
}
contextFill();
If you want to update canvas on window resize, you can watch the resize event on the window. The downside is it can occur many times depending on how the user is resizing. You can use a library like lodash to ensure that the function only is called every N milliseconds. For example:
window.addEventListener('resize', _.debounce(function() {
contextFill();
}, 500));

Related

I want to start a new animation every time I click with requestFrameAnimation

I'm having multiple issues.
Everytime I click the animation goes faster. SOLVED #Jorge Fuentes González
Everytime I click the
last animation stops moving SOLVED #Kaiido
I have changed about everything I could think of around and still the same issue. Any help would be appreciated. Thanks!
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
function step() {
frameCount++;
if (frameCount < 30) {
window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
step();
}
==============================
JS Fiddle:
https://jsfiddle.net/HYUTS/q4fazt6L/9/
=======================================
Each time you click, you call step();, which will call window.requestAnimationFrame(step);, which will call step() the next animation frame. I don't see any stop point so the loop will be called forever.
So, when you call step() the first time, step() will be called continuously for ever, and if you click again, another step() "line" will be called a second time which will call window.requestAnimationFrame(step); for ever again, so now you will have two "lines" calling step(). That's why the animation goes faster, because on each animation frame step() will be called twice, doubling the calculations.
What you have to do is to check if the animation is already running (with a flag) and do not run it again, or to window.cancelAnimationFrame(ID) before starting the step() loop again. Note that on each click you must restart the variables that control the animation, like frameCount and currentLoopIndex
function drawFrame(frameX, frameY, canvasX, canvasY) {
ctx.drawImage(img,
frameX * width, frameY * height,
width, height,
x_click, y_click,
scaledWidth, scaledHeight);
}
// Number of frames in animation
var cycleLoop = [3, 2, 1, 0, 7, 6, 5];
// Position of sprite in sheet
var currentLoopIndex = 0;
var frameCount = 0;
var animationid = null;
function step() {
frameCount++;
if (frameCount < 30) {
animationid = window.requestAnimationFrame(step);
return;
}
frameCount = 0;
// ctx.clearRect(0, 0, canvas.width, canvas.height);
drawFrame(cycleLoop[currentLoopIndex++], 0, 0, 0);
// Starts animation over
if (currentLoopIndex >= cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
cycleLoop.reverse();
// Reseting position of which sprite to use
currentLoopIndex = 0;
}
animationid = window.requestAnimationFrame(step);
}
canvas.addEventListener("mousedown", getPosition, false);
function getPosition(event) {
x_click = event.x;
y_click = event.y;
x_click -= canvas.offsetLeft * 10;
y_click -= canvas.offsetTop * 10;
frameCount = currentLoopIndex = 0;
window.cancelAnimationFrame(animationid);
step();
}
First step in your situation, is to create different objects for every animatables, so they can be drawn and updated independently.
After, you will have to split your logic in several parts.
A basic setup is to have one main loop that runs constantly in the background, and which will call all higher level objects update function, then all the drawing functions.
It's in these higher level methods that you will do the checks as to whether they should actually be discarded or not. The main loop doesn't have to take care of it.
In the example below, I created a class for your animatable objects. These objects will now have their own status, and will be able to update as they wish independently of others.
With this setup, adding a new Object in the scene is just a matter of pushing it in an Array.
// Our Animatable class (ES5 style...)
// Each object as its own frameCount and its own loopIndex
function Animatable(x, y) {
this.x = x;
this.y = y;
this.frameCount = 0;
this.loopIndex = 0;
this.cycleLoop = [3, 2, 1, 0, 7, 6, 5];
}
Animatable.prototype = {
update: function() {
this.frameCount++;
if (this.frameCount < 30) {
return;
}
this.frameCount = 0;
this.loopIndex++
if (this.loopIndex >= this.cycleLoop.length) {
// If you want to loop back in oposite direction after full animation
this.cycleLoop.reverse();
// Reseting position of which sprite to use
this.loopIndex = 0;
}
},
draw: function() {
// check the image is loaded
if (!img.naturalWidth) return;
var frameX = this.cycleLoop[this.loopIndex];
ctx.drawImage(img,
frameX * width, 0,
width, height,
this.x - scaledWidth/2, this.y - scaledHeight/2,
scaledWidth, scaledHeight);
}
};
// the main anim loop, independent
function startAnimLoop() {
animloop();
function animloop() {
requestAnimationFrame(animloop);
// updates
animatables.forEach(update);
// drawings
ctx.clearRect(0,0,canvas.width, canvas.height);
animatables.forEach(draw);
}
function update(animatable) {
animatable.update();
}
function draw(animatable) {
animatable.draw();
}
}
// one image for all
var img = new Image();
img.src = 'https://imgur.com/u2hjhwq.png';
img.onload = startAnimLoop;
// here we will hold all our objects
var animatables = [new Animatable(50, 50)]; // start with a single one
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
// some constant from OP's fiddle
var scale = 1.5;
var width = 100; // Bigger numbers push left <-, smaller right ->
var height = 100;
var scaledWidth = scale * width;
var scaledHeight = scale * height;
canvas.onclick = function(evt) {
var rect = canvas.getBoundingClientRect();
var x = evt.clientX - rect.left;
var y = evt.clientY - rect.top;
// we simply create a new object ;-)
animatables.push(new Animatable(x, y));
};
canvas{border:1px solid}
<canvas id="canvas" width="500" height="500"></canvas>
window.requestAnimationFrame is still running when you click again, and when you click you add another tick per frame to your animation, doubling your speed, as step() is called two times each frame now. You should cancel the previous animation frame when clicking again, using window.cancelAnimationFrame()
https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelAnimationFrame
Like this:
...
var animationID;
//in step() save the id in every call
function step() {
...
animationID = window.requestAnimationFrame(step);
...
}
//In getPosition cancel the current animation
function.getPosition(event) {
...
window.cancelAnimationFrame(animationId);
...
}
And if you want multiple animations running, create an object for each and make the function step() their property, then run window.requestAnimationFrame(this.step) inside of step(). You'd also have to save every variable needed for the animation like currentLoopIndex as part of the object.

Adding listener on canvas points

Hey I have a simple triangle in canvas, and I would like to add listener to each triangle's point, so that if user click each point an action will happen.
I would like to know if such procedure is possible in canvas, if not what my alternatives? so my main question is as follows:
Can I add a listener on canvas single points? if not what are my alternatives?
Update:
I have tried my luck and succeed in adding click event on the all canvas and then get current points of mouse click,but my solution isn't final and very not precious.
Can I create an area around each point which is clicked so the user will not have to be precious in his clicks?
http://codepen.io/Barak/pen/VadQYm
$(function(){
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var triangle = [{ x: 58, y: 845 }, { x: 984, y: 845 }, { x: 521, y: 41 }];
drawTriangle(triangle);
function drawTriangle(t) {
ctx.beginPath();
ctx.moveTo(t[0].x, t[0].y);
ctx.lineTo(t[1].x, t[1].y);
ctx.lineTo(t[2].x, t[2].y);
ctx.closePath();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
}
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="canvas-wrapper">
<canvas id="canvas" width=1024 height=980></canvas>
</div>
There is no single point you could attach an event to in your canvas - it is just a drawing Api - a container for your Image (See W3School for further reference).
But if you have the coordinates, of course you can always detect the clicked position in your Canvas.
I made you a fiddle, to show the possible solution.
canvas.addEventListener("mousedown", doMouseDown, false);
function doMouseDown(e) {
for (i = 0; i < triangle.length; ++i) {
if ((e.x triangle[i].x) && (e.y === triangle[i].y)) {
console.log('you hit an edge!');
}
}
}
Update:
If you want to create a tolerance, you have to define the plus and the minus of the actual position you want to accept. A simple and not elegant way could be the following:
function tolerance(number){
//define the tolerance here
var tolerance = 15;
//all the accepted numbers within the tolerance will be in this array
var numberArray=[];
for(i=0;i<(tolerance)*2;++i){
if(i >= tolerance){
if(i!=tolerance){
numberArray[i] = numberArray[i-1]-1;
}else{
numberArray[i] = number -1;
}
}else{
if(i!=0){
numberArray[i] = numberArray[i-1]+1;
}else{
numberArray[i] = number +1;
}
}
}
//don't forget to put the actual number in the array
numberArray.push(number);
return numberArray;
}
You can now change the doMouseDown function to look something like this:
function doMouseDown(e) {
for (k = 0; k < triangle.length; ++k) {
if ((tolerance(triangle[k].x).indexOf(e.x)!=-1) && (tolerance(triangle[k].y).indexOf(e.y)!=-1)) {
console.log('you hit an edge!');
}
}
}

JavaScript - how to draw on the canvas and get an image to follow the mouse

I've gotten a lot of help from this site, but I seem to be having a problem putting all of it together. Specifically, in JS, I know how to
a) draw an image onto canvas
b) make a rectangle follow the cursor (Drawing on a canvas) and (http://billmill.org/static/canvastutorial/ball.html)
c) draw a rectangle to use as a background
What I can't figure out is how to use a rectangle as the background, and then draw an image (png) on the canvas and get it to follow the cursor.
What I have so far looks like this:
var canvas = document.getElementByID('canvas');
var ctx = canvas.getContext('2d');
var WIDTH = canvas.width;
var HEIGHT = canvas.height;
var bgColor = '#FFFFFF';
var cirColor = '#000000';
clear = function() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
}
drawIMG = function(x,y,r) {
ctx.fillStyle = cirColor;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}
draw = function() {
ctx.fillStyle = bgColor;
clear();
ctx.fillRect(0, 0, WIDTH, HEIGHT);
drawIMG(150, 150, 30);
drawIMG(300, 500, 12);
};
draw();
This will draw in the HTML5 canvas element, the height and width of which are specified in the HTML and so are variable, with a white rectangle the size of the canvas beneath two black circles at (150,150) and (300,500). It does that perfectly well.
However, I don't know how to also make JS draw a .png on top of that that follows the cursor. Like I said, I've been able to do most of the steps individually, but I have no idea how to combine them. I know, for instance, that I have to do
img = new Image();
and then
img.src = 'myPic.png';
at some point. They need to be combined with position modifiers like
var xPos = pos.clientX;
var yPos = pos.clientY;
ctx.drawImage(img, xPos, yPos);
But I have no idea how to do that while maintaining any of the other things I've written above (specifically the background).
Thanks for your patience if you read through all of that. I have been up for a while and I'm afraid my brain is so fried I wouldn't recognize the answer if it stripped naked and did the Macarena. I would appreciate any help you could possibly send my way, but I think a working example would be best. I am an initiate in the religion of programming and still learn best by shamelessly copying and then modifying.
Either way, you have my optimistic thanks in advance.
First off, I've made an animated purple fire follow the mouse. Click (edit doesn't exist anymore)here to check it out.
Before you continue, I recommend you check out these websites:
http://www.williammalone.com/articles/create-html5-canvas-javascript-sprite-animation/
William talks about the basic techniques of canvas animations
http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
Paul Irish talks about a recursive animation function that turns at 60 fps.
Using both of their tutorials is pretty a good start for animation.
Now from my understanding you want one 'background' and one animation that follows the cursor. The first thing you should keep in mind is once you draw on your canvas, whatever you draw on, gets replaced. So the first thing I notice that will cause performance issues is the fact you clear your whole canvas, and not what needs to be cleared.
What you need to do is memorize the position and size of your moving element. It doesn't matter what form it takes because your clearRect() should completely remove it.
Now you're probably asking, what if I draw on the rectangle in the background. Well that will cause a problem. You have two solutions. Either, (a) Clear the background and clear your moving animation and draw them back again in the same order or (b) since you know your background will never move, create a second canvas with position = absolute , z-index = -1 , and it's location the same as the first canvas.
This way you never have to worry about the background and can focus on the animation currently going on.
Now getting back to coding part, the first thing you'll want to do is copy Paul Irish's recursive function:
(function() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
Question then is, how to use it? If you go here you can check out how it was done:
function fireLoop()
{
window.requestAnimationFrame(fireLoop);
fire.update();
fire.render();
console.log('you spin me right round baby right round');
follow();
}
This is the loop I use. Every second Paul Irish's function will call the main loop. In this loop. I update the information choose the right animation that needs to be drawn and then I draw on the canvas (after having removed the previous element).
The follow function is the one that chooses the next coordinates for the animation. You'll have to change this part since, you don't want to move the canvas but move the animation. You can use the same code, but you need to apply location to where you want to draw on the canvas.
function follow()
{
$(fireCanvas).offset({
top: getTop(),
left: getLeft()
});
}
function getTop()
{
var off = $(fireCanvas).offset();
if(off.top != currentMousePos.y - $(fireCanvas).height() + 10)
{
if(off.top > currentMousePos.y - $(fireCanvas).height() + 10)
{
return off.top - 1;
}
else
{
return off.top + 1;
}
}
}
function getLeft()
{
var off = $(fireCanvas).offset();
if(off.left != currentMousePos.x - $(fireCanvas).width()/2)
{
if(off.left > currentMousePos.x - $(fireCanvas).width()/2)
{
return off.left - 1;
}
else
{
return off.left + 1;
}
}
}
var currentMousePos = { x: -1, y: -1 };
$(document).mousemove(function(event) {
currentMousePos.x = event.pageX;
currentMousePos.y = event.pageY;
});
If you want me to go into depth about anything specific let me know.

Manipulate an image on a canvas

I have to load an image on a canvas object and after clicking some buttons or sliding some sliders, I need to change some pixels on this canvas. Thats my code so far:
function CanvasImage(element, src) {
var width, height;
var canvas = document.getElementById(element);
var ctx = canvas.getContext("2d");
var img = new Image;
img.onload = function () {
width = canvas.width = img.width;
height = canvas.height = img.width;
ctx.drawImage(img, 0, 0, width, height)
};
img.src = src;
this.brightness = function (amount) {
var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var data = imgData.data;
for (var i = 0; i < data.length; i += 4) {
data[i] += amount;
data[i + 1] += amount;
data[i + 2] += amount
}
imgData.data = data;
ctx.putImageData(imgData, 0, 0)
};
this.transform = function (data) {
ctx.save();
this.brightness(data.brightness);
ctx.restore()
}
}
Thats I've just added the function to manipulate the brightness of the image. At the beginning, the image is loaded to the canvas element. That works fine for me. The function brightness is just adding the giving amount to the pixels. That also works. I think my problem is the transform function. I save the state of the canvas context and call my transform function. After this manipulation I restore the state. I would say, the context should look like the earlier loaded image.
My problem now is, that after changing a slider and returning it to the original state my image is still brighter or darker and doesn't have the original state of the image. Do you have any suggestions for me, please?
I appreciate every answer. :D
save and restore only saves the state of the canvas not image data. State are things such as styles, translations, text alignment and so forth.
In order to get back original content you need to either redraw the source or store the data to be restored with another canvas element (ie. off-screen canvas), or store the data as an array (getImageData()).
Also worth to notice is that putImageData by-passes the canvas' state and changes pixels directly independently.
My recommendation would be to keep the source available one way or another and always redraw it to canvas before a new alteration takes place. For reset simply redraw source and skip alteration.
ONLINE DEMO HERE
this.brightness = function (amount) {
ctx.drawImage(img, 0, 0, width, height)
/// no change, just exit
if (amount === 0) return;
/// ... snipped
};

Move html elements positions slow on iOS

I have a huge performance issue on iOS html5 webapp when I modify the position of multiple html elements in CSS. I would like also to move manually my elements. I do not want to use CSS transformation because it is not possible to stop the animation (we are making a highly responsive game).
My example works fine on a desktop browser (chrome, firefox, etc.), on Android. But it is very slow on an iPad 2 and an iPhone 4S (both running iOS 5.1).
Running the html5 code in a Phonegap app is better than directly in the browser but is still slow.
What do you suggest to improve things?
editable example
full screen example
First of all, if you want something that is not slow, avoid all jQuery call you can.
Here is how I would rewrite (really quickly) your code :
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById('canvas-test');
canvas.height = 500;
canvas.width = 500;
var context = canvas.getContext('2d');
// in this example, the fillstyle is always the same. no need to change it at every loop
context.fillStyle = "#FF0000";
var balls = [];
var ballcanvas = [];
var ballctx = [];
// create 30 balls in canvases
var eDivBody = document.getElementById('divbody');
for (var i = 0; i < 30; i++){
balls[i] = {
x : 250,
y : 100 + i * 2,
dx : 3, // direction
};
// create the canvas
var eBall = document.createElement('canvas');
eBall.id = 'ballcanvas' + i;
eBall.width = 75;
eBall.height = 75;
eDivBody.appendChild(eBall);
// some css
// no need for jQuery
eBall.style.position = "absolute";
eBall.style.left = balls[i].x + "px";
eBall.style.top = balls[i].y + "px";
eBall.style.backgroundColor = "#000000";
// associate the element to the ball, no need to go threw the DOM after
balls[i].element = eBall;
}
var ball_test = {
x : 250,
y : 300,
dx : 3 // direction
};
function loop(ball_test, balls, canvas, context, ballcanvas, ballctx){
//change of direction on the sides
if (ball_test.x > 400 || ball_test.x < 100)
ball_test.dx *= -1;
// movement
ball_test.x += ball_test.dx;
// the same for balls in canvases
// never use array.legth in a loop condition. put it in a variable then compare.
for (var i = 0, j = balls.length; i < j; i++){
// balls are following the test ball, no need to re-check the bounds
// we take the test ball direction
balls[i].dx = ball_test.dx;
//movement
balls[i].x += balls[i].dx;
// change left style - No need for jQuery
balls[i].element.style.left = balls[i].x + "px";
}
// display ball_test
displayBallTest(ball_test, canvas, context);
// Prefer the use of requestAnimationFrame
requestAnimFrame(function(){
loop(ball_test, balls, canvas, context, ballcanvas, ballctx);
});
};
// no need to recalculate Math.PI * 2 for every loop.
// do it just the first time then use the value
var pi2 = Math.PI * 2;
function displayBallTest(ball, canvas, context){
// clear canvas
// you don't need to clear all the canvas, just the zone where you now the ball is.
// must need some calculation to be the most efficient possible
context.clearRect(ball.x - 50 , ball.y - 50, 100, 100);
context.beginPath();
context.arc(ball.x, ball.y, 40, 0, pi2 );
context.fill();
};
// start main loop
loop(ball_test, balls, canvas, context, ballcanvas, ballctx);
I commented the code but here are what I did :
totally avoiding jQuery. No need, except maybe for the ready if you choose to not put your script at the end of the content
using requestAnimationFrame when possible
avoiding recalculation or reset of values when they are global
(Math.PI*2 , context.fillStyle ... )
avoiding the use of .length if for loop condition
But I think your problem come from the fact that you want to move 30 canvas elements instead of drawing theyre content into the main canvas.
iOS is known to be fast when you use Canvas Drawing.
For me, your performance problems will be resolved if you choose to draw on the main canvas instead of moving DOM elements.
One obvious thing you can do is cache your selector instead of executing it every time:
// some css
$('#ballcanvas' + i).css("position", "absolute");
$('#ballcanvas' + i).css("left", balls[i].x + "px");
$('#ballcanvas' + i).css("top", balls[i].y + "px");
$('#ballcanvas' + i).css("background-color", "#000000");
Should be something like:
var thisBall = $('#ballcanvas' + i)
thisBall.css("position", "absolute");
... rest of your code ....
Aside: Why bother using document.getElementById, when you already have Jquery $.

Categories

Resources