I have a canvas element that automatically fills the entire browser window of the client when loaded. On it you can draw with the mouse, like in the result of any "make a drawing board"-tutorial out there. What I want to do however is to make it so that if you move the mouse to any extreme of the canvas (or maybe select a certain "move"-tool, you can drag the canvas in any direction you'd like), it scrolls. In particular, I want it to be possible to in theory scroll forever, so I can't really pre-generate, I have to generate "more canvas" on the fly. Does any one have any idea on how to do this?
If it helps, this is the client-side javascript right now: (the html is just a canvas-tag)
$(document).ready(function() {
init();
});
function init() {
var canvas = document.getElementById('canvas')
, ctx = canvas.getContext('2d')
, width = window.innerWidth
, height = window.innerHeight;
// Sets the canvas size to be the same as the browser size
canvas.width = width;
canvas.height = height;
// Binds mouse and touch events to functions
$(canvas).bind({
'mousedown': startDraw,
'mousemove': draw,
'mouseup': stopDraw,
});
};
// Triggered on mousedown, sets draw to true and updates X, Y values.
function startDraw(e) {
this.draw = true;
this.X = e.pageX;
this.Y = e.pageY;
};
// Triggered on mousemove, strokes a line between this.X/Y and e.pageX/Y
function draw(e) {
if(this.draw) {
with(ctx) {
beginPath();
lineWidth = 4;
lineCap = 'round';
moveTo(this.X, this.Y);
lineTo(e.pageX, e.pageY);
stroke();
}
this.X = e.pageX;
this.Y = e.pageY;
}
};
// Triggered on mouseup, sets draw to false
function stopDraw() {
this.draw = false;
};
The canvas element uses real memory of your computer, so there is no infinite canvas which scrolls forever. But, you may simulate this behavior using a virtual canvas. Just record the xy coords captured by draw() into an array and calculate a new center of the virtual canvas if the mouse touches the border. Then filter out the xy coords which fit into center +- screen size and draw them.
However, the array recording the xy coords can not grow infinitely and the filter code will get slower over the size of the array. Are 10,000 points enough?
More optimized code will turn the mouse coords into splines and saves only points needed to redraw the (smoothed) path of the mouse.
Related
I am using React and javascript, I have a canvas with a 'player' square and an 'enemy' square. The player position is determined by the mouse coordinates, while I want the 'enemy' to have a pre determined path.
const Canvas = props => {
useEffect( () => {
setEnemy1X(enemy1X+1);
setEnemy1Y(enemy1Y+1);
},[tempGame])
useEffect(() =>{
//canvas variables
const canvas = canvasRef.current
const context = canvas.getContext('2d')
canvas.width = canvas.getBoundingClientRect().width
canvas.height = canvas.getBoundingClientRect().height
setCanvasWidth(canvas.width);
setCanvasHeight(canvas.height);
var player1 = { x: playerX, y: playerY, draggable: gameStatus }
var enemy1 = { x: enemy1X, y: enemy1Y, width: enemy3Width, height: enemy4Height}
// this function is running forever
const drawFun = () => {
context.clearRect(0,0, canvasWidth, canvasHeight)
context.beginPath();
context.fillStyle = '#00f4cc'
context.fillRect(player1.x, player1.y, playerWidth, playerHeight)
context.beginPath();
context.fillStyle = 'red'
context.fillRect(enemy1.x, enemy1.y, enemy1Width, enemy1Height)
requestAnimationFrame(drawFun);
}
drawFun();
//on mouse move
canvas.addEventListener('mousemove', e => {
e.preventDefault();
var rect = e.target.getBoundingClientRect();
var x = e.clientX - rect.left; //x position within the element.
var y = e.clientY - rect.top; //y position within the element.
//if game is running
if( gameStatus ){
setPlayerX(x - playerWidth/2);
setPlayerY(y - playerHeight/2);
The issue I am having right now is I am trying to update the X and Y coordinates of the red square by incrementing the values by 1 using setEnemy1X(enemy1X+1) and setEnemy1Y(enemy1Y+1) if I place this under mousemove event, the animation looks smooth and slow. If i try updating the coordinates within drawFun or the UseEffect at the beginning, the whole canvas flickers as if it has to be redrawn to fast.
To be more clear, I am trying to have that red square move diagonally across the screen by updating the X and Y coordinates. If I do this via a mousemove event, the animation looks nice and smooth, anywhere else I try to update the animation the whole canvas starts flickering because of what I'm assuming is the canvas being updated to many times a second. I tried to include everything I think is relevant to the question, any help greatly appreciated!
I want to use Draw with Mouse on the canvas. The code is as follows:
// Stores the initial position of the cursor
let coord = {x:0 , y:0};
// // set a flag
let paint = false;
// Updates the coordianates of the cursor when an event e is triggered
function getPosition(event){
coord.x = event.clientX - canvas.offsetLeft;
coord.y = event.clientY - canvas.offsetTop;
}
// The following functions toggle the flag to start and stop drawing
function startPainting(event){
if (mouseID.checked){
myCanvas.style.cursor = "url('/paint.cur'),auto"
paint = true;
getPosition(event);
}
}
function stopPainting(){
paint = false;
}
function sketch(event){
if (!paint) return;
ctx.beginPath();
ctx.lineWidth = mouseline.value;
// set to a round shape.
ctx.lineCap = "round";
ctx.strokeStyle = bcolor.value;
// The cursor to start drawing
ctx.moveTo(coord.x, coord.y);
// The position of the cursor gets updated when move the mouse around.
getPosition(event);
ctx.lineTo(coord.x , coord.y);
// Draws the line.
ctx.stroke();
}
The problem is when I run it on my desktop the mouse is fine but on another computer, the mouse gets a mismatched position with the drawing. Can anyone help me with the issue?
I'm trying to capture an image in the browser using html2canvas. Capturing an image of the whole browser works. But I need to specify x,y start and end coordinates that I want to capture. In the docs I saw that html2canvas can accept x,y coordinates:
x: Default: Element x-offset Description: Crop canvas x-coordinate
y: Default: Element y-offset Description: Crop canvas y-coordinate
Passing my x,y coordinates to those parameters just captures the whole window.
So instead, I tried capturing the whole window, and then cropping an area from it using drawImage() (found at some other stackoverflow post, not sure which):
function snapImage(x1,y1,x2,y2, e){
html2canvas(document.body).then(function(canvas) {
// calc the size -- but no larger than the html2canvas size!
var width = Math.min(canvas.width,Math.abs(x2-x1));
var height = Math.min(canvas.height,Math.abs(y2-y1));
// create a new avatarCanvas with the specified size
var avatarCanvas = document.createElement('canvas');
avatarCanvas.width=width;
avatarCanvas.height=height;
avatarCanvas.id = 'avatarCanvas';
// put avatarCanvas into document body
document.body.appendChild(avatarCanvas);
// use the clipping version of drawImage to draw
// a clipped portion of html2canvas's canvas onto avatarCanvas
var avatarCtx = avatarCanvas.getContext('2d');
avatarCtx.drawImage(canvas,x1,y1,width,height,0,0,width,height);
});
}
This draws a shifted image with a wrong offset. For example, given the following website:
image taken from the example at: https://github.com/niklasvh/html2canvas/tree/master/examples
I mark "pluot?" area to snap it:
see the dotted rectangle
The dotted rectangle is drawn using js, given the mouse coordinates in 2 events: onmousedown and onmouseup. Because the rectangle is drawn correctly, I assume my coordinates are correct. But when I pass these coordinates to the function snapImage() above, I get the following captured image:
Looks like there's an offset. Maybe the start coordinates drawImage() operates on differ from my canvas start coordinates?
EDIT:
Turns out that my code works when I'm on 100% zoom. It doesn't though when I zoom in / out.
I guess this is because you get x and y from event with clientX and clientY. Use pageX and pageY instead. Have a look at this jsFiddle
let startX, startY;
document.getElementsByTagName('body')[0].addEventListener('mousedown', function(event) {
console.log("ok");
startX = Math.floor(event.pageX);
startY = Math.floor(event.pageY);
});
document.getElementsByTagName('body')[0].addEventListener('mouseup', function(event) {
snapImage(Math.min(event.pageX, startX), Math.min(event.pageY, startY), Math.max(event.pageX, startX), Math.max(event.pageY, startY));
});
function snapImage(x1,y1,x2,y2, e){
console.log(x1, x2, y1, y2);
html2canvas(document.body).then(function(canvas) {
// calc the size -- but no larger than the html2canvas size!
var width = Math.min(canvas.width,Math.abs(x2-x1));
var height = Math.min(canvas.height,Math.abs(y2-y1));
// create a new avatarCanvas with the specified size
var avatarCanvas = document.createElement('canvas');
avatarCanvas.width=width;
avatarCanvas.height=height;
avatarCanvas.id = 'avatarCanvas';
// put avatarCanvas into document body
document.body.appendChild(avatarCanvas);
// use the clipping version of drawImage to draw
// a clipped portion of html2canvas's canvas onto avatarCanvas
var avatarCtx = avatarCanvas.getContext('2d');
avatarCtx.drawImage(canvas,x1,y1,width,height,0,0,width,height);
});
}
Turns out there's a built-in chrome function captureVisibleTab that captures the image of the active tab. So I ended up using that instead of html2canvas. I got help from the Copyfish Chrome Extension. Github code here: Copyfish.
Here's my code:
Listener:
//listener in background.js which invokes the screen capture
chrome.tabs.captureVisibleTab(function (dataURL) {
sendResponse({
dataURL: dataURL,
});
});
Receiver:
//receiver in content.js which gets the captured image and crops it accordingly
function(response){
var img = new Image();
img.src = response.dataURL;
var dpf = window.innerWidth / img.width;
var scaleFactor = 1 / dpf,
sx = Math.min(x1, x2) * scaleFactor,
sy = Math.min(y1, y2) * scaleFactor,
width = Math.abs(x2 - x1),
height = Math.abs(y2 - y1);
// create a new avatarCanvas with the specified size
var avatarCanvas = document.createElement('canvas');
avatarCanvas.width = width;
avatarCanvas.height = height;
avatarCanvas.id = 'avatarCanvas';
// put avatarCanvas into document body
document.body.appendChild(avatarCanvas);
// use the clipping version of drawImage to draw
var avatarCtx = avatarCanvas.getContext('2d');
avatarCtx.drawImage(img, sx, sy, scaledWidth, scaledHeight, 0, 0, width, height);
}
x,y coordinates are taken by e.clientX and e.clientY respectively.
This method is zoom- and resolution- proof.
I have a canvas in the center of a website. When I perform a mouse click on the canvas, I want a small image to be drawn at the click-location. In manage to get the correct coordinates of a canvas click I structute a JavaScript-function like this:
function click( event ) {
var ctxt;
var myX = event.clientX;
var myY = event.clientY;
myX-=canvas.offsetTop;
myY-=canvas.offsetLeft;
ctxt = canvas.getContext("2d");
ctxt.drawImage(myImage, myX, myY);
alert(myX + " " + myY);
}
The alert function shows the correct coordinates, but the image is drawn at a location with much higher coordinate-values. If I click a little bit to far down or to the left, the image is not drawn at all (probably because its outside the canvas).
The drawn image has a x-coordinate that's about 3 times as high as the x-coordinate of the click, and the y-coordinate is about 5 times as high.
What can be the problem?
Hank
You probably forgot to define a size for the canvas bitmap, and is only using CSS to set the size. Remember that canvas size must set implicit as CSS does not affect its bitmap size.
<canvas id="myCanvas" width=500 height=500></canvas>
If not your bitmap which defaults to 300x150 will be stretched to whatever you set with CSS which means your coordinates will be scaled as well.
CSS should be skipped for this but if you absolutely want to use it set width and height in CSS to the same size as defined for your canvas element.
The mouse position you get will be relative to the window so you need to subtract the canvas position to make it relative to canvas element. You probably have this working already and iHank's example should work, although I would not obtain the context each time:
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d');
canvas.addEventListener('click', mouseClick, false);
ctx.strokeRect(0, 0, canvas.width, canvas.height);
function mouseClick(e) {
var rect = canvas.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top;
// draw image here, for demo - drawn from corner not center:
ctx.fillRect(x, y, 5, 5);
}
Canvas: <canvas id="myCanvas" width=500 height=180></canvas>
Seems like I missed that the default size of a canvas was (300, 150). If I change the width and height of the canvas-object to the sizes specified in the cs-file, it works!
Try:
function click( event ) {
var ctxt;
var myX = event.clientX;
var myY = event.clientY;
offsetXY = canvas.getBoundingClientRect();
myX-=offsetXY.top;
myY-=offsetXY.left;
ctxt = canvas.getContext("2d");
ctxt.drawImage(myImage, myX, myY);
alert(myX + " " + myY);
}
"The returned value is a TextRectangle object, which contains read-only left, top, right and bottom properties describing the border-box in pixels. top and left are relative to the top-left of the viewport." https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect
Hope that's what you needed.
EDIT: offsetXY.top and offsetXY.left. Those properties of the object are not capital.
I want to visualize a huge diagram that is drawn in a HTML5 canvas. As depicted below, let’s imagine the world map, it’s impossible to visualize it all at the same time with a “decent” detail. Therefore, in my canvas I would like to be able to pan over it using the mouse to see the other countries that are not visible.
Does anyone know how to implement this sort of panning in a HTML5 canvas? Another feature would be the zoom in and out.
I've seen a few examples but I couldn't get them working nor they seam to address my question.
Thanks in advance!
To achieve a panning functionality with a peep-hole it's simply a matter of two draw operations, one full and one clipped.
To get this result you can do the following (see full code here):
Setup variables:
var ctx = canvas.getContext('2d'),
ix = 0, iy = 0, /// image position
offsetX = 0, offsetY = 0, /// current offsets
deltaX, deltaY, /// deltas from mouse down
mouseDown = false, /// in mouse drag
img = null, /// background
rect, /// rect position
rectW = 200, rectH = 150; /// size of highlight area
Set up the main functions that you use to set size according to window size (including on resize):
/// calc canvas w/h in relation to window as well as
/// setting rectangle in center with the pre-defined
/// width and height
function setSize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
rect = [canvas.width * 0.5 - rectW * 0.5,
canvas.height * 0.5 - rectH * 0.5,
rectW, rectH]
update();
}
/// window resize so recalc canvas and rect
window.onresize = setSize;
The main function in this is the draw function. Here we draw the image on the position calculated by mouse moving (see next section).
First step to get that washed-out look is to set alpha down to about 0.2 (you could also draw a transparent rectangle on top but this is more efficient).
Then draw the complete image.
Reset alpha
Draw the peep-hole using clipping with corrected offsets for the source.
-
/// main draw
function update() {
if (img === null) return;
/// limit x/y as drawImage cannot draw with negative
/// offsets for clipping
if (ix + offsetX > rect[0]) ix = rect[0] - offsetX;
if (iy + offsetY > rect[1]) iy = rect[1] - offsetY;
/// clear background to clear off garbage
ctx.clearRect(0, 0, canvas.width, canvas.height);
/// make everything transparent
ctx.globalAlpha = 0.2;
/// draw complete background
ctx.drawImage(img, ix + offsetX, iy + offsetY);
/// reset alpha as we need opacity for next draw
ctx.globalAlpha = 1;
/// draw a clipped version of the background and
/// adjust for offset and image position
ctx.drawImage(img, -ix - offsetX + rect[0], /// sx
-iy - offsetY + rect[1], /// sy
rect[2], rect[3], /// sw/h
/// destination
rect[0], rect[1], rect[2], rect[3]);
/// make a nice sharp border by offsetting it half pixel
ctx.strokeRect(rect[0] + 0.5, rect[1] + 0.5, rect[2], rect[3]);
}
Now it's a matter of handling mouse down, move and up and calculate the offsets -
In the mouse down we store current mouse positions that we'll use for calculating deltas on mouse move:
canvas.onmousedown = function(e) {
/// don't do anything until we have an image
if (img === null) return;
/// correct mouse pos
var coords = getPos(e),
x = coords[0],
y = coords[1];
/// store current position to calc deltas
deltaX = x;
deltaY = y;
/// here we go..
mouseDown = true;
}
Here we use the deltas to avoid image jumping setting the corner to mouse position. The deltas are transferred as offsets to the update function:
canvas.onmousemove = function(e) {
/// in a drag?
if (mouseDown === true) {
var coords = getPos(e),
x = coords[0],
y = coords[1];
/// offset = current - original position
offsetX = x - deltaX;
offsetY = y - deltaY;
/// redraw what we have so far
update();
}
}
And finally on mouse up we make the offsets a permanent part of the image position:
document.onmouseup = function(e) {
/// was in a drag?
if (mouseDown === true) {
/// not any more!!!
mouseDown = false;
/// make image pos. permanent
ix += offsetX;
iy += offsetY;
/// so we need to reset offsets as well
offsetX = offsetY = 0;
}
}
For zooming the canvas I believe this is already answered in this post - you should be able to merge this with the answer given here:
Zoom Canvas to Mouse Cursor
To do something like you have requested, it is just a case of having 2 canvases, each with different z-index. one canvas smaller than the other and position set to the x and y of the mouse.
Then you just display on the small canvas the correct image based on the position of the x and y on the small canvas in relation to the larger canvas.
However your question is asking for a specific solution, which unless someone has done and they are willing to just dump their code, you're going to find it hard to get a complete answer. I hope it goes well though.