I'm trying to build a canvas that i can drag using mouse movement. And I'm doing something wrong that i cannot understand cause seems to work at first and then there is like an incremental error that make the canvas move too fast.
Considering the following code,
window.onload = function() {
var canvas = document.getElementById("canvas");
var context = canvas.getContext('2d');
function draw() {
context.fillRect(25, 25, 100, 100);
}
function clear() {
context.clearRect(0, 0, canvas.width, canvas.height);
}
var drag = false;
var dragStart;
var dragEnd;
draw()
canvas.addEventListener('mousedown', function(event) {
dragStart = {
x: event.pageX - canvas.offsetLeft,
y: event.pageY - canvas.offsetTop
}
drag = true;
})
canvas.addEventListener('mousemove', function(event) {
if (drag) {
dragEnd = {
x: event.pageX - canvas.offsetLeft,
y: event.pageY - canvas.offsetTop
}
context.translate(dragEnd.x - dragStart.x, dragEnd.y - dragStart.y);
clear()
draw()
}
})
}
live example on Plunker https://plnkr.co/edit/j8QCxwDzXJZN2DKszKwm.
Can someone help me to understand what piece I'm missing?
The problem your code has is that each time you move the rectangle blablabla px relative to the dragStart position, the translate() method is not based on dragStart position, but your current position.
To fix this, you should add the following after calling the translate method:
dragStart = dragEnd;
So that your position is also based on current mouse position.
Related
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 have a bar drew in Canvas.
When user clicks anywhere in the bar I want to get the start position. Then, the user drags the mouse to any position and releases the mouse and I would get the last position.
I've been doing that for some time but I couldn't get the right events.
Here's my code.
<canvas id="demoCanvas" width="500" height="300"></canvas>
var stage = new createjs.Stage("demoCanvas");
var rect = new createjs.Shape();
rect.graphics.beginFill("#000").drawRect(0, 20, 200, 50);
rect.on('mousedown', function (mousedownEvent) {
var startX = mousedownEvent.rawX;
console.log('mousedown');
});
rect.on('mouseup', function(mouseupEvent) {
var stopX = mouseupEvent.rawX;
console.log('mouseup');
console.log(stopX);
});
stage.addChild(rect);
stage.update();
http://jsfiddle.net/noppanit/x0bdq3aa/
The event you are looking for is 'pressup', you may also need 'mouseleave'and 'mouseout'.
Strangely, this easeljs tutorial explicitly says that you can use the 'mouseup' event as you just did.
However, when you look to the docs about events attached to the Stage class and the ones attached to the DisplayObject class, there is no mention of this 'mouseup'.
About the 'pressup' event :
After a mousedown occurs on a display object, a pressup event will be generated on that object when that mouse press is released. This can be useful for dragging and similar operations.
var stage = new createjs.Stage("demoCanvas");
var rect = new createjs.Shape();
rect.graphics.beginFill("#000").drawRect(0, 20, 200, 50);
rect.on('mousedown', function (mousedownEvent) {
var startX = mousedownEvent.rawX;
snippet.log('mousedown');
});
rect.on('pressup', function(mouseupEvent) {
var stopX = mouseupEvent.rawX;
snippet.log('mouseup');
snippet.log(stopX);
});
stage.addChild(rect);
stage.update();
<script src="https://code.createjs.com/easeljs-0.8.1.min.js"></script>
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
<canvas id="demoCanvas" width="500" height="70"></canvas>
Check this code once, Here you will get x and y position continously.
function writeMessage(canvas, message) {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.font = '18pt Calibri';
context.fillStyle = 'white';
context.fillText(message, 10, 25);
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
var canvas = document.getElementById('demoCanvas');
var context = canvas.getContext('2d');
canvas.addEventListener('mousemove', function(evt) {
var mousePos = getMousePos(canvas, evt);
var message = 'Mouse position: ' + mousePos.x + ',' + mousePos.y;
writeMessage(canvas, message);
}, false);
check this fiddle-> http://jsfiddle.net/x0bdq3aa/3/
This is to get the mouse coordinates relative to Canvas,getMousePos() method which returns the mouse coordinates based on the position of the client mouse and the position of the canvas obtained from the getBoundingClientRect() method of the window object.
I have a HTML5 canvas of a certain size on the page
<canvas id="canvas" width="200" height="100" style="border:1px solid #000000;">
Right now, the canvas is painted when the mouse is dragged over it (i.e. you click first(not necessarily inside the canvas) then start dragging the mouse over it without releasing) Script below;
$(function () {
var c = document.getElementById("canvas");
var context = c.getContext("2d");
var clickX = new Array();
var clickY = new Array();
var clickDrag = new Array();
var paint;
var $canvas=$("#canvas");
$(this).mousedown(function (e) {
paint = true;
addClick(e.pageX - $canvas[0].offsetLeft, e.pageY - $canvas[0].offsetTop);
redraw();
});
$(this).mousemove(function (e) {
if (paint) {
addClick(e.pageX - $canvas[0].offsetLeft, e.pageY - $canvas[0].offsetTop, true);
redraw();
}
});
$(this).mouseup(function (e) {
paint = false;
});
$(this).mouseleave(function (e) {
paint = false;
});
function addClick(x, y, dragging) {
clickX.push(x);
clickY.push(y);
clickDrag.push(dragging);
}
function redraw() {
context.clearRect(0, 0, context.canvas.width, context.canvas.height); // Clears the canvas
context.strokeStyle = "#000000";
context.lineJoin = "round";
context.lineWidth = 2;
for (var i = 0; i < clickX.length; i++) {
context.beginPath();
if (clickDrag[i] && i) {
context.moveTo(clickX[i - 1], clickY[i - 1]);
} else {
context.moveTo(clickX[i] - 1, clickY[i]);
}
context.lineTo(clickX[i], clickY[i]);
context.closePath();
context.stroke();
}
}
});
What I need now is a way to scale all the mouse coordinates on document during the document's mouse events to the relative coordinates inside the canvas.
so that no matter wherever you drag the mouse on the document it is drawn inside the canvas (in relatively small size of course). Any Idea how to achieve this?
http://jsfiddle.net/umwc5/3/
Why I need this?
It is for a signature application, When a user scribbles the signature using a tablet on a page(without seeing the page!) the entire signature is to be registered in a small canvas.
Update
The final working fiddle
The most important thing you were missing here was to multiply by the canvas/screen ratio.
First calculate the ratio:
var docToCanv = Math.min($canvas[0].width / $('body').width(), $canvas[0].height/$('body').height());
Then use it like this:
addClick(e.pageX*docToCanv, e.pageY*docToCanv);
Depending on the additional behavior you want, you may need to adjust the location a bit, but this should get you past the current issue you are having.
Demo
First off, the square draws fine and it does work, but there are still a couple of issues. I have two problems... the first one is that when I draw the square, it's unable to collapse back on itself to a single point (and get smaller) if you made your square too big. The second problems is that when I draw the square, it shows up about a centimeter below my finger, rather than directly underneath it.
Can anyone help me out with these problems?
Here's the code:
JAVASCRIPT
// "Draw Rectangle" Button
function rect(){
var canvas = document.getElementById('canvasSignature'), ctx = canvas.getContext('2d'), rect = {}, drag = false;
function init() {
canvas.addEventListener("touchstart", touchHandler, false);
canvas.addEventListener("touchmove", touchHandler, false);
canvas.addEventListener("touchend", touchHandler, false);
}
function touchHandler(event) {
if (event.targetTouches.length == 1) { //one finger touche
var touch = event.targetTouches[0];
if (event.type == "touchstart") {
rect.startX = touch.pageX;
rect.startY = touch.pageY;
drag = true;
} else if (event.type == "touchmove") {
if (drag) {
rect.w = touch.pageX - rect.startX;
rect.h = touch.pageY - rect.startY ;
draw();
}
} else if (event.type == "touchend" || event.type == "touchcancel") {
drag = false;
}
}
}
function draw() {
ctx.fillRect(rect.startX, rect.startY, rect.w, rect.h);
ctx.fillStyle = "orange";
}
init();
}
Thanks,
Wardenclyffe
The first problem is caused by you not clearing the canvas. The previous data will remain in the canvas unless you clear it, and so the previously-drawn rectangle will remain. You can clear the canvas like this:
ctx.clearRect(0, 0, canvas.width, canvas.height);
I assume your second problem is that you're using the page-relative X and Y and expecting it to be the canvas-relative X and Y. This is not the case unless the canvas's upper left hand corner is the same as the document's upper left hand corner. You can transform a page X and Y into a canvas X and Y in newer browsers like this:
var clientRect = canvas.getBoundingClientRect();
var canvasX = touch.pageX - clientRect.left - window.pageXOffset,
canvasY = touch.pageY - clientRect.top - window.pageYOffset;
This will need further tweaking if the canvas is in an element with a scrollbar.
I'm trying to implement functionality to "pan" inside a canvas in HTML5 and I am unsure about the best way to go about accomplishing it.
Currently - I am trying to detect where the mouse is on the canvas, and if it is within 10% of an edge, it will move in that direction, as shown:
Current Edge Detection:
canvas.onmousemove = function(e)
{
var x = e.offsetX;
var y = e.offsetY;
var cx = canvas.width;
var cy = canvas.height;
if(x <= 0.1*cx && y <= 0.1*cy)
{
alert("Upper Left");
//Move "viewport" to up and left (if possible)
}
//Additional Checks for location
}
I know I could probably accomplish this by creating paths within the canvas and attaching events to them, but I haven't worked with them much, so I thought I would ask here. Also - if a "wrapping" pan would be possible that would be awesome (panning to the left will eventually get to the right).
Summary: I am wondering what the best route is to accomplish "panning" is within the HTML5 Canvas. This won't be using images but actual drawn objects (if that makes any difference). I'll be glad to answer any questions if I can.
Demo:
Demo
It depends on how you want panning with mouse movement to be implemented, but today it's often 'realtime' panning in that you can drag around. I tried to update your fiddle a little: http://jsfiddle.net/pimvdb/VWn6t/3/.
var isDown = false; // whether mouse is pressed
var startCoords = []; // 'grab' coordinates when pressing mouse
var last = [0, 0]; // previous coordinates of mouse release
canvas.onmousedown = function(e) {
isDown = true;
startCoords = [
e.offsetX - last[0], // set start coordinates
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; // don't pan if mouse is not pressed
var x = e.offsetX;
var y = e.offsetY;
// set the canvas' transformation matrix by setting the amount of movement:
// 1 0 dx
// 0 1 dy
// 0 0 1
ctx.setTransform(1, 0, 0, 1,
x - startCoords[0], y - startCoords[1]);
render(); // render to show changes
}
pimvdb's fiddle shows the concept nicely but doesn't actually work, at least not for me.
Here's a fixed version: http://jsfiddle.net/VWn6t/173/
The meat of it is basically the same.
var startCoords = {x: 0, y: 0};
var last = {x: 0, y: 0};
var isDown = false;
canvas.onmousemove = function (e) {
if(isDown) {
ctx.setTransform(1, 0, 0, 1,
xVal - startCoords.x,
yVal - startCoords.y);
}
};
canvas.onmousedown = function (e) {
isDown = true;
startCoords = {x: e.pageX - this.offsetLeft - last.x,
y: e.pageY - this.offsetTop - last.y};
};
canvas.onmouseup = function (e) {
isDown = false;
last = {x: e.pageX - this.offsetLeft - startCoords.x,
y: e.pageY - this.offsetTop - startCoords.y};
};