Changing variable to draw on Canvas - javascript

I'm having a few issues with canvas and changing a variable to assign a different mousestate. I have shown a section of my code below.
$("#shape").click(function() { //Runs the drawbackground function on click
mouse_state = "fill_shape";
console.log("shape: " + mouse_state);
});
$("#paint").click(function() { //Runs the drawbackground function on click
console.log('hi');
mouse_state = "paint";
console.log("paint: " + mouse_state);
});
var mouse_state = "paint";
if (myCanvas) { //Checks is canvas exists and/or is supported by the browser
var isDown = false; //Stores the current status of the mouseclick, default is not down
var ctx = myCanvas.getContext("2d"); //Stores the 2d context of the canvas
var canvasX, canvasY; //Initialises variables canvasX and canvasY
if (mouse_state == "paint"){
$(myCanvas).mousedown(function(e) { //When the user clicks on the canvas, this function runs
e.preventDefault(); //Prevents the cursor from changing when clicking on the canvas
isDown = true; //Sets the mouseclick variable to down
ctx.beginPath(); //Begins the path
canvasX = e.pageX - myCanvas.offsetLeft; //Stores the mouse position on the x axis by subtracting the distance from the left edge of the canvas from the position from the left edge of the document.
canvasY = e.pageY - myCanvas.offsetTop; //Stores the mouse position on the y axis by subtracting the distance from the top edge of the canvas from the position from the top edge of the document.
ctx.moveTo(canvasX, canvasY); //Sets the position which the drawing will begin
}).mousemove(function(e) {
if (isDown != false) { //On the mousemouse the line continues to be drawn as the mouseclick variable will still be set as false
canvasX = e.pageX - myCanvas.offsetLeft; //Similar to above
canvasY = e.pageY - myCanvas.offsetTop;
ctx.lineTo(canvasX, canvasY); //Stores the information which should be drawn
ctx.strokeStyle = current_colour; //Sets the colour to be drawn as the colour stored in the current colour variable
ctx.stroke(); //Draws the path given
}
}).mouseup(function(e) { //When the mouse click is released, do this function...
isDown = false; //Sets the mouseclick variable to false
ctx.closePath(); //Closes the path
});
}
else if(mouse_state == "fill_shape"){
//Checks is canvas exists and/or is supported by the browser
$(myCanvas).mousedown(function(ev) { //When the user clicks on the canvas, this function runs
console.log("1" + mouse_state);
ev.preventDefault(); //Prevents the cursor from changing when clicking on the canvas
isDown = true; //Sets the mouseclick variable to down
ctx.beginPath(); //Begins the path
canvasX = ev.pageX - myCanvas.offsetLeft; //Stores the mouse position on the x axis by subtracting the distance from the left edge of the canvas from the position from the left edge of the document.
canvasY = ev.pageY - myCanvas.offsetTop; //Stores the mouse position on the y axis by subtracting the distance from the top edge of the canvas from the position from the top edge of the document.
ctx.moveTo(canvasX, canvasY); //Sets the position which the drawing will begin
}).mousemove(function(ev) {
if (isDown != false) { //On the mousemouse the line continues to be drawn as the mouseclick variable will still be set as false
canvasX = ev.pageX - myCanvas.offsetLeft; //Similar to above
canvasY = ev.pageY - myCanvas.offsetTop;
ctx.lineTo(canvasX, canvasY); //Stores the information which should be drawn
ctx.strokeStyle = current_colour; //Sets the colour to be drawn as the colour stored in the current colour variable
ctx.stroke(); //Draws the path given
}
}).mouseup(function(ev) { //When the mouse click is released, do this function...
ctx.fillStyle = current_colour;
ctx.fill();
isDown = false; //Sets the mouseclick variable to false
ctx.closePath(); //Closes the path
});
}};
The drawing of the canvas works fine and the two different 'mouse_states' work fine with the first (paint) simply drawing the lines or shapes and the second (fill_shape) drawing shapes and then filling them in using ctx.fill.
The mouse_state variable is initialised as "paint" so the paint function runs and when I change it to "shape_fill" the shape fill function runs fine. The problem arises when changing between the two states using the buttons to change the variable name. The console log shows that the variable name changes as expected but it just doesn't seem to take any affect and sticks with the initial value of the mouse_state variable. I would appreciate any help or tips with this.

You're running the if statements at the wrong time - they're executing on page load, and subsequently only binding the first set of events.
Instead, bind only one set of events, and check the variable within them and run the corresponding code:
if (myCanvas) { //Checks is canvas exists and/or is supported by the browser
var isDown = false; //Stores the current status of the mouseclick, default is not down
var ctx = myCanvas.getContext("2d"); //Stores the 2d context of the canvas
var canvasX, canvasY; //Initialises variables canvasX and canvasY
$(myCanvas).mousedown(function(e) { //When the user clicks on the canvas, this function runs
if (mouse_state == "paint") {
//code for paint
} else if (mouse_state == "fill_shape") {
//code for fill_shape
}
}); //etc, for other events
}

Related

Html Canvas - move object on X or Y axis only

I'm having a problem with moving an object on the canvas but only on x or y-axis once at a time.
Idea:
The user can drag an object with CTRL / Shift pressed and then he's able to move an object on the x-axis or y-axis only. I move an object on an axis that I'm further away from starting position. This feature is present in most vector software (Corel, Inkscape, etc.).
On this video you can see what I mean:
https://www.youtube.com/watch?v=9AheCfh13Aw
To be honest - I don't know where to start. I guess I have to track the origin position of the object while dragging so I can check which axis should be locked while mouse movement.
Forked jsFiddle I'm using for developing:
https://jsfiddle.net/sores/1emj47q9/38/
Mouse movement event listener:
canvas.addEventListener('mousemove', function(e) {
if (myState.dragging) {
console.warn('mouseMove and dragging');
console.warn('object position: ', myState.selection);
var mouse = myState.getMouse(e);
console.warn('mouse: ', mouse);
// We don't want to drag the object by its top-left corner, we want to drag it
// from where we clicked. Thats why we saved the offset and use it here
// get the very first position of the object?
myState.selection.x = mouse.x - myState.dragoffx;
// y locked
// myState.selection.y = mouse.y - myState.dragoffy;
console.warn('new position: ', myState.selection);
myState.valid = false; // Something's dragging so we must redraw
}
}, true);
If anyone is familiar with such a thing I will be very grateful for any tips.
Thanks!
This answer is posted as code untested. The theory should point you in the right direction, though. I've commented the additions I've made to hopefully make it clearer but the basic rundown is;
Get the current mouse position
Move the mouse
Get the new mouse position
Compare the new mouse position against the old one
The biggest difference in positions determines the axis
When you've determined the direction (such as y), reset the position of the other axis (in this case, x) as it'll probably move slightly. I haven't included code for this.
// A reference to the previous mouse position
let initialMousePosition = {
x: 0,
y: 0
}
// A reference to locked axis that can be set or unset later
let dragLock: {
x: false,
y: false
}
// A reference to the control key
let ctrlPressed = false;
// Moved previousMousePosition logic in to on mouse down for increased reliability
canvas.addEventListener('mousedown', function(e){
var mouse = myState.getMouse(e);
initialMousePosition.x = mouse.x;
initialMousePosition.y = mouse.y;
})
canvas.addEventListener('mousemove', function(e) {
if (myState.dragging) {
var mouse = myState.getMouse(e);
// check for 0 positions to skip the first tick
if (initialMousePosition.x > 0 || initialMousePosition.y > 0) {
// compare previous mouse x & y positions to the current mouse positions
// assume the biggest difference is the direction of dragging
if (mouse.x - initialMousePosition.x > mouse.y - initialMousePosition.y) {
dragLock.y = true;
} else {
dragLock.x = true;
}
}
if (!dragLock.x || !ctrlPressed) myState.selection.x = mouse.x - myState.dragoffx;
if (!dragLock.y || !ctrlPressed) myState.selection.y = mouse.y - myState.dragoffy;
myState.valid = false; // Something's dragging so we must redraw
}
}, true);
//
canvas.addEventListener('keydown', function(e) {
if (event.which == "17")
ctrlPressed = true;
});
canvas.addEventListener('keyup', function(e) {
ctrlPressed = false;
})

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.

Canvas get percent of filled area

I've got a canvas where the user can draw, but I need to detect the percent of filled area. The user has to continue to draw during the checking.
I already have my canvas and the user can draw on it. I got here a little function to check if some pixels aren't blank, but it's too slow and the user can't draw anymore.
Have you any idea of how I can do this?
UPDATE :
for drawing on my canvas I'm using lineTo() :
$.fn.drawMouse = function() {
var clicked = 0;
var start = function(e) {
clicked = 1;
ctx.beginPath();
x = e.pageX;
y = e.pageY;
ctx.moveTo(x,y);
};
var move = function(e) {
if(clicked){
x = e.pageX;
y = e.pageY;
ctx.lineTo(x,y);
ctx.stroke();
}
};
var stop = function(e) {
clicked = 0;
};
$(this).on("mousedown", start);
$(this).on("mousemove", move);
$(window).on("mouseup", stop);
};
Wouldn't it be better, if you added a "x percent covered" value to a percentFilled variable each time the user draws something?
Example:
Suppose you draw by creating circles. You can calculate the area of the circle, then perhaps check, if some of the pixels aren't already filled. Add (areaOfCircle (in %, or maybe px^2) - areaAlreadyFilled (in %, or maybe px^2) ) to your percentFilled variable and you get the current filled percentage. Everytime you paint something, it adds the painted area. You can completely avoid calculating the covered part of the canvas as a whole entirely.

Canvas pixel coordinate offset after scroll bar is used

I'm using a canvas at the top of a page. Im writing out the pixel coordinates from the canvas at a mousemove event. Normally, the most bottom Y-value is equal to the canvas height, i e. 700px. But after scrollbar is used to scroll down a bit on the page, the bottom y-coordinate in the canvas will change accordingly to say 400px instead.
document.getElementById("mapcanvas").addEventListener("mousemove", getPosition, false);
function getPosition(event)
{
var x = event.x;
var y = event.y;
var canvas = document.getElementById("mapcanvas");
x -= canvas.offsetLeft;
y -= canvas.offsetTop;
document.getElementById("canvascoords").innerHTML = "Canvascoords: "+ "x=" + x + ", y=" + y;
}
... Where "mapcanvas" is my div holding the canvas.
Any ideas of making the y-coordinate independent from usage of scroll bar so that the lower y-coordinate always i 700px?
As you've discovered, canvas.offsetLeft & canvas.offsetTop do not account for scrolling.
To account for scrolling, you can use canvas.getBoundingClientRect
var BB=canvas.getBoundingClientRect();
var x=event.clientX-BB.left;
var y=event.clientY-BB.top;
BTW, you might want to fetch a reference to the canvas element just once outside your getPosition() function instead of fetching it repeatedly inside getPosition().
var canvas = document.getElementById("mapcanvas");
function getPosition(event){
...

RaphealJS Resize and mouse shift

I have been using RaphealJS to create a vector drawing tool, I have all the drawing completed and working
my issues comes in when I resize the browser window and try to draw the mouse pointer is off from the location that is being drawn.
I use the mouse move event on the browser and draw lines , Like so
$(document).mousemove(function(e){
if (IE) {
var dh = $("#details").height();
var dw = $("#details").width();
xx = e.offsetX;
yy = e.offsetY;
} else {
var offset = $("#workcanvas").offset();
xx = e.pageX - offset.left;
yy = e.pageY - offset.top;
}
if (lineObject != null) {
lineObject.updateEnd(xx, yy);
} else {
lineObject = Line(xx, yy, xx, yy, MasterCanvas);
}
});
I create my canvas and background image
var MasterCanvas = Raphael($("#workcanvas").attr("id"));
var MasterBGImage = MasterCanvas.image(imgPath, 0, 0, $("#workcanvas").width(),$("#workcanvas").height());
MasterCanvas.setViewBox(0, 0, $("#workcanvas").width(), $("#workcanvas").height(), true);
and in my window resize event I tried this
MasterCanvas.setSize($("#workcanvas").width(), $("#workcanvas").height());
Now I have beat my head against this for a few days to no avail. Please note: I can the drawing function work, and as long as the window does not resize every thing is great but when the page resizes the drawing point is off.
Just in case anyone else has this problem, it turns out to be a viewBox problem, I had to calculate the mouse position based on the viewBox coordinates not the screen so my original code becomes:
$(document).mousemove(function(e){
var uupos = MasterCanvas.canvas.createSVGPoint();
uupos.x = e.clientX;
uupos.y = e.clientY;
var ctm = MasterCanvas.canvas.getScreenCTM();
if (ctm = ctm.inverse())
uupos = uupos.matrixTransform(ctm);
x = uupos.x;
y = uupos.y;
if (lineObject != null) {
lineObject.updateEnd(x, y);
} else {
lineObject = Line(x, y, x, y, MasterCanvas);
}
});
Edit:
Looks like this solution is SVG only though and it does not work in IE8 which is a requirement for me - any ideas.
Is there something like viewBox coordinates in VML

Categories

Resources