var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var rect = {};
var drag = false;
make_base();
init();
function make_base() {
base_image = new Image();
base_image.src = 'https://www.w3schools.com/css/img_fjords.jpg';
base_image.onload = function() {
context.drawImage(base_image, 0, 0, 800, 500);
}
}
function writeMessage(canvas, message) {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(base_image, 0, 0, 800, 500);
context.font = '12pt Calibri';
context.fillStyle = 'red';
context.fillText(message, 25, 25);
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
function init() {
canvas.addEventListener('mousedown', mouseDown, false);
canvas.addEventListener('mouseup', mouseUp, false);
canvas.addEventListener('mousemove', mouseMove, false);
}
function mouseDown(e) {
rect.startX = e.pageX - this.offsetLeft;
rect.startY = e.pageY - this.offsetTop;
drag = true;
}
function mouseUp() {
drag = false;
}
function mouseMove(e) {
if(drag) {
rect.w = (e.pageX - this.offsetLeft) - rect.startX;
rect.h = (e.pageY - this.offsetTop) - rect.startY ;
context.clearRect(rect.startX, rect.startY, rect.w, rect.h);
draw();
}
}
function draw() {
context.lineWidth="1";
context.strokeStyle = "blue";
context.beginPath();
context.rect(rect.startX, rect.startY, rect.w, rect.h);
context.stroke();
context.closePath();
}
<canvas id="myCanvas" width="800" height="500" style="border:1px solid #000000;">
</canvas>
Now I can draw multi rectangle on image using mouse.
However, it will be white?
How to draw a rectangle more like select a area?
Best practice.
Rendering
You should never render from mouse or other UI events as they are not synced to the display. This can result in unpleasant shearing and flickering and also cause needless rendering that may only be partially or not at all seen, chewing power and battery life for no reason.
If you are regularly updating elements in the DOM (not just canvas) use render loop called via requestAnimationFrame. This ensures you only present DOM content that can be seen.
Lost input
When getting mouse input that is intended to be dragged you should listen to the document's mouse events rather than the element's. This allows you to follow the dragged input when it moves of the element and page. If you do not do this the drag can get locked if the user drags of the element/page and releases the mouse button.
Both the other answers fail to handle this correctly.
Constants and style
Use constants (const) wherever possible. They are block scoped and help to reduce bugs (though this is arguable).
Get used to adding "use strict"; top the top of your code, it will help you reduce and spot bugs early. Do not add "use strict" once you have completed the code unless you do full testing cycle as it will/may break what was once working code.
Though best practice do not define naming styles it does mean that you use a naming style consistently. If you choose snake case (snake_case) then use it throughout the code, if you choose camel case (camelCase) then only use that. You remember variable as the words, trying to remember which style you used for a variable will slow you down and can result in bugs.
"use strict";
requestAnimationFrame(mainLoop);
const canvas = document.getElementById("myCanvas");
const ctx = canvas.getContext("2d");
const storedRects = [];
const baseImage = loadImage("https://www.w3schools.com/css/img_fjords.jpg");
var refresh = true;
const rect = (() => {
var x1, y1, x2, y2;
var show = false;
function fix() {
rect.x = Math.min(x1, x2);
rect.y = Math.min(y1, y2);
rect.w = Math.max(x1, x2) - Math.min(x1, x2);
rect.h = Math.max(y1, y2) - Math.min(y1, y2);
}
function draw(ctx) { ctx.strokeRect(this.x, this.y, this.w, this.h) }
const rect = {x : 0, y : 0, w : 0, h : 0, draw};
const API = {
restart(point) {
x2 = x1 = point.x;
y2 = y1 = point.y;
fix();
show = true;
},
update(point) {
x2 = point.x;
y2 = point.y;
fix();
show = true;
},
toRect() {
show = false;
return Object.assign({}, rect);
},
draw(ctx) {
if (show) { rect.draw(ctx) }
},
show : false,
}
return API;
})();
function loadImage(url) {
const image = new Image();
image.src = url;
image.onload = () => refresh = true;
return image;
}
const mouse = {
button : false,
x : 0,
y : 0,
down : false,
up : false,
element : null,
event(e) {
const m = mouse;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;
const prevButton = m.button;
m.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
if (!prevButton && m.button) { m.down = true }
if (prevButton && !m.button) { m.up = true }
},
start(element) {
mouse.element = element;
"down,up,move".split(",").forEach(name => document.addEventListener("mouse" + name, mouse.event));
}
}
mouse.start(canvas);
function draw() {
ctx.drawImage(baseImage, 0, 0, ctx.canvas.width, ctx.canvas.width);
ctx.lineWidth = 1;
ctx.strokeStyle = "yellow";
storedRects.forEach(rect => rect.draw(ctx));
ctx.strokeStyle = "red";
rect.draw(ctx);
}
function mainLoop() {
if (refresh || mouse.down || mouse.up || mouse.button) {
refresh = false;
if (mouse.down) {
mouse.down = false;
rect.restart(mouse);
} else if (mouse.button) {
rect.update(mouse);
} else if (mouse.up) {
mouse.up = false;
rect.update(mouse);
storedRects.push(rect.toRect());
}
draw();
}
requestAnimationFrame(mainLoop)
}
<canvas id="myCanvas" width="800" height="500" title = "click and drag to add rectangles" style="border:1px solid #000000;cursor:crosshair"></canvas>
Heavily inspired by #Blindman67's excellent answer, this solution accepts touch events as well as mouse events, which I required for my own use.
The idea of the API Object was maintained but called "RectangleMaker".
Animation frames are only requested on the move event.
I found it hard to follow the signature mouse object run through the mainLoop loop function so I pulled that apart into three functions: "start", "move" and "stop". Events are added individually for each event type.
There are a few extra bits:
The code is invoked via a constructor so as to allow many canvases on a page.
Rectangle can preloaded, rectangle can be returned.
function AnnotateImage (canvas) {
const canvasContext = canvas.getContext('2d');
const rectangleMaker = new RectangleMaker(canvasContext);
const mouseObj = { x: 0, y: 0 };
let dragAction = false;
const img1 = new Image();
img1.src = canvas.toDataURL();
attachEvents(canvas);
let rectangles = [];
this.returnRectangles = () => {
return rectangles;
};
this.loadRectangles = (rects) => {
rectangles = rects;
rectangleMaker.drawRectangles();
return this;
};
function RectangleMaker (canvasContext) {
let x1, y1, x2, y2;
const finishedRectangle = { x: 0, y: 0, w: 0, h: 0 };
function makeFinishedRectangle () {
finishedRectangle.x = Math.min(x1, x2);
finishedRectangle.y = Math.min(y1, y2);
finishedRectangle.w = Math.max(x1, x2) - Math.min(x1, x2);
finishedRectangle.h = Math.max(y1, y2) - Math.min(y1, y2);
}
this.setFirstPoint = (point) => {
return setFirstPoint(point);
};
function setFirstPoint (point) {
x2 = x1 = point.x;
y2 = y1 = point.y;
makeFinishedRectangle();
}
this.setSecondPoint = (point) => {
return setSecondPoint(point);
};
function setSecondPoint (point) {
x2 = point.x;
y2 = point.y;
makeFinishedRectangle();
}
this.saveLocal = () => {
const rect = Object.assign({}, finishedRectangle);
rectangles.push(rect);
};
this.getRectangleToSave = () => {
return getRectangleToSave();
};
function getRectangleToSave () {
return Object.assign({}, finishedRectangle);
}
this.drawRectangles = () => {
return drawRectangles();
};
function drawRectangles () {
if (rectangles && rectangles.length > 0) {
rectangles.forEach((rectangle) => {
canvasContext.strokeRect(rectangle.x, rectangle.y, rectangle.w, rectangle.h);
});
}
}
}
function attachEvents (canvas) {
canvas.addEventListener('mousedown', (e) => {
start(e);
});
canvas.addEventListener('touchstart', (e) => {
start(e);
});
function start (e) {
const bounds = canvas.getBoundingClientRect();
mouseObj.x = (e.type.includes('mouse')) ? e.pageX - bounds.left - scrollX : e.targetTouches[0].pageX - bounds.left - scrollX;
mouseObj.y = (e.type.includes('mouse')) ? e.pageY - bounds.top - scrollY : e.targetTouches[0].pageY - bounds.top - scrollY;
rectangleMaker.setFirstPoint(mouseObj);
dragAction = true;
}
canvas.addEventListener('mousemove', (e) => {
move(e);
});
canvas.addEventListener('touchmove', (e) => {
move(e);
});
function move (e) {
e.preventDefault();
if (dragAction) {
// const prior = rectangleMaker.getRectangleToSave();
// canvasContext.strokeRect(prior.x, prior.y, prior.w, prior.h);
const bounds = canvas.getBoundingClientRect();
mouseObj.x = (e.type.includes('mouse')) ? e.pageX - bounds.left - scrollX : e.targetTouches[0].pageX - bounds.left - scrollX;
mouseObj.y = (e.type.includes('mouse')) ? e.pageY - bounds.top - scrollY : e.targetTouches[0].pageY - bounds.top - scrollY;
rectangleMaker.setSecondPoint(mouseObj);
const newRect = rectangleMaker.getRectangleToSave();
window.requestAnimationFrame(() => {
canvasContext.drawImage(img1, 0, 0);
rectangleMaker.drawRectangles();
canvasContext.strokeRect(newRect.x, newRect.y, newRect.w, newRect.h);
});
}
}
canvas.addEventListener('mouseup', (e) => {
stop(e);
});
canvas.addEventListener('touchend', (e) => {
stop(e);
});
function stop (e) {
dragAction = false;
const bounds = canvas.getBoundingClientRect();
mouseObj.x = (e.type.includes('mouse')) ? e.pageX - bounds.left - scrollX : e.changedTouches[0].pageX - bounds.left - scrollX;
mouseObj.y = (e.type.includes('mouse')) ? e.pageY - bounds.top - scrollY : e.changedTouches[0].pageY - bounds.top - scrollY;
rectangleMaker.setSecondPoint(mouseObj);
rectangleMaker.saveLocal();
rectangleMaker.drawRectangles();
}
}
}
/**
* #description code to load the canvas and call the Annotate constructor
*/
const canvas = document.querySelector('canvas');
loadImageFromlink(canvas).then(() => {
const annotate = new AnnotateImage(canvas);
});
function loadImageFromlink (canvas) {
const promise = new Promise((resolve, reject)=>{
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = canvas.dataset.src;
img.crossOrigin = 'Anonymous';
img.onload = () => {
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
resolve('loaded');
};
});
return promise;
}
<canvas data-src="https://www.gannett-cdn.com/-mm-/dcd88fe5876fd549823feb35af14c0ae9f0885b6/c=0-25-492-303/local/-/media/Phoenix/None/2014/10/17/635491415661570024-garfi8.PNG?width=492&height=277&fit=crop&format=pjpg&auto=webp"></canvas>
Related
I am creating a canvas with multiple image which have zoom and pan action in Svelte application. And now want to draw line on it after disable zoom and pan. Zoom and pan working well but I cannot draw line on the canvas after zoom and pan disabled as expected. Unable to draw line on cursor point or correcting-coordinates. It's drawing far from cursor and moving the line too far on mouse move.
I am giving full code here so that any one can run it in Svelte application to see it
I tried so many ways but can't solve it.
<script>
import { onMount } from "svelte";
import { f7ready } from "framework7-svelte";
let canvasWidth = 500;
let canvasHeight = 500;
let ctx;
let canvas;
let isDrawing = false;
let start;
var scaleFactor = 1.1;
var factor = 1.1;
let canDragAndZoom = true;
let t, l;
$: ctx;
$: canvas;
$: isDrawing;
$: start;
$: t;
$: l;
$: scaleFactor;
$: factor;
$: canDragAndZoom;
let lastX = canvasWidth;
let lastY = canvasHeight;
$: lastX;
$: lastY;
onMount(() => {
f7ready(async () => {
canvas = document.getElementById("myCanvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
var gkhead1 = new Image();
var gkhead2 = new Image();
var gkhead3 = new Image();
var gkhead4 = new Image();
var gkhead5 = new Image();
gkhead1.src =
"http://localhost/asset_ims/assets/uploads/files/canvasImg1.jpeg";
gkhead2.src =
"http://localhost/asset_ims/assets/uploads/files/canvasImg2.jpeg";
gkhead3.src =
"http://localhost/asset_ims/assets/uploads/files/canvasImg3.jpeg";
gkhead4.src =
"http://localhost/asset_ims/assets/uploads/files/canvasImg4.jpeg";
gkhead5.src =
"http://localhost/asset_ims/assets/uploads/files/canvasImg5.jpeg";
ctx = canvas.getContext("2d");
ctx.lineWidth = 1;
trackTransforms(ctx);
function redraw() {
// Clear the entire canvas
var p1 = ctx.transformedPoint(0, 0);
var p2 = ctx.transformedPoint(canvas.width, canvas.height);
ctx.clearRect(p1.x, p1.y, p2.x - p1.x, p2.y - p1.y);
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore();
ctx.drawImage(gkhead1, 50, 150, 100, 129);
ctx.drawImage(gkhead2, 135, 150, 100, 194);
ctx.drawImage(gkhead3, 240, 150, 100, 141);
ctx.drawImage(gkhead4, 345, 150, 100, 125);
ctx.drawImage(gkhead5, 50, 300, 100, 75);
}
redraw();
var dragStart, dragged;
canvas.addEventListener(
"mousedown",
function (evt) {
document.body.style.mozUserSelect =
document.body.style.WebkitUserSelect =
document.body.style.userSelect =
document.body.style.msUserSelect =
"auto";
// drag code
lastX = evt.offsetX || evt.pageX - canvas.offsetLeft;
lastY = evt.offsetY || evt.pageY - canvas.offsetTop;
dragStart = ctx.transformedPoint(lastX, lastY);
// drag code
dragged = false;
},
false
);
canvas.addEventListener(
"mousemove",
function (evt) {
lastX = evt.offsetX || evt.pageX - canvas.offsetLeft;
lastY = evt.offsetY || evt.pageY - canvas.offsetTop;
dragged = true;
dragStart = canDragAndZoom ? dragStart : null;
if (dragStart) {
var pt = ctx.transformedPoint(lastX, lastY);
ctx.translate(pt.x - dragStart.x, pt.y - dragStart.y);
redraw();
}
},
false
);
canvas.addEventListener(
"mouseup",
function (evt) {
dragStart = null;
if (!dragged) zoom(evt.shiftKey ? -1 : 1);
},
false
);
// var scaleFactor = 1.1;
var zoom = function (clicks) {
if (!canDragAndZoom) {
return false;
}
var pt = ctx.transformedPoint(lastX, lastY);
ctx.translate(pt.x, pt.y);
factor = Math.pow(scaleFactor, clicks);
ctx.scale(factor, factor);
ctx.translate(-pt.x, -pt.y);
redraw();
};
var handleScroll = function (evt) {
var delta = evt.wheelDelta
? evt.wheelDelta / 40
: evt.detail
? -evt.detail
: 0;
if (delta) zoom(delta);
return evt.preventDefault() && false;
};
canvas.addEventListener("DOMMouseScroll", handleScroll, false);
canvas.addEventListener("mousewheel", handleScroll, false);
// };
});
});
$: if (ctx) {
ctx.strokeStyle = "red";
}
const handleStart = ({ offsetX: x, offsetY: y }) => {
isDrawing = true;
start = { x, y };
};
const handleEnd = () => {
isDrawing = false;
};
const dragEnable = () => {
canDragAndZoom = canDragAndZoom ? false : true;
};
const handleMove = (event) => {
if (!isDrawing) return;
let canvas = document.getElementById("myCanvas");
let context = canvas.getContext("2d");
let rect = canvas.getBoundingClientRect();
let currentX = event.clientX - rect.left;
let currentY = event.clientY - rect.top;
const { x, y } = start;
context.beginPath();
context.moveTo(x, y);
context.lineTo(currentX, currentY);
context.stroke();
start = { x: currentX, y: currentY };
};
function trackTransforms(ctx) {
var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
var xform = svg.createSVGMatrix();
ctx.getTransform = function () {
return xform;
};
var savedTransforms = [];
var save = ctx.save;
ctx.save = function () {
savedTransforms.push(xform.translate(0, 0));
return save.call(ctx);
};
var restore = ctx.restore;
ctx.restore = function () {
xform = savedTransforms.pop();
return restore.call(ctx);
};
var scale = ctx.scale;
ctx.scale = function (sx, sy) {
xform = xform.scaleNonUniform(sx, sy);
return scale.call(ctx, sx, sy);
};
var rotate = ctx.rotate;
ctx.rotate = function (radians) {
xform = xform.rotate((radians * 180) / Math.PI);
return rotate.call(ctx, radians);
};
var translate = ctx.translate;
ctx.translate = function (dx, dy) {
xform = xform.translate(dx, dy);
return translate.call(ctx, dx, dy);
};
var transform = ctx.transform;
ctx.transform = function (a, b, c, d, e, f) {
var m2 = svg.createSVGMatrix();
m2.a = a;
m2.b = b;
m2.c = c;
m2.d = d;
m2.e = e;
m2.f = f;
xform = xform.multiply(m2);
return transform.call(ctx, a, b, c, d, e, f);
};
var setTransform = ctx.setTransform;
ctx.setTransform = function (a, b, c, d, e, f) {
xform.a = a;
xform.b = b;
xform.c = c;
xform.d = d;
xform.e = e;
xform.f = f;
return setTransform.call(ctx, a, b, c, d, e, f);
};
var pt = svg.createSVGPoint();
ctx.transformedPoint = function (x, y) {
pt.x = x;
pt.y = y;
return pt.matrixTransform(xform.inverse());
};
}
</script>
<canvas
id="myCanvas"
bind:this={canvas}
on:mousedown={handleStart}
on:mouseup={handleEnd}
on:mouseleave={handleEnd}
on:mousemove={handleMove}
/>
<a href="#" on:click={dragEnable}>
<i class="f7-icons presc_icon">pencil_outline</i>
</a>
After so many try and lots of effort i was able to solve the issue and now it is working as expected .
just had to add some line in handleMove function .
I am posting the solution as answer here so that any one next time can learn it and use the whole code with answer .
const handleMove = (event) => {
if (!isDrawing) return;
let currentX = event.offsetX;
let currentY = event.offsetY;
const { x, y } = start;
let startx = ctx.transformedPoint(x, y);
let endy = ctx.transformedPoint(currentX, currentY);
ctx.beginPath();
ctx.moveTo(startx.x, startx.y);
ctx.lineTo(endy.x, endy.y);
ctx.stroke();
start = { x: currentX, y: currentY };
};
I am trying to make an 'undo' action like Ctrl + Z.
I made a simple canvas paint example to make it easier to understand what I would like. When the user moves the mouse without releasing mouse1, something will be drawn on the canvas. Then, when they stop pressing mouse1, the drawing ends. The Ctrl + Z shortcut will undo these drawings.
Here is the code:
//var
const canvas = document.getElementById('canvas');
const cC = canvas.getContext('2d');
//trigger for write or not
var pressedQ = false;
//
function getMousePosition(evt) {
var rect = canvas.getBoundingClientRect();
var root = document.documentElement;
var mouseX = evt.clientX - rect.left - root.scrollLeft;
var mouseY = evt.clientY - rect.top - root.scrollTop;
return {
x: mouseX,
y: mouseY
};
}
function writeCircle(posX, posY, size, color) {
cC.fillStyle = 'black';
cC.beginPath();
cC.arc(posX, posY, size, 0, Math.PI*2, true);
cC.fill();
}
function pencil(evt) {
if (pressedQ == true) {
var mousePos = getMousePosition(evt);
writeCircle(mousePos.x, mousePos.y, 50);
}
else{}
}
function activate(textmode, evt) {
pressedQ = true;
console.log('start');
}
function deactivate() {
pressedQ = false;
console.log('finish');
}
window.onload = function() {
cC.clearRect(0, 0, canvas.width, canvas.height);
canvas.addEventListener('mousedown', activate);
canvas.addEventListener('mousemove', pencil);
canvas.addEventListener('mouseup', deactivate);
}
<canvas id="canvas" width="700" height="490"></canvas>
Thanks!
Note: I cant understand ES2015+ syntax
What you're looking for is the command design pattern. Since you're using functions here, you just need to store the function name and its parameters. Then, you can use that data to call the function again later. The following example isn't perfect, but it should demonstrate the basic idea. (I tried to avoid any JavaScript syntax that was added after the 2015 update)
var canvas = document.getElementById('canvas');
var cC = canvas.getContext('2d');
var pressedQ = false; //trigger for write or not
var commands = [];
var commandTypes = {
drawCircle: function (posX, posY, size, color) {
cC.fillStyle = 'black';
cC.beginPath();
cC.arc(posX, posY, size, 0, Math.PI * 2, true);
cC.fill();
}
};
function execute() {
var commandType = arguments[0];
var data = Array.prototype.slice.call(arguments, 1);
if (!commandTypes.hasOwnProperty(commandType))
throw new Error(commandType + ' is not a real command');
commandTypes[commandType].apply(null, data);
}
function pushAndExecute() {
commands.push(arguments);
execute.apply(null, arguments);
}
function getMousePosition(evt) {
var rect = canvas.getBoundingClientRect();
var root = document.documentElement;
return {
x: evt.offsetX - rect.left - root.scrollLeft,
y: evt.offsetY - rect.top - root.scrollTop
};
}
function pencil(evt) {
if (!pressedQ) return;
var mousePos = getMousePosition(evt);
pushAndExecute('drawCircle', mousePos.x, mousePos.y, 50);
}
function activate(evt) {
pressedQ = true;
// console.log('start');
pencil(evt);
}
function deactivate() {
pressedQ = false;
// console.log('finish');
}
function handleKeys(evt) {
if (evt.ctrlKey && evt.key === 'z') {
// console.log('undo');
// Remove the most recent command from the list
commands.splice(-1, 1);
// Clear canvas
cC.clearRect(0, 0, canvas.width, canvas.height);
// Re-play all commands (re-draw canvas from scratch)
commands.forEach(function (command) {
execute.apply(null, command);
});
}
}
window.onload = function() {
cC.clearRect(0, 0, canvas.width, canvas.height);
canvas.addEventListener('mousedown', activate);
canvas.addEventListener('mousemove', pencil);
canvas.addEventListener('mouseup', deactivate);
window.addEventListener('keydown', handleKeys);
}
<canvas id="canvas" width="700" height="490"></canvas>
I have made a canvas in html5. I'm using it to show parts of a bigger image that you can move and I want to make it possible to zoom both in and out. But I do not know how to make the image scale from a certain point. When I increase the size of the image the part which is shown by the canvas is moved, I want the part in the center of the canvas to be the focus point when the scaling is complete, but no matter how I try it gets distorted some how. It seems like depending on which part of the image is shown, when the scaling happens that part of the picture is going to get moved to different coordinates. I do not know what type of algorithm I would have to use so calculate the movement of the image.
This is a link to a visual example of the movement i mean, https://imgur.com/a/aZiVM, the two images are scaled the same but depending on which part of the image that is visible in the canvas, the amount the image needs to be moved tor the zoom differs.
This is my code, but it isn't really working that well.
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js"></script>
<style>
body {
padding: 0px;
margin: 0px;
}
</style>
</head>
<body>
<div id="map" style="position:absolute;top:10px;left:50px;">
<canvas id="canvas" width="800" height="600" style="float:left;border:1px solid #000000;">Your browser doesn't support canvas</canvas>
<div id="floorDown" onMouseDown="zoomIn()" style="width:200px;height:50px;float:left;">Zoom in</div><br>
<div id="floorDown" onMouseDown="zoomOut()" style="width:200px;height:50px;float:left;">Zoom out</div>
</div>
<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var canvasOffset=$("#canvas").offset();
var offsetX=canvasOffset.left;
var offsetY=canvasOffset.top;
var startX;
var startY;
var isDown=false;
//start position
var imageX=0;
var imageY=0;
var imageWidth,imageHeight,imageRight,imageBottom;
var draggingImage=false;
var startX;
var startY;
var img=new Image();
img.onload=function(){
imageWidth=img.width;
imageHeight=img.height;
draw();
}
img.src='http://orig00.deviantart.net/35cb/f/2013/030/f/0/tripolar_by_zy0rg-d5t9tqh.png';
function draw(){
// clear the canvas
ctx.clearRect(0,0,canvas.width,canvas.height);
//Disable anti-aliasing
ctx.imageSmoothingEnabled=false;
// draw the image
ctx.drawImage(img,0,0,img.width,img.height,imageX,imageY,imageWidth,imageHeight);
}
function handleMouseDown(e){
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
draggingImage= true;
}
function handleMouseUp(e){
draggingImage=false;
draw();
}
function handleMouseOut(e){
handleMouseUp(e);
}
function handleMouseMove(e){
if(draggingImage){
imageClick=false;
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// move the image by the amount of the latest drag
var dx=mouseX-startX;
var dy=mouseY-startY;
imageX+=dx;
imageY+=dy;
// reset the startXY for next time
startX=mouseX;
startY=mouseY;
// redraw the image with border
draw();
}
}
// TEST zoom in/out functions
function zoomIn() {
imageX=imageX*2;
imageY=imageY*2;
imageWidth=imageWidth*2;
imageHeight=imageHeight*2;
draw();
}
function zoomOut() {
imageX=imageX/2;
imageY=imageY/2;
imageWidth=imageWidth/2;
imageHeight=imageHeight/2;
draw();
}
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
$("#canvas").mouseout(function(e){handleMouseOut(e);});
</script>
</body>
Given the origin (pos) and scale to zoom at a point
var pos = {x : 0, y : 0};
var scale = 1;
function zoomAt(x,y,_scale)
scale *= _scale
pos.x = x - (x - pos.x) * scale;
pos.y = y - (y - pos.y) * scale;
}
You can then create the transform with
ctx.setTransform(scale, 0, 0, scale, pos.x, pos.y);
So to zoom at the center of screen
zoomAt(canvas.width / 2, canvas.height / 2, 1.1); // zoom in
zoomAt(canvas.width / 2, canvas.height / 2, 1 / 1.1); // zoom out
Put all together
// the following globals are available
// w, h, cw, ch, width height centerWidth centerHeight of canvas
// canvas, ctx, mouse, globalTime
const image = new Image;
image.src = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/KTZ_2TE10U_Aynabulak.jpg/800px-KTZ_2TE10U_Aynabulak.jpg";
const font = {
font : "28px Arial",
textAlign : "center",
textBaseline : "middle",
}
function setStyle(ctx, style){
Object.keys(style).forEach(key => ctx[key] = style[key]);
}
// Handle all key input
const keys = { // key input object
ArrowLeft : false, // only add key names you want to listen to
ArrowRight : false,
keyEvent (event) {
if (keys[event.code] !== undefined) { // are we interested in this key
keys[event.code] = event.type === "keydown";
}
}
}
// add key listeners
document.addEventListener("keydown", keys.keyEvent);
document.addEventListener("keyup", keys.keyEvent);
const view = (()=>{
const matrix = [1,0,0,1,0,0]; // current view transform
const invMatrix = [1,0,0,1,0,0]; // current inverse view transform
var m = matrix; // alias
var im = invMatrix; // alias
var rotate = 0; // current x axis direction in radians
var scale = 1; // current scale
const pos = { // current position of origin
x : 0,
y : 0,
}
var dirty = true;
return {
apply(ctx){
if(dirty){ this.update() }
var m = matrix;
ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
},
update(){ // call to update transforms
var xdx = Math.cos(rotate) * scale;
var xdy = Math.sin(rotate) * scale;
m[0] = xdx;
m[1] = xdy;
m[2] = -xdy;
m[3] = xdx;
m[4] = pos.x;
m[5] = pos.y;
// calculate the inverse transformation
cross = m[0] * m[3] - m[1] * m[2];
im[0] = m[3] / cross;
im[1] = -m[1] / cross;
im[2] = -m[2] / cross;
im[3] = m[0] / cross;
dirty = false;
},
toWorld(x,y,point = {}){ // convert screen to world coords
var xx, yy;
if(dirty){ this.update() }
xx = x - matrix[4];
yy = y - matrix[5];
point.x = xx * im[0] + yy * im[2];
point.y = xx * im[1] + yy * im[3];
return point;
},
toScreen(x,y,point = {}){ // convert world coords to coords
if(dirty){ this.update() }
point.x = x * m[0] + y * m[2] + m[4];
point.y = x * m[1] + y * m[3] + m[5];
return point;
},
movePos(x,y){
pos.x += x;
pos.y += y;
dirty = true;
},
setPos(x,y){
pos.x = x;
pos.y = y;
dirty = true;
},
setScale(sc){
scale = sc;
dirty = true;
},
scaleScale(sc){
scale *= sc;
dirty = true;
},
scaleAt(x,y,sc){
if(dirty){ this.update() }
scale *= sc;
pos.x = x - (x - pos.x) * sc;
pos.y = y - (y - pos.y) * sc;
dirty = true;
}
};
})();
function onResize(){
setStyle(ctx,font);
}
const drag = {
dragging : false,
lastX : 0,
lastY : 0,
update(){
var dx,dy;
if(mouse.w){
if(mouse.w < 0){
mouse.w += 10;
view.scaleAt(mouse.x,mouse.y,1/1.02);
if(mouse.w > 0){
mouse.w = 0;
}
} else if(mouse.w > 0){
mouse.w -= 10;
view.scaleAt(mouse.x,mouse.y,1.02);
if(mouse.w < 0){
mouse.w = 0;
}
}
}
if(mouse.buttonRaw){
if(!this.dragging){
this.dragging = true;
this.lastX = mouse.x;
this.lastY = mouse.y;
}else{
if(mouse.buttonRaw & 1){
dx = mouse.x-this.lastX;
dy = mouse.y-this.lastY;
this.lastX = mouse.x;
this.lastY = mouse.y;
view.movePos(dx,dy);
}
}
}else{
if(this.dragging){
this.dragging = false;
}
}
}
}
function display() { // call once per frame
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
if(keys.ArrowLeft ){ mouse.w += 10 }
if(keys.ArrowRight){ mouse.w -= 10 }
drag.update();
if(image.complete){
view.apply(ctx);
ctx.drawImage(image,0,0);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillText("Click drag to pan. Wheel to zoom or left/right arrow.",cw,20)
}else{
ctx.fillText("Loading Image...",cw,ch)
}
}
/******************************************************************************
The code from here down is generic full page mouse and canvas boiler plate
code. As I do many examples which all require the same mouse and canvas
functionality I have created this code to keep a consistent interface. The
Code may or may not be part of the answer.
This code may or may not have ES6 only sections so will require a transpiler
such as babel.js to run on legacy browsers.
*****************************************************************************/
// V2.0 ES6 version for Stackoverflow and Groover QuickRun
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0;
// You can declare onResize (Note the capital R) as a callback that is also
// called once at start up. Warning on first call canvas may not be at full
// size.
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var resizeTimeoutHandle;
var firstRun = true;
function createCanvas () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 10;
document.body.appendChild(c);
return c;
}
function resizeCanvas () {
if (canvas === undefined) { canvas = createCanvas() }
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals() }
if (typeof onResize === "function") {
clearTimeout(resizeTimeoutHandle);
if (firstRun) { onResize() }
else { resizeTimeoutHandle = setTimeout(onResize, RESIZE_DEBOUNCE_TIME) }
firstRun = false;
}
}
function setGlobals () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) { e.preventDefault() }
var m; // alias for mouse
var mouse = {
x : 0, y : 0, w : 0, // mouse position and wheel
alt : false, shift : false, ctrl : false, // mouse modifiers
buttonRaw : 0,
over : false, // true if mouse over the element
buttonOnMasks : [0b1, 0b10, 0b100], // mouse button on masks
buttonOffMasks : [0b110, 0b101, 0b011], // mouse button off masks
active : false,
bounds : null,
eventNames : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(","),
event(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.buttonOnMasks[e.which - 1] }
else if (t === "mouseup") { m.buttonRaw &= m.buttonOffMasks[e.which - 1] }
else if (t === "mouseout") { m.over = false }
else if (t === "mouseover") { m.over = true }
else if (t === "mousewheel") {
m.w = e.wheelDelta
e.preventDefault();
}
else if (t === "DOMMouseScroll") {
m.w = -e.detail
e.preventDefault();
}
},
start(element) {
m.element = element === undefined ? document : element;
m.eventNames.forEach(name => document.addEventListener(name, mouse.event) );
document.addEventListener("contextmenu", preventDefault, false);
m.active = true;
},
}
m = mouse;
return mouse;
})();
function update(timer) { // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update)
}
setTimeout(function(){
canvas = createCanvas();
mouse.start(canvas, true);
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();
/** SimpleFullCanvasMouse.js end **/
#imageCC {
font-family : arial;
font-size : 10px;
position : absolute;
z-index : 100;
bottom : 3px;
right : 10px;
background : rgba(255,255,255,0.7);
}
<div id=imageCC>Image rights.
Kabelleger / David Gubler (http://www.bahnbilder.ch), KTZ 2TE10U Aynabulak, CC BY-SA 3.0
</div>
I have a canvas function which draws a square if I click on the canvas field and move the mouse, that works so far.
My Problem is that if I release the mouse and click at the canvas again the old drawn rectangle vanishes.
How do I make it possible that the old drawn does not get vanished.
My function:
function foo() {
var tool = this;
this.started = false;
var canvasx = canvas.offsetLeft;
var canvasy = canvas.offsetTop;
var last_mousex = 0;
var last_mousey = 0;
var mousex = 0;
var mousey = 0;
this.mousedown = function (ev) {
if(checkboxSquare.checked) {
last_mousex = parseInt(ev.clientX-canvasx);
last_mousey = parseInt(ev.clientY-canvasy);
context.strokeStyle = $('#selectColor').val();
context.lineWidth = $('#selectWidth').val();
tool.started = true;
}
};
this.mousemove = function (ev) {
if (tool.started && checkboxSquare.checked) {
mousex = parseInt(ev.clientX-canvasx);
mousey = parseInt(ev.clientY-canvasy);
context.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
context.beginPath();
var width = mousex-last_mousex;
var height = mousey-last_mousey;
context.rect(last_mousex,last_mousey,width,height);
context.stroke();
}
};
this.mouseup = function (ev) {
if (tool.started && checkboxSquare.checked) {
tool.mousemove(ev);
tool.started = false;
}
};
}
It Looks something like this: http://jsfiddle.net/AbdiasSoftware/kqW4X/
The old drawn rectangle vanishes on click because, you are clearing the entire canvas each time before drawing a rectangle.
The easiest workaround would be to save the entire canvas as an image on mouseup and draw that image before drawing each rectangle.
var canvas;
var _foo = new foo();
canvas.onmousedown = _foo.mousedown;
canvas.onmousemove= _foo.mousemove;
canvas.onmouseup = _foo.mouseup;
function foo() {
canvas = $('#canvas')[0];
var context = canvas.getContext('2d');
var checkboxSquare = $('#checkboxSquare')[0];
var img = new Image();
var tool = this;
this.started = false;
var last_mousex = 0;
var last_mousey = 0;
var mousex = 0;
var mousey = 0;
this.mousedown = function (ev) {
if(checkboxSquare.checked) {
last_mousex = ev.offsetX;
last_mousey = ev.offsetY;
context.strokeStyle = $('#selectColor').val();
context.lineWidth = $('#selectWidth').val();
tool.started = true;
}
};
this.mousemove = function (ev) {
if (tool.started && checkboxSquare.checked) {
mousex = ev.offsetX;
mousey = ev.offsetY;
context.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
context.drawImage(img, 0, 0); // draw saved canvas (image)
context.beginPath();
var width = mousex-last_mousex;
var height = mousey-last_mousey;
context.rect(last_mousex,last_mousey,width,height);
context.stroke();
}
};
this.mouseup = function (ev) {
if (tool.started && checkboxSquare.checked) {
tool.mousemove(ev);
img.src = canvas.toDataURL(); // save canvas as image
tool.started = false;
}
};
}
canvas {
border: 1px solid black;
cursor: default;
margin-top: 5px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="checkbox" id="checkboxSquare">Square | Color
<select id="selectColor">
<option value="red">red</option>
<option value="green">green</option>
<option value="blue">blue</option>
</select> | Width
<select id="selectWidth">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<canvas id="canvas" width="400" height="400"></canvas>
Just create a background canvas same as the main canvas. When you drag out a new box, first draw the background canvas (with all the past boxes) on the main canvas then the current box being drawn. When you finish dragging the box, just daw it to the background canvas.
const canvas = document.createElement("canvas");
const background = document.createElement("canvas");
canvas.style.border="2px solid black";
canvas.style.cursor = "crosshair";
background.width = canvas.width = innerWidth - 24;
background.height = canvas.height = innerHeight - 24;
const ctx = canvas.getContext("2d");
background.ctx = background.getContext("2d");
document.body.appendChild(canvas);
const bounds = canvas.getBoundingClientRect();
var currentBox;
const boxStyle = {
fillStyle : "#4aF",
strokeStyle : "black",
lineWidth : 3,
lineJoin : "round",
}
const mouse = { x : 0, y : 0,button : false, changed : false };
["mousemove","mousedown","mouseup"].forEach(en => document.addEventListener(en, mouseEvent));
function createBox(x,y,w,h,style){ return {x,y,w,h,style,draw : drawBox} }
function drawBox(ctx){
setStyle(ctx, this.style);
ctx.beginPath();
ctx.rect(this.x,this.y,this.w,this.h);
ctx.fill();
ctx.stroke();
}
function setStyle(ctx, style){ Object.keys(style).forEach(key => ctx[key] = style[key]) }
function mouseEvent(event) {
mouse.x = event.pageX - bounds.left - scrollX;
mouse.y = event.pageY - bounds.top - scrollY;
if(event.type === "mousedown"){ mouse.button = true }
else if(event.type === "mouseup"){ mouse.button = false }
mouse.changed = true;
}
function mainLoop(){
var b = currentBox; // alias for readability
if(mouse.changed){
if(mouse.button){
if(!b){
b = currentBox = createBox(mouse.x,mouse.y,0,0,boxStyle);
}else{
b.w = mouse.x - b.x;
b.h = mouse.y - b.y;
}
}else if(b){
b.draw(background.ctx);
b = currentBox = undefined;
}
if(b){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(background,0,0);
b.draw(ctx);
canvas.style.cursor = "none";
}else{
canvas.style.cursor = "crosshair";
}
mouse.changed = false;
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
Extra Note. Capture the mouse using the Document
When you create canvas drawing apps you should listen to the document mouse events rather than the canvas. When the mouse button is down the mouse is captured and will continue to send mouse events while the mouse is down, even if you have moved off the canvas, document, or event outside the browser window.
This means you can drag content of the canvas and not worry about losing the mouseup event.
Burn some time.
I have some time to burn so will extend the demo above to include selecting and moving existing boxes. Draw boxes as normal. Mouse over boxes will highlight them, click to select them. When selected can be dragged. Uses the same method background image to hold old boxes. But have added a box list to hold old boxes
A more extensive example
const canvas = document.createElement("canvas");
const background = document.createElement("canvas");
canvas.style.border="2px solid black";
canvas.style.cursor = "crosshair";
background.width = canvas.width = innerWidth - 24;
background.height = canvas.height = innerHeight - 24;
const ctx = canvas.getContext("2d");
background.ctx = background.getContext("2d");
document.body.appendChild(canvas);
const bounds = canvas.getBoundingClientRect();
var currentBox;
var selectedBox;
var mouseOverBox;
const styles = {
box : {
fillStyle : "#4aF",
strokeStyle : "black",
lineWidth : 3,
lineJoin : "round",
},
highlight : {
strokeStyle : "white",
lineWidth : 1,
lineJoin : "round",
setLineDash : [[10,10]],
},
selected : {
strokeStyle : "red",
lineWidth : 2,
lineJoin : "round",
setLineDash : [[5,5]],
},
}
const boxes = {
items : [],
add(box){ // add a box and fix width and height to positive
if(box.w < 0){
box.x += box.w;
box.w = -box.w;
}
if(box.h < 0){
box.y += box.h;
box.h = -box.h;
}
boxes.items.push(box)
},
apply(name, ...args){
for(var i = 0; i < boxes.items.length; i ++ ){
boxes.items[i][name](...args);
}
},
};
const mouse = { x : 0, y : 0,button : false, changed : false };
["mousemove","mousedown","mouseup"].forEach(en => document.addEventListener(en, mouseEvent));
const boxBehaviours = {
draw(ctx, style = this.style){
if(!this.hide){
setStyle(ctx, style);
ctx.beginPath();
ctx.rect(this.x,this.y,this.w,this.h);
if(style.fillStyle) { ctx.fill() }
if(style.strokeStyle) {ctx.stroke() }
}
},
isPointOver(x,y){
var b = this;
if(x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b.h){
b.mouseOver = true;
boxBehaviours.topMouseBox = b;
}else {
b.mouseOver =false;
}
},
}
function createBox(x,y,w,h,style){
return {x,y,w,h,style, ...boxBehaviours};
}
function setStyle(ctx, style){
Object.keys(style).forEach(key => {
if(typeof ctx[key] === "function"){
ctx[key](...style[key]);
}else{
ctx[key] = style[key];
}
})
}
function mouseEvent(event) {
mouse.x = event.pageX - bounds.left - scrollX;
mouse.y = event.pageY - bounds.top - scrollY;
if(event.type === "mousedown"){ mouse.button = true }
else if(event.type === "mouseup"){ mouse.button = false }
}
function redrawBackground(){
background.ctx.clearRect(0,0,canvas.width,canvas.height)
boxes.apply("draw",background.ctx);
}
function mainLoop(time){
var b = currentBox; // alias for readability
var mob = mouseOverBox; // alias for readability
var sb = selectedBox; // alias for readability
// first check mouse button. If button down could be
// dragging a selected box or creating a new box
if(mouse.button){
if(sb){ // is selected box
if(!mouse.drag){ // start the drag
mouse.drag = {x : mouse.x - sb.x, y : mouse.y - sb.y}
}else{ // move the box
sb.x = mouse.x- mouse.drag.x;
sb.y = mouse.y- mouse.drag.y;
}
}else{ // else muse be create (or select click)
if(!b){
b = currentBox = createBox(mouse.x,mouse.y,0,0,styles.box);
}else{
b.w = mouse.x - b.x;
b.h = mouse.y - b.y;
}
}
}else if(b || sb){ // mouse up and there is a box
if(sb){ // if selected box
if(mouse.drag){ // is dragging then drop it
mouse.drag = undefined;
sb.hide = false;
redrawBackground();
sb = selectedBox = undefined;
}
// is the mouse is down and has not moved over 2 pixels
// and there is a mob (mouseOverBox) under it
// then dump the new box and select the mob box
}else if(Math.abs(b.w) < 2 && Math.abs(b.h) < 2 && mob){
sb = selectedBox = mob;
mob = mouseOverBox = undefined;
b = currentBox = undefined;
sb.hide = true;
redrawBackground();
}else{
// just a normal box add it to box array
// draw it and remove it from currentBox
boxes.add(b);
b.draw(background.ctx);
b = currentBox = undefined;
}
}
// clear andf draw background
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(background,0,0);
if(b){ // is there a current box then draw that
b.draw(ctx);
canvas.style.cursor = "none";
} else { // no current box so
// find any boxes under the mouse
boxBehaviours.topMouseBox = null;
boxes.apply("isPointOver",mouse.x, mouse.y);
// is there a selected box (sb)
if(sb){ // yes selected box then draw it
ctx.save();
styles.selected.lineDashOffset = time / 25;
sb.hide = false;
sb.draw(ctx,styles.selected);
sb.hide = true;
ctx.restore();
canvas.style.cursor = "move";
// no selected box sp then just high light the box under the
// mouse and assign it to mouseOverBox (mob);
}else if(boxBehaviours.topMouseBox){
mob = mouseOverBox = boxBehaviours.topMouseBox;
ctx.save();
styles.highlight.lineDashOffset = time / 20;
mob.draw(ctx, styles.highlight);
ctx.restore();
canvas.style.cursor = "pointer";
}else{
canvas.style.cursor = "crosshair";
}
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
var point = [];
var clicks = 0;
var sketch = document.querySelector('#sketch');
var sketch_style = getComputedStyle(sketch);
// Creating a tmp canvas
var tmp_canvas = document.createElement('canvas');
var tmp_ctx = tmp_canvas.getContext('2d');
tmp_canvas.id = 'tmp_canvas';
tmp_canvas.width = parseInt(sketch_style.getPropertyValue('width'));
tmp_canvas.height = parseInt(sketch_style.getPropertyValue('height'));
sketch.appendChild(tmp_canvas);
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.id = 'paint';
canvas.width = parseInt(sketch_style.getPropertyValue('width'));
canvas.height = parseInt(sketch_style.getPropertyValue('height'));
sketch.appendChild(canvas);
tmp_canvas.addEventListener('mousedown', mousedown, false);
tmp_canvas.addEventListener('mousemove', mousemove, false);
tmp_canvas.addEventListener('mouseup', mouseup, false);
function mousemove(e) {
if (clicks == 1) {
x = e.layerX - this.offsetLeft;
y = e.layerY - this.offsetTop;
showRect(x, y);
}
}
function showRect(x, y) {
tmp_ctx.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
tmp_ctx.beginPath();
var width = x - point[0].x;
var height = y - point[0].y;
tmp_ctx.rect(point[0].x, point[0].y, width, height);
tmp_ctx.stroke();
}
function mousedown(e) {
x = e.layerX - this.offsetLeft;
y = e.layerY - this.offsetTop;
point.push({
x,
y
});
clicks++;
};
function mouseup() {
context.drawImage(tmp_canvas, 0, 0);
clicks = 0;
point.length = 0;
}
html, body {
width: 100% ;
height: 100% ;
}
#sketch {
border: 10px solid gray;
height: 100% ;
position: relative;
}
#tmp_canvas {
position: absolute;
left: 0px;
right: 0;
bottom: 0;
top: 0;
cursor: crosshair;
}
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="sketch">
</div>
</body>
</html>
Try to do in temporary canvas and redraw all in main.
jsfiddle:-https://jsfiddle.net/durga598/v0m06faz/
I'm assuming that the foo() function is being called for every frame, either through setInterval or requestAnimationFrame. If my assumption is right, the reason why your previously drawn square disappears is because you are only storing the x and y coordinates of one rectangle, and every time you click on the canvas again, it gets overwritten by the new values for the new rectangle.
To solve your problem, you should store the x and y coordinates as well as the dimensions of the square on mouseup. These coordinates can be stored in an array.
var squares = [];
this.mouseup = function (ev) {
// other code
var square = {
x: last_mousex,
y: last_mousey,
width: mousex - last_mousex,
height: mousey - last_mousey
};
squares.push(square);
};
Now every time you draw the square, draw the squares stored in the squares array first.
this.mousemove = function (ev) {
if (tool.started && checkboxSquare.checked) {
// other code
context.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
// draw each square in the squares array after clearning the canvas
squares.forEach(function(square) {
context.beginPath();
context.rect(square.x, square.y, square.width, square.height);
});
context.beginPath();
var width = mousex - last_mousex;
var height = mousey - last_mousey;
context.rect(last_mousex, last_mousey, width, height);
context.stroke();
}
};
You'll see some code repetitions in drawing the squares, it's a good opportunity to abstract it into a separate function.
http://jsfiddle.net/cs5Sg/11/
I want to do the scalable canvas. I created two circles (arcs) and one line, when you click on circle and move it, the line will follow and change position. The problem is when I added code for resize:
var canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d'),
radius = 12,
p = null,
point = {
p1: { x:100, y:250 },
p2: { x:400, y:100 }
},
moving = false;
window.addEventListener("resize", OnResizeCalled, false);
function OnResizeCalled() {
var gameWidth = window.innerWidth;
var gameHeight = window.innerHeight;
var scaleToFitX = gameWidth / 800;
var scaleToFitY = gameHeight / 480;
var currentScreenRatio = gameWidth / gameHeight;
var optimalRatio = Math.min(scaleToFitX, scaleToFitY);
if (currentScreenRatio >= 1.77 && currentScreenRatio <= 1.79) {
canvas.style.width = gameWidth + "px";
canvas.style.height = gameHeight + "px";
}
else {
canvas.style.width = 800 * optimalRatio + "px";
canvas.style.height = 480 * optimalRatio + "px";
}
}
function init() {
return setInterval(draw, 10);
}
canvas.addEventListener('mousedown', function(e) {
for (p in point) {
var
mouseX = e.clientX - 1,
mouseY = e.clientY - 1,
distance = Math.sqrt(Math.pow(mouseX - point[p].x, 2) + Math.pow(mouseY - point[p].y, 2));
if (distance <= radius) {
moving = p;
break;
}
}
});
canvas.addEventListener('mouseup', function(e) {
moving = false;
});
canvas.addEventListener('mousemove', function(e) {
if(moving) {
point[moving].x = e.clientX - 1;
point[moving].y = e.clientY - 1;
}
});
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.moveTo(point.p1.x,point.p1.y);
context.lineTo(point.p2.x,point.p2.y);
context.closePath();
context.fillStyle = '#8ED6FF';
context.fill();
context.stroke();
for (p in point) {
context.beginPath();
context.arc(point[p].x,point[p].y,radius,0,2*Math.PI);
context.fillStyle = 'red';
context.fill();
context.stroke();
}
context.closePath();
}
init();
The canvas is scalable, but the problem is with the points (circles). When you change the window size, they still have the same position on the canvas area, but the distance change (so the click option fails). How to fix that?
Live Demo
Basically you just need a scaler value that takes into account the actual dimensions of the canvas, and the css dimensions like so
var scaledX = canvas.width/ canvas.offsetWidth,
scaledY = canvas.height/ canvas.offsetHeight;
And then every time the window is resized you need to make sure to update the scaler values.
function OnResizeCalled() {
scaledX = canvas.width/ canvas.offsetWidth;
scaledY = canvas.height/ canvas.offsetHeight;
}
To get the correct coords you need to multiply the clientX and clientY by the scaler in all of your mouse events
canvas.addEventListener('mousedown', function(e) {
for (p in point) {
var
mouseX = e.clientX*scaledX,
mouseY = e.clientY*scaledY,
distance = Math.sqrt(Math.pow(mouseX - point[p].x, 2) + Math.pow(mouseY - point[p].y, 2));
if (distance <= radius) {
moving = p;
break;
}
}
});
canvas.addEventListener('mousemove', function(e) {
var mouseX = e.clientX*scaledX,
mouseY = e.clientY*scaledY;
if(moving) {
point[moving].x = mouseX;
point[moving].y = mouseY;
}
});
Full code
var canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d'),
radius = 12,
p = null,
point = {
p1: { x:100, y:250},
p2: { x:400, y:100}
},
moving = false,
scaledX = canvas.width/ canvas.offsetWidth,
scaledY = canvas.height/ canvas.offsetHeight;
window.addEventListener("resize", OnResizeCalled, false);
function OnResizeCalled() {
var gameWidth = window.innerWidth;
var gameHeight = window.innerHeight;
var scaleToFitX = gameWidth / 800;
var scaleToFitY = gameHeight / 480;
var currentScreenRatio = gameWidth / gameHeight;
var optimalRatio = Math.min(scaleToFitX, scaleToFitY);
if (currentScreenRatio >= 1.77 && currentScreenRatio <= 1.79) {
canvas.style.width = gameWidth + "px";
canvas.style.height = gameHeight + "px";
}
else {
canvas.style.width = 800 * optimalRatio + "px";
canvas.style.height = 480 * optimalRatio + "px";
}
scaledX = canvas.width/ canvas.offsetWidth;
scaledY = canvas.height/ canvas.offsetHeight;
}
function init() {
return setInterval(draw, 10);
}
canvas.addEventListener('mousedown', function(e) {
for (p in point) {
var
mouseX = e.clientX*scaledX,
mouseY = e.clientY*scaledY,
distance = Math.sqrt(Math.pow(mouseX - point[p].x, 2) + Math.pow(mouseY - point[p].y, 2));
if (distance <= radius) {
moving = p;
break;
}
}
});
canvas.addEventListener('mouseup', function(e) {
moving = false;
});
canvas.addEventListener('mousemove', function(e) {
var mouseX = e.clientX*scaledX,
mouseY = e.clientY*scaledY;
if(moving) {
point[moving].x = mouseX; //1 is the border of your canvas
point[moving].y = mouseY;
}
});
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.moveTo(point.p1.x,point.p1.y);
context.lineTo(point.p2.x,point.p2.y);
context.closePath();
context.fillStyle = '#8ED6FF';
context.fill();
context.stroke();
for (p in point) {
context.beginPath();
context.arc(point[p].x,point[p].y,radius,0,2*Math.PI);
context.fillStyle = 'red';
context.fill();
context.stroke();
}
context.closePath();
}
init();