When I move my object from the left to the right my rectangle (where I am supposed to put the high score and lives) flickers? How can I fix this? I have the same problem when I use a fillText, the text flickers each time I go to the left or right with my paddle.
var canvas = document.getElementById("mijnCanvas");
var mijnObject = canvas.getContext("2d");
var afbeelding = new Image();
var balkX = (canvas.width/2)-50;
var balkY = canvas.height-100;
function makenBalkKort() {
mijnObject.drawImage(afbeelding, balkX, balkY, afbeelding.width, afbeelding.height);
}
afbeelding.src = "Afbeeldingen/BrickSmasher_Balk_Kort.png";
function tekenenObjecten() {
mijnObject.clearRect(0, 0, canvas.width, canvas.height);
makenBalkKort();
drawFrame();
}
setInterval(tekenenObjecten, 20);
window.addEventListener("keydown", function LinksOfRechts() {
mijnObject.clearRect(balkX, balkY, canvas.width, canvas.height);
var balkNaarX = 15;
var code = event.which || event.keyCode;
if(code == 37) {
if(balkX > 0) {
balkX -= balkNaarX;
}
}
else if(code == 39) {
if(balkX < canvas.width-afbeelding.width) {
balkX += balkNaarX;
}
}
mijnObject.drawImage(afbeelding, balkX, balkY, afbeelding.width, afbeelding.height);
});
function drawFrame() {
mijnObject.beginPath();
mijnObject.fillStyle = "#000000";
mijnObject.strokeStyle = "#000000";
mijnObject.rect(0, 750, canvas.width, 50);
mijnObject.fill();
mijnObject.stroke();
mijnObject.closePath();
}
Here is my image:
Your image is flickering because you're clearing a large rectangle in your keydown event handler, and part of this covers the bottom area. There is then a delay of up to 20 milliseconds before the function that redraws the whole board is queued.
A simple, but dirty fix would be to adjust the area being cleared in the keydown handler to only cover the image area:
mijnObject.clearRect(balkX, balkY, afbeelding.width, afbeelding.height);
However, a better solution would be to avoid making any changes to the canvas in any event handlers; that is, to remove the clearRect and drawImage calls from the event handler. To ensure that the canvas refreshes as soon as possible with the updated state, you can then use requestAnimationFrame instead of setInterval to call the tekenenObjecten function:
function tekenenObjecten() {
mijnObject.clearRect(0, 0, canvas.width, canvas.height);
makenBalkKort();
drawFrame();
requestAnimationFrame(tekenenObjecten);
}
requestAnimationFrame(tekenenObjecten);
Related
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.
I am working on a sketch tool with html canvas.
I am using a common algorithm for this, that uses the mousedown, mousemove, mouseup events.
mousedown
I beginPath(), and moveTo(// the mouse coordinates).
mousemove
I draw lineTo(// the mouse coordinates), and then stoke(// the line to render it)
mouseup
I do nothing, // closePath()
And I noticed that, calling the stroke method without first calling closePath or beginPath, will redraw or restroke all previous paths or lines, which makes them appear thicker than the define color.
without a transparent color its is barely noticeable, but the colors do appear thicker than they should be.
but with color with transparency|alpha e.g. rgba(). The most recent path or line respects the color's transparency, but all previous line due to the redraw, the transparent colored line overlap and that causes previous lines to get thicker in sequence or succession.
is there a way to avoid|prevent this behavior. thank in advance.
sample is below, try drawing really fast!
var cvs = document.querySelector("canvas");
cvs.width = cvs.parentElement.clientWidth;
var colorInput = document.querySelector("input");
var ctx = cvs.getContext("2d");
ctx.strokeStyle = "rgba(0, 0, 0, 0.4)"
ctx.lineWidth = 20;
onDraw(cvs, {
penDown: function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
ctx.strokeStyle = colorInput.value;
ctx.beginPath();
ctx.moveTo(x, y);
},
penMove: function(e) {
var x = e.pageX - this.offsetLeft;
var y = e.pageY - this.offsetTop;
ctx.lineTo(x, y);
ctx.stroke();
},
penUp: function() {
// ctx.closePath;
}
});
function onDraw(node, drawHandler, beginHandler, endHandler, outOfBoundHandler, sticky) {
var mouseDown = false, mouseOut = false;
if( typeof drawHandler === "object" ) {
var drawEvents = drawHandler;
drawHandler = get(drawEvents.penMove);
beginHandler = get(drawEvents.penDown);
endHandler = get(drawEvents.penUp);
outOfBoundHandler = get(drawEvents.penOff);
sticky = drawEvents.sticky;
}
function get(name) {
return typeof name === "string" ? drawEvents[ name ] : name;
}
node.addEventListener('mousedown', function(e) {
mouseDown = true;
beginHandler&&beginHandler.call(this, e);
});
node.addEventListener('mousemove', function(e) {
mouseDown&&drawHandler&&drawHandler.call(this, e);
});
node.addEventListener('mouseup', function(e) {
mouseDown = false;
endHandler&&endHandler.call(this, e);
});
node.addEventListener('mouseout', function(e) {
mouseDown&&outOfBoundHandler&&outOfBoundHandler.call(this, e);
if( !sticky ) {
mouseDown = false;
}
});
}
.wrapper { border: 1px solid #aaa }
<div class="wrapper">
<canvas border="1" width="600" hieght="400">Canvas is not supported</canvas>
<input type="text" value="rgba(0, 0, 0, 0.3)" placeholder="rgba(#, #, #, #)">
</div>
If no Path argument is passed to stroke and fill methods they will use the path currently being declared with the context's drawing methods.
const ctx = c.getContext('2d');
// starts Path declaration
ctx.moveTo(20, 20);
ctx.lineTo(30, 80);
ctx.stroke(); // first rendering
setTimeout(() => {
ctx.clearRect(0, 0, 300, 150); // even if we clear the canvas
ctx.lineTo(70, 20); // this will continue path declaration
setTimeout(() => {
ctx.stroke(); // and this will draw everything
}, 1000);
}, 1000);
<canvas id="c"></canvas>
The only ways to start a new path declaration (except for the first one) are to either reset the whole context (not good), or to use beginPath method.
const ctx = c.getContext('2d');
// starts Path declaration
ctx.moveTo(20, 20);
ctx.lineTo(30, 80);
ctx.stroke(); // first rendering
setTimeout(() => {
ctx.clearRect(0, 0, 300, 150);
ctx.beginPath(); // start a new Path declaration
ctx.moveTo(30, 80); // we need to move to the previous coords
ctx.lineTo(70, 20); // this will be alone
ctx.stroke(); // and this will draw only the new path
}, 1000);
<canvas id="c"></canvas>
About closePath, it's just a lineTo(last_point_in_current_path_declaration), and doesn't ends a path declaration in no way.
So for your problem, there are two strategies you can adopt :
keep only the last coordinates, and at every mousemove,
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(nextX, nextY);
keep all your coordinates in an array and redraw everything every time
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
coords.forEach(pt => ctx.lineTo(pt.x, pt.y));
ctx.stroke();
Personally, I prefer the second one, which allows some undo - redo, and to e.g change your pen's style.
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.
I loop over a part of a HTML video and simultaneously draw a Canvas with the current frame of the video.
When the Videos starts over, there is always 1 gray frame at the canvas.
If the loop region is long, its not a big problem, but for my needs these regions are maybe 0.5 seconds and then the canvas starts to flicker if you loop over and over again.
When drawing the canvas, I also tried different video properties (ended, loop, networkState, readyState) - doesnt help
I provided a jsfiddle to show you my problem. (just press play at the video)
https://jsfiddle.net/Lz17fnf3/2/
$('#v').on('timeupdate', function () {
if ($('#v')[0].currentTime > 2) {//Loop for one second
$('#v')[0].currentTime = 1;
}
var $this = $('#v')[0]; //cache
(function loop() {
if (!$this.paused && !$this.ended) {
drawCanvas();
setTimeout(loop, 1000 / 25); // drawing at 25fps
}
})();
});
function drawCanvas() {
var elem = document.getElementById('c');
var c = elem.getContext('2d');
var v = $('#v')[0];
$('#c').attr('width', v.videoWidth);
$('#c').attr('height', v.videoHeight);
if (v.readyState == 4) {
c.drawImage(v, 0, 0, v.videoWidth, v.videoHeight, 0, 0, v.videoWidth, v.videoHeight);
}
}
The reason it flickers is because when you assign the width or height to a canvas element, this action resets the entire context of the canvas, most likely that is causing the blank frame. Try moving all the canvas/context definitions outside the drawCanvas.
Something like:
var elem = document.getElementById('c');
var c = elem.getContext('2d');
var v = $('#v')[0];
// In order to set the canvas width & height, we need to wait for the video to load.
function init() {
if (v.readyState == 4) {
$('#c').attr('width', v.videoWidth);
$('#c').attr('height', v.videoHeight);
} else {
requestAnimationFrame(init);
}
}
init();
$('#v').on('timeupdate', function () {
if ($('#v')[0].currentTime > 2) { //Loop for one second
$('#v')[0].currentTime = 1;
}
var $this = $('#v')[0]; //cache
(function loop() {
if (!$this.paused && !$this.ended) {
drawCanvas();
setTimeout(loop, 1000 / 25); // drawing at 25fps
}
})();
});
function drawCanvas() {
c.drawImage(v, 0, 0, v.videoWidth, v.videoHeight, 0, 0, v.videoWidth, v.videoHeight);
}
I have this html5 drawing app that draws just fine on the canvas element. My problem is, I have an img of an eraser and I want the user to be able to click it and it will erase the canvas. Extra karma points if you can tell me also how to change the stroke color to white.
This is my html:
<div id="draw_area">
<canvas id="myCanvas" />
<p>browser sucks, here's links blah blah blah</p>
</canvas>
</div>
This is the JS to complement it:
var points = new Array();
var outlineImage = new Image();
function clearCanvas(){
context.clearRect(0, 0, canvas.width, canvas.height);
}
if (window.addEventListener) {
window.addEventListener('load', function () {
var canvas, context, tool;
function init() {
// Find the canvas element.
canvas = document.getElementById('imageView');
if (!canvas) {
alert('Error: I cannot find the canvas element!');
return;
}
if (!canvas.getContext) {
alert('Error: no canvas.getContext!');
return;
}
// Size the canvas:
canvas.width = 367;
canvas.height= 249;
// Get the 2D canvas context.
context = canvas.getContext('2d');
if (!context) {
alert('Error: failed to getContext!');
return;
}
// Pencil tool instance.
tool = new tool_pencil();
// Attach the mousedown, mousemove and mouseup event listeners.
canvas.addEventListener('mousedown', ev_canvas, false);
canvas.addEventListener('mousemove', ev_canvas, false);
canvas.addEventListener('mouseup', ev_canvas, false);
}
// This painting tool works like a drawing pencil which tracks the mouse
// movements.
function tool_pencil() {
var tool = this;
this.started = false;
// This is called when you start holding down the mouse button.
// This starts the pencil drawing.
this.mousedown = function (ev) {
context.beginPath();
context.moveTo(ev._x, ev._y);
tool.started = true;
};
// This function is called every time you move the mouse. Obviously, it only
// draws if the tool.started state is set to true (when you are holding down
// the mouse button).
this.mousemove = function (ev) {
if (tool.started) {
context.lineTo(ev._x, ev._y);
context.stroke();
}
};
// This is called when you release the mouse button.
this.mouseup = function (ev) {
if (tool.started) {
tool.mousemove(ev);
tool.started = false;
}
};
}
// The general-purpose event handler. This function just determines the mouse
// position relative to the canvas element.
function ev_canvas(ev) {
if (navigator.appName == 'Microsoft Internet Explorer' || navigator.vendor == 'Google Inc.' || navigator.vendor == 'Apple Computer, Inc.') { // IE or Chrome
ev._x = ev.offsetX;
ev._y = ev.offsetY;
} else if (ev.layerX || ev.layerX == 0) { // Firefox
ev._x = ev.layerX - this.offsetLeft;
ev._y = ev.layerY - this.offsetTop;
} else if (ev.offsetX || ev.offsetX == 0) { // Opera
ev._x = ev.offsetX;
ev._y = ev.offsetY;
}
// Call the event handler of the tool.
var func = tool[ev.type];
if (func) {
func(ev);
}
points.push(ev);
}
init();
}, false);
}
I think I need a redraw function, but I don't really know what I'm talking about in regards to this issue. Any insight is much appreciated!
Assuming your background color is white, which is what clearRect will give you, then all you need to do is change the stroke color to white when the user selects the eraser. This can easily be done with
context.strokeStyle = 'white';
See http://www.w3.org/TR/2dcontext/#fill-and-stroke-styles for more information about changing colors.
If your eraser image is drawn on the canvas, then you will have to detect the clicking of it in your mousedown or mouseup event handlers. If it is outside the canvas, then just add a onclick function to set the strokeStyle when it is clicked. However, if you want the eraser to erase everything then do
function clearCanvas(){
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
}
Your canvas variable was not available in the scope where clearCanvas is called. While there are other ways to fix this, simply do this:
function clearContext( ctx ){
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
and pass the context into your function when you call it.
However note that if you context is transformed in any way the above will not clear the entire visible region. To safeguard against this, you may want:
function clearContext( ctx ){
ctx.save();
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
ctx.restore();
}