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;
})
Related
I'm trying to create drag-and-drop and I want my window to be scrolled in case I start the drag and reach the top / bottom of my page until I come out of the top/bottom "zone". So far what I have written works differently and I can't figure out a way to make it work the way I want.
Is there any way to do so using vanilla JS?
let mouseDown = true;
function mousedown() {
mouseDown = true;
}
function mouseup() {
mouseDown = false;
}
if (!document.querySelector(".arrow-timeline")) {
this.element.addEventListener('mousemove', function() {
let x, y;
function handleMouse(e) {
// Verify that x and y already have some value
if (x && y && mouseDown) {
// Scroll window by difference between current and previous positions
window.scrollBy(e.clientX - x, e.clientY - y);
}
// Store current position
x = e.clientX;
y = e.clientY;
}
// Assign handleMouse to mouse movement events
document.onmousedown = mousedown;
document.onmousemove = handleMouse;
document.onmouseup = mouseup;
})
}
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
}
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.
I'm trying to make a pan-and-zoom Canvas for use as a minimap in a game. I've set the stage to be draggable so the player can move around with the mouse, as well as move individual objects on the layers of the stage. However, I don't want to be able to drag the stage into the surrounding white space. In other words, I only want to allow panning while zoomed in so you never encounter that white space. To try and constrain the stage, I've set up a dragBoundFunc:
dragBoundFunc: function(pos) {
return {
x: (pos.x < 0 ? 0 : pos.x > width ? width : pos.x),
y: (pos.y < 0 ? 0 : pos.y > height ? height : pos.y)
};
}
(Full JSFiddle example: http://jsfiddle.net/4Brry/)
I'm encountering two problems:
Firstly, the canvas is still able to move upwards and to the left.
Secondly, and more annoyingly, the constraints begin to misbehave when we begin to zoom.
When you zoom, the constraints don't take this fact into account. So, what if we add the stage offsets?
dragBoundFunc: function(pos) {
return {
x: ((ui.stage.getOffset().x+pos.x) < 0 ? 0 : pos.x > width ? width : pos.x),
y: ((ui.stage.getOffset().y+pos.y) < 0 ? 0 : pos.y > height ? height : pos.y)
};
}
(Full JSFiddle example: http://jsfiddle.net/2fLCd/)
This is a lot better, but now the view "snaps back" when you go too far. It would be nicer if it just stopped moving in the disallowed direction.
Anyone know how I could fix these issues?
Ok, I integrated the Zynga Scroller functionality with the KineticJS framework to get what I wanted.
Code in action
Let's step look at the code, which is an amalgamation of things I found online and wrote myself.
First, we generate the canvas using KineticJS:
var width = 700;
var height = 700;
var stage = new Kinetic.Stage({
container: 'container',
width: width,
height: height
});
var layer = new Kinetic.Layer({});
stage.add(layer);
/* I skipped some circle generation code. */
Then, we define some events that fire when dragging and dropping something on the layer. We'll use these to populate a global variable called somethingIsBeingDraggedInKinetic. We'll use this variable in the panning code of Zynga Scroller so the entire stage isn't moved around when you're dragging a KineticJS shape.
var somethingIsBeingDraggedInKinetic = false;
layer.on('dragstart', function(evt) {
// get the thing that is being dragged
var thing = evt.targetNode;
if( thing )
somethingIsBeingDraggedInKinetic = true;
});
layer.on('dragend', function(evt) {
// get the thing that is being dragged
var thing = evt.targetNode;
if( thing )
somethingIsBeingDraggedInKinetic = false;
});
Next up is the Zynga Scroller initialization code. The Zynga Scroller code handles input and transformations, and then passes on three values to a rendering function: top, left and zoom. These values are perfect for passing on to the KineticJS framework:
// Canvas renderer
var render = function(left, top, zoom) {
// Constrain the stage from going too far to the right
if( (left + (width / zoom)) > width )
left = width - (width / zoom );
// Constrain the stage from going too far to the left
if( (top + (height / zoom)) > height )
top = height - (height / zoom );
stage.setOffset(left, top);
stage.setScale(zoom);
stage.draw();
};
// Initialize Scroller
this.scroller = new Scroller(render, {
zooming: true,
animating: false,
bouncing: false,
locking: false,
minZoom: 1
});
After that, we need to position the Zynga Scroller correctly. I'll admit that this part is a bit of black magic for me. I copied the rest of the code over from the "asset/ui.js" file.
var container = document.getElementById("container");
var rect = container.getBoundingClientRect();
scroller.setPosition(rect.left + container.clientLeft, rect.top + container.clientTop);
scroller.setDimensions(700, 700, width, height);
Finally, I copied over the panning code as well, and added some code that checks if the KineticJS framework is moving something:
var mousedown = false;
container.addEventListener("mousedown", function(e) {
if (e.target.tagName.match(/input|textarea|select/i)) {
return;
}
scroller.doTouchStart([{
pageX: e.pageX,
pageY: e.pageY
}], e.timeStamp);
mousedown = true;
}, false);
document.addEventListener("mousemove", function(e) {
if (somethingIsBeingDraggedInKinetic)
return;
if (!mousedown) {
return;
}
scroller.doTouchMove([{
pageX: e.pageX,
pageY: e.pageY
}], e.timeStamp);
mousedown = true;
}, false);
document.addEventListener("mouseup", function(e) {
if (!mousedown) {
return;
}
scroller.doTouchEnd(e.timeStamp);
mousedown = false;
}, false);
Oh, and the zoom handler.
container.addEventListener(navigator.userAgent.indexOf("Firefox") > -1 ? "DOMMouseScroll" : "mousewheel", function(e) {
scroller.doMouseZoom(e.detail ? (e.detail * -120) : e.wheelDelta, e.timeStamp, e.pageX, e.pageY);
}, false);
This is perfect as a basis for a zoomable map!
So far, it's mobile touch pencil tool, which can draw perfectly, but I'm wondering... how would I make it so that it draws a completely straight line instead of a basic pencil tool which can draw outside the line?
Here's the code so far:
// "Draw Line" Button
$(document).ready(function () {
initialize();
});
// works out the X, Y position of the click inside the canvas from the X, Y position on the page
function getPosition(mouseEvent, sigCanvas) {
var x, y;
if (mouseEvent.pageX != undefined && mouseEvent.pageY != undefined) {
x = mouseEvent.pageX;
y = mouseEvent.pageY;
} else {
x = mouseEvent.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = mouseEvent.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
return { X: x - sigCanvas.offsetLeft, Y: y - sigCanvas.offsetTop };
}
function initialize() {
// get references to the canvas element as well as the 2D drawing context
var sigCanvas = document.getElementById("canvasSignature");
var context = sigCanvas.getContext("2d");
context.strokeStyle = 'Black';
// This will be defined on a TOUCH device such as iPad or Android, etc.
var is_touch_device = 'ontouchstart' in document.documentElement;
if (is_touch_device) {
// create a drawer which tracks touch movements
var drawer = {
isDrawing: false,
touchstart: function (coors) {
context.beginPath();
context.moveTo(coors.x, coors.y);
this.isDrawing = true;
},
touchmove: function (coors) {
if (this.isDrawing) {
context.lineTo(coors.x, coors.y);
context.stroke();
}
},
touchend: function (coors) {
if (this.isDrawing) {
this.touchmove(coors);
this.isDrawing = false;
}
}
};
// create a function to pass touch events and coordinates to drawer
function draw(event) {
// get the touch coordinates. Using the first touch in case of multi-touch
var coors = {
x: event.targetTouches[0].pageX,
y: event.targetTouches[0].pageY
};
// Now we need to get the offset of the canvas location
var obj = sigCanvas;
if (obj.offsetParent) {
// Every time we find a new object, we add its offsetLeft and offsetTop to curleft and curtop.
do {
coors.x -= obj.offsetLeft;
coors.y -= obj.offsetTop;
}
// The while loop can be "while (obj = obj.offsetParent)" only, which does return null
// when null is passed back, but that creates a warning in some editors (i.e. VS2010).
while ((obj = obj.offsetParent) != null);
}
// pass the coordinates to the appropriate handler
drawer[event.type](coors);
}
// attach the touchstart, touchmove, touchend event listeners.
sigCanvas.addEventListener('touchstart', draw, false);
sigCanvas.addEventListener('touchmove', draw, false);
sigCanvas.addEventListener('touchend', draw, false);
// prevent elastic scrolling
sigCanvas.addEventListener('touchmove', function (event) {
event.preventDefault();
}, false);
}
else {
// start drawing when the mousedown event fires, and attach handlers to
// draw a line to wherever the mouse moves to
$("#canvasSignature").mousedown(function (mouseEvent) {
var position = getPosition(mouseEvent, sigCanvas);
context.moveTo(position.X, position.Y);
context.beginPath();
// attach event handlers
$(this).mousemove(function (mouseEvent) {
drawLine(mouseEvent, sigCanvas, context);
}).mouseup(function (mouseEvent) {
finishDrawing(mouseEvent, sigCanvas, context);
}).mouseout(function (mouseEvent) {
finishDrawing(mouseEvent, sigCanvas, context);
});
});
}
}
// draws a line to the x and y coordinates of the mouse event inside
// the specified element using the specified context
function drawLine(mouseEvent, sigCanvas, context) {
var position = getPosition(mouseEvent, sigCanvas);
context.lineTo(position.X, position.Y);
context.stroke();
}
// draws a line from the last coordiantes in the path to the finishing
// coordinates and unbind any event handlers which need to be preceded
// by the mouse down event
function finishDrawing(mouseEvent, sigCanvas, context) {
// draw the line to the finishing coordinates
drawLine(mouseEvent, sigCanvas, context);
context.closePath();
// unbind any events which could draw
$(sigCanvas).unbind("mousemove")
.unbind("mouseup")
.unbind("mouseout");
}
Thanks,
Wardenclyffe
Anchor the pen down point at first touch and then wherever the next touch is draw a straight line from the first touch to the new touch. Have a floating check mark above the new touch to accept the new line. If the check mark isn't selected and there is another touch then remove the previous line, draw a new line, and show another check mark to save the new line. Provide an (x) above the original pen down point to cancel the line drawing tool.
The difference between a straight line tool and a pencil tool is that the pencil draws as many points as possible, and connects them with (possibly smoothed) line segments. A straight line tool connects a small number of lines with relatively large line segments.
One option would be for the pencil tool to lay down points as long as the finger touches the screen. And then the line tool draws from tap to tap. Alternately, if you tap and slide, the line tool could rubber-band the line from the touch point to the drag point, and then keep the last point before the touch lifts.