I want to draw the rectangle using the mouse down event only when the button is clicked. I have added the javascript code for a button event but its not working as it start drawing when the checkbox is clicked but keeps drawing even if the check box is unchecked. I want this to draw only when the button is checked. can I limit the rectangle to be drawn only once.
My fiddle is : http://jsfiddle.net/G6tLn/7/
I am modifying the simonsaris code for the selection handles using the mouse events.
Please help.
<html>
<head>
<title>Canvas Demo</title>
<style type="text/css">
hr{margin:0px; padding:0px;}
form, table {display:inline; margin:0px; padding:0px;}
.ck-button {
margin:4px;
background-color:#8080FF;
border-radius:4px;
border:1px solid #D0D0D0;
overflow:auto;
float:left;
}
.ck-button:hover {
background-color:#B2B2FF;
}
.ck-button label {
float:left;
width:4.0em;
}
.ck-button label span {
text-align:center;
padding:3px 0px;
display:block;
}
.ck-button label input {
position:absolute;
top:-20px;
}
.ck-button input:checked + span {
background-color:#0000FF;
color:white;
}
#container2 {
position: relative;
}
#canvas2 {
border: 1px solid #000;
}
</style>
</head>
<body>
<canvas id="canvas2"
width="450"
height="450"
style="border:2px solid black">
Your browser does not support the HTML5 canvas tag.
</canvas>
<div class="ck-button">
<input type="checkbox" id="btnROI" value="ROI" onclick="CallRoi()">
<span>ROI</span>
<input type="checkbox" id="btnMetric" value="Metric" onclick="CallMetric()">
<span>Metric</span>
</div>
<script type="text/javascript">
// holds all our boxes
var boxes2 = [];
// New, holds the 8 tiny boxes that will be our selection handles
// the selection handles will be in this order:
// 0 1 2
// 3 4
// 5 6 7
var selectionHandles = [];
// Hold canvas information
var canvas;
var ctx;
var WIDTH;
var HEIGHT;
var INTERVAL = 20; // how often, in milliseconds, we check to see if a redraw is needed
var isDrag = false;
var isResizeDrag = false;
var isCreationDrag = false;
var expectResize = -1; // New, will save the # of the selection handle if the mouse is over one.
var mx, my, startMx, startMy; // mouse coordinates
// when set to true, the canvas will redraw everything
// invalidate() just sets this to false right now
// we want to call invalidate() whenever we make a change
var canvasValid = false;
// The node (if any) being selected.
// If in the future we want to select multiple objects, this will get turned into an array
var mySel = null;
// The selection color and width. Right now we have a red selection with a small width
var mySelColor = '#CC0000';
var mySelWidth = 2;
var mySelBoxColor = 'darkred'; // New for selection boxes
var mySelBoxSize = 6;
// we use a fake canvas to draw individual shapes for selection testing
var ghostcanvas;
var gctx; // fake canvas context
// since we can drag from anywhere in a node
// instead of just its x/y corner, we need to save
// the offset of the mouse when we start dragging.
var offsetx, offsety;
// Padding and border style widths for mouse offsets
var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
// Box object to hold data
function Box2() {
this.x = 0;
this.y = 0;
this.w = 1; // default width and height?
this.h = 1;
this.fill = '#444444';
}
// New methods on the Box class
Box2.prototype = {
// we used to have a solo draw function
// but now each box is responsible for its own drawing
// mainDraw() will call this with the normal canvas
// myDown will call this with the ghost canvas with 'black'
draw:function (context, optionalColor) {
if (context === gctx) {
context.fillStyle = 'black'; // always want black for the ghost canvas
} else {
context.fillStyle = this.fill;
}
// We can skip the drawing of elements that have moved off the screen:
if (this.x > WIDTH || this.y > HEIGHT) return;
if (this.x + this.w < 0 || this.y + this.h < 0) return;
context.fillRect(this.x, this.y, this.w, this.h);
// draw selection
// this is a stroke along the box and also 8 new selection handles
if (mySel === this) {
context.strokeStyle = mySelColor;
context.lineWidth = mySelWidth;
context.strokeRect(this.x, this.y, this.w, this.h);
// draw the boxes
var half = mySelBoxSize / 2;
// 0 1 2
// 3 4
// 5 6 7
// top left, middle, right
selectionHandles[0].x = this.x - half;
selectionHandles[0].y = this.y - half;
selectionHandles[1].x = this.x + this.w / 2 - half;
selectionHandles[1].y = this.y - half;
selectionHandles[2].x = this.x + this.w - half;
selectionHandles[2].y = this.y - half;
//middle left
selectionHandles[3].x = this.x - half;
selectionHandles[3].y = this.y + this.h / 2 - half;
//middle right
selectionHandles[4].x = this.x + this.w - half;
selectionHandles[4].y = this.y + this.h / 2 - half;
//bottom left, middle, right
selectionHandles[6].x = this.x + this.w / 2 - half;
selectionHandles[6].y = this.y + this.h - half;
selectionHandles[5].x = this.x - half;
selectionHandles[5].y = this.y + this.h - half;
selectionHandles[7].x = this.x + this.w - half;
selectionHandles[7].y = this.y + this.h - half;
context.fillStyle = mySelBoxColor;
for (var i = 0; i < 8; i++) {
var cur = selectionHandles[i];
context.fillRect(cur.x, cur.y, mySelBoxSize, mySelBoxSize);
}
}
} // end draw
}
//Initialize a new Box, add it, and invalidate the canvas
function addRect(x, y, w, h, fill) {
var rect = new Box2;
rect.x = x;
rect.y = y;
rect.w = w
rect.h = h;
rect.fill = fill;
boxes2.push(rect);
invalidate();
}
function CallRoi(){
var oROI = document.getElementById("btnROI");
console.log(oROI.checked);
if (oROI.checked) {
init2();
}
}
function CallMetric(){
var oMetric = document.getElementById("btnMetric");
console.log(btnMetric.checked);
if (oMetric.checked) {
init1();
console.log("hello");
}
}
// initialize our canvas, add a ghost canvas, set draw loop
// then add everything we want to intially exist on the canvas
function init2() {
canvas = document.getElementById('canvas2');
HEIGHT = canvas.height;
WIDTH = canvas.width;
ctx = canvas.getContext('2d');
ghostcanvas = document.createElement('canvas');
ghostcanvas.height = HEIGHT;
ghostcanvas.width = WIDTH;
gctx = ghostcanvas.getContext('2d');
//fixes a problem where double clicking causes text to get selected on the canvas
canvas.onselectstart = function () {
return false;
}
// fixes mouse co-ordinate problems when there's a border or padding
// see getMouse for more detail
if (document.defaultView && document.defaultView.getComputedStyle) {
stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
}
// make mainDraw() fire every INTERVAL milliseconds
setInterval(mainDraw, INTERVAL);
// set our events. Up and down are for dragging,
// double click is for making new boxes
canvas.onmousedown = myDown;
canvas.onmouseup = myUp;
canvas.ondblclick = myDblClick;
canvas.onmousemove = myMove;
// set up the selection handle boxes
for (var i = 0; i < 8; i++) {
var rect = new Box2;
selectionHandles.push(rect);
}
}
function init1() {
canvas = document.getElementById('canvas2');
HEIGHT = canvas.height;
WIDTH = canvas.width;
ctx = canvas.getContext('2d');
ghostcanvas = document.createElement('canvas');
ghostcanvas.height = HEIGHT;
ghostcanvas.width = WIDTH;
gctx = ghostcanvas.getContext('2d');
//fixes a problem where double clicking causes text to get selected on the canvas
canvas.onselectstart = function () { return false; }
// fixes mouse co-ordinate problems when there's a border or padding
// see getMouse for more detail
if (document.defaultView && document.defaultView.getComputedStyle) {
stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
}
// make mainDraw() fire every INTERVAL milliseconds
setInterval(mainDraw, INTERVAL);
// set our events. Up and down are for dragging,
// double click is for making new boxes
canvas.onmousedown = myDown;
canvas.onmouseup = myUp;
canvas.ondblclick = myDblClick;
canvas.onmousemove = myMove;
// set up the selection handle boxes
for (var i = 0; i < 8; i ++) {
var rect = new Box2;
selectionHandles.push(rect);
}
// add custom initialization here:
// add a large green rectangle
addRect(50,180,50,50, 'green');
}
//wipes the canvas context
function clear(c) {
c.clearRect(0, 0, WIDTH, HEIGHT);
}
// Main draw loop.
// While draw is called as often as the INTERVAL variable demands,
// It only ever does something if the canvas gets invalidated by our code
function mainDraw() {
if (canvasValid == false) {
clear(ctx);
// Add stuff you want drawn in the background all the time here
// draw all boxes
var l = boxes2.length;
for (var i = 0; i < l; i++) {
boxes2[i].draw(ctx); // we used to call drawshape, but now each box draws itself
}
// Add stuff you want drawn on top all the time here
canvasValid = true;
}
}
// Happens when the mouse is moving inside the canvas
function myMove(e) {
if (isDrag) {
getMouse(e);
mySel.x = mx - offsetx;
mySel.y = my - offsety;
// something is changing position so we better invalidate the canvas!
invalidate();
} else if (isResizeDrag) {
// time ro resize!
var oldx = mySel.x;
var oldy = mySel.y;
// 0 1 2
// 3 4
// 5 6 7
switch (expectResize) {
case 0:
mySel.x = mx;
mySel.y = my;
mySel.w += oldx - mx;
mySel.h += oldy - my;
break;
case 1:
mySel.y = my;
mySel.h += oldy - my;
break;
case 2:
mySel.y = my;
mySel.w = mx - oldx;
mySel.h += oldy - my;
break;
case 3:
mySel.x = mx;
mySel.w += oldx - mx;
break;
case 4:
mySel.w = mx - oldx;
break;
case 5:
mySel.x = mx;
mySel.w += oldx - mx;
mySel.h = my - oldy;
break;
case 6:
mySel.h = my - oldy;
break;
case 7:
mySel.w = mx - oldx;
mySel.h = my - oldy;
break;
}
invalidate();
}
getMouse(e);
// if there's a selection see if we grabbed one of the selection handles
if (mySel !== null && !isResizeDrag) {
for (var i = 0; i < 8; i++) {
// 0 1 2
// 3 4
// 5 6 7
var cur = selectionHandles[i];
// we dont need to use the ghost context because
// selection handles will always be rectangles
if (mx >= cur.x && mx <= cur.x + mySelBoxSize &&
my >= cur.y && my <= cur.y + mySelBoxSize) {
// we found one!
expectResize = i;
invalidate();
switch (i) {
case 0:
this.style.cursor = 'nw-resize';
break;
case 1:
this.style.cursor = 'n-resize';
break;
case 2:
this.style.cursor = 'ne-resize';
break;
case 3:
this.style.cursor = 'w-resize';
break;
case 4:
this.style.cursor = 'e-resize';
break;
case 5:
this.style.cursor = 'sw-resize';
break;
case 6:
this.style.cursor = 's-resize';
break;
case 7:
this.style.cursor = 'se-resize';
break;
}
return;
}
}
// not over a selection box, return to normal
isResizeDrag = false;
expectResize = -1;
this.style.cursor = 'auto';
}
}
// Happens when the mouse is clicked in the canvas
function myDown(e) {
if (document.getElementById('btnROI').checked === false) return;
getMouse(e);
//we are over a selection box
if (expectResize !== -1) {
isResizeDrag = true;
return;
}
clear(gctx);
var l = boxes2.length;
for (var i = l - 1; i >= 0; i--) {
// draw shape onto ghost context
boxes2[i].draw(gctx, 'black');
// get image data at the mouse x,y pixel
var imageData = gctx.getImageData(mx, my, 1, 1);
var index = (mx + my * imageData.width) * 4;
// if the mouse pixel exists, select and break
if (imageData.data[3] > 0) {
mySel = boxes2[i];
offsetx = mx - mySel.x;
offsety = my - mySel.y;
mySel.x = mx - offsetx;
mySel.y = my - offsety;
isDrag = true;
invalidate();
clear(gctx);
return;
}
}
// havent returned means we have selected nothing
// mySel = null;
addRect(mx, my, 0, 0, 'rgba(220,205,65,0.7)');
mySel = boxes2[boxes2.length - 1];
// console.log("box", box);
offsetx = mx - mySel.x;
offsety = my - mySel.y;
mySel.x = mx - offsetx;
mySel.y = my - offsety;
isResizeDrag = true;
expectResize = 7;
// getMouse(e);
// startMx = mx;
// startMy = my;
// clear the ghost canvas for next time
// clear(gctx);
// invalidate because we might need the selection border to disappear
// invalidate();
}
function myUp(e) {
isDrag = false;
isResizeDrag = false;
expectResize = -1;
if (isCreationDrag) {
// alert("end creation drag");
getMouse(e);
isCreationDrag = false;
addRect(startMx, startMy, mx - startMx, my - startMy, 'rgba(220,205,65,0.7)');
}
}
// adds a new node
function myDblClick(e) {
getMouse(e);
// for this method width and height determine the starting X and Y, too.
// so I left them as vars in case someone wanted to make them args for something and copy this code
var width = 50;
var height = 50;
addRect(mx - (width / 2), my - (height / 2), width, height, 'green');
}
function invalidate() {
canvasValid = false;
}
// Sets mx,my to the mouse position relative to the canvas
// unfortunately this can be tricky, we have to worry about padding and borders
function getMouse(e) {
var element = canvas, offsetX = 0, offsetY = 0;
if (element.offsetParent) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
offsetX += stylePaddingLeft;
offsetY += stylePaddingTop;
offsetX += styleBorderLeft;
offsetY += styleBorderTop;
mx = e.pageX - offsetX;
my = e.pageY - offsetY
}
</script>
</body>
</html>
Add this line
if(!$("#btnROI").is(":checked") || boxes2.length > 0) return;
at the beginning of the myDown function (line 336 of the fiddle) to allow only one box to be drawn, and only when ROI is checked.
Updated fiddle: http://jsfiddle.net/G6tLn/21
Related
I have a script which I'm using to draw rectangles on a canvas. The user can drag/reposition the drawn rectangles and also add new ones by double-clicking. Now I want to add an image on the background that is, I don't want to use a plain canvas but instead an image on which I can place my rectangles.
What I am trying to build is these rectangles correspond to particular regions of the images. Once this images and rectangles are loaded, if the regions and rectangles are not aligned, the user should be able to reposition them correctly.
Here is my code:
//Box object to hold data for all drawn rects
function Box() {
this.x = 0;
this.y = 0;
this.w = 1; // default width and height?
this.h = 1;
this.fill = '#444444';
}
//Initialize a new Box, add it, and invalidate the canvas
function addRect(x, y, w, h, fill) {
var rect = new Box;
rect.x = x;
rect.y = y;
rect.w = w
rect.h = h;
rect.fill = fill;
boxes.push(rect);
invalidate();
}
// holds all our rectangles
var boxes = [];
var canvas;
var ctx;
var WIDTH;
var HEIGHT;
var INTERVAL = 20; // how often, in milliseconds, we check to see if a redraw is needed
var isDrag = false;
var mx, my; // mouse coordinates
// when set to true, the canvas will redraw everything
// invalidate() just sets this to false right now
// we want to call invalidate() whenever we make a change
var canvasValid = false;
// The node (if any) being selected.
// If in the future we want to select multiple objects, this will get turned into an array
var mySel;
// The selection color and width. Right now we have a red selection with a small width
var mySelColor = '#CC0000';
var mySelWidth = 2;
// we use a fake canvas to draw individual shapes for selection testing
var ghostcanvas;
var gctx; // fake canvas context
// since we can drag from anywhere in a node
// instead of just its x/y corner, we need to save
// the offset of the mouse when we start dragging.
var offsetx, offsety;
// Padding and border style widths for mouse offsets
var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
// initialize our canvas, add a ghost canvas, set draw loop
// then add everything we want to intially exist on the canvas
function init() {
// canvas = fill_canvas();
canvas = document.getElementById('canvas');
HEIGHT = canvas.height;
WIDTH = canvas.width;
ctx = canvas.getContext('2d');
ghostcanvas = document.createElement('canvas');
ghostcanvas.height = HEIGHT;
ghostcanvas.width = WIDTH;
gctx = ghostcanvas.getContext('2d');
//fixes a problem where double clicking causes text to get selected on the canvas
canvas.onselectstart = function () { return false; }
// fixes mouse co-ordinate problems when there's a border or padding
// see getMouse for more detail
if (document.defaultView && document.defaultView.getComputedStyle) {
stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
}
// make draw() fire every INTERVAL milliseconds
setInterval(draw, INTERVAL);
// set our events. Up and down are for dragging,
// double click is for making new boxes
canvas.onmousedown = myDown;
canvas.onmouseup = myUp;
canvas.ondblclick = myDblClick;
// add custom initialization here:
// add an orange rectangle
addRect(200, 200, 200, 200, '#FFC02B');
// add a smaller blue rectangle
addRect(25, 90, 250, 150 , '#2BB8FF');
}
//wipes the canvas context
function clear(c) {
c.clearRect(0, 0, WIDTH, HEIGHT);
}
// While draw is called as often as the INTERVAL variable demands,
// It only ever does something if the canvas gets invalidated by our code
function draw() {
if (canvasValid == false) {
clear(ctx);
// Add stuff you want drawn in the background all the time here
// draw all boxes
var l = boxes.length;
for (var i = 0; i < l; i++) {
drawshape(ctx, boxes[i], boxes[i].fill);
}
// draw selection
// right now this is just a stroke along the edge of the selected box
if (mySel != null) {
ctx.strokeStyle = mySelColor;
ctx.lineWidth = mySelWidth;
ctx.strokeRect(mySel.x,mySel.y,mySel.w,mySel.h);
}
// Add stuff you want drawn on top all the time here
canvasValid = true;
}
}
// Draws a single shape to a single context
// draw() will call this with the normal canvas
// myDown will call this with the ghost canvas
function drawshape(context, shape, fill) {
context.fillStyle = fill;
// We can skip the drawing of elements that have moved off the screen:
if (shape.x > WIDTH || shape.y > HEIGHT) return;
if (shape.x + shape.w < 0 || shape.y + shape.h < 0) return;
context.fillRect(shape.x,shape.y,shape.w,shape.h);
}
// Happens when the mouse is moving inside the canvas
function myMove(e){
if (isDrag){
getMouse(e);
mySel.x = mx - offsetx;
mySel.y = my - offsety;
// something is changing position so we better invalidate the canvas!
invalidate();
}
}
// Happens when the mouse is clicked in the canvas
function myDown(e){
getMouse(e);
clear(gctx);
var l = boxes.length;
for (var i = l-1; i >= 0; i--) {
// draw shape onto ghost context
drawshape(gctx, boxes[i], 'black');
// get image data at the mouse x,y pixel
var imageData = gctx.getImageData(mx, my, 1, 1);
var index = (mx + my * imageData.width) * 4;
// if the mouse pixel exists, select and break
if (imageData.data[3] > 0) {
mySel = boxes[i];
offsetx = mx - mySel.x;
offsety = my - mySel.y;
mySel.x = mx - offsetx;
mySel.y = my - offsety;
isDrag = true;
canvas.onmousemove = myMove;
invalidate();
clear(gctx);
return;
}
}
// havent returned means we have selected nothing
mySel = null;
// clear the ghost canvas for next time
clear(gctx);
// invalidate because we might need the selection border to disappear
invalidate();
}
function myUp(){
isDrag = false;
canvas.onmousemove = null;
}
// adds a new node
function myDblClick(e) {
getMouse(e);
// for this method width and height determine the starting X and Y, too.
// so I left them as vars in case someone wanted to make them args for something and copy this code
var width = 150;
var height = 100;
addRect(mx - (width / 2), my - (height / 2), width, height, '#77DD44');
}
function invalidate() {
canvasValid = false;
}
// Sets mx,my to the mouse position relative to the canvas
// unfortunately this can be tricky, we have to worry about padding and borders
function getMouse(e) {
var element = canvas, offsetX = 0, offsetY = 0;
if (element.offsetParent) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
offsetX += stylePaddingLeft;
offsetY += stylePaddingTop;
offsetX += styleBorderLeft;
offsetY += styleBorderTop;
mx = e.pageX - offsetX;
my = e.pageY - offsetY
}
// If you dont want to use <body onLoad='init()'>
// You could uncomment this init() reference and place the script reference inside the body tag
init();
<!DOCTYPE html>
<html>
<head>
<title>js</title>
</head>
<body>
<canvas id="canvas" width="400" height="300">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</body>
</html>
The snippet below should work if an image is loaded in the html.
Changes in the code are two:
loading the image into a variable
drawing the image onto the canvas using ctx.drawImage(im, 0, 0)
//Box object to hold data for all drawn rects
function Box() {
this.x = 0;
this.y = 0;
this.w = 1; // default width and height?
this.h = 1;
this.fill = '#444444';
}
//Initialize a new Box, add it, and invalidate the canvas
function addRect(x, y, w, h, fill) {
var rect = new Box;
rect.x = x;
rect.y = y;
rect.w = w
rect.h = h;
rect.fill = fill;
boxes.push(rect);
invalidate();
}
//***************************
// This will load the image into the variable "im"
var im = document.getElementById("myImage");
//***************************
// holds all our rectangles
var boxes = [];
var canvas;
var ctx;
var WIDTH;
var HEIGHT;
var INTERVAL = 20; // how often, in milliseconds, we check to see if a redraw is needed
var isDrag = false;
var mx, my; // mouse coordinates
// when set to true, the canvas will redraw everything
// invalidate() just sets this to false right now
// we want to call invalidate() whenever we make a change
var canvasValid = false;
// The node (if any) being selected.
// If in the future we want to select multiple objects, this will get turned into an array
var mySel;
// The selection color and width. Right now we have a red selection with a small width
var mySelColor = '#CC0000';
var mySelWidth = 2;
// we use a fake canvas to draw individual shapes for selection testing
var ghostcanvas;
var gctx; // fake canvas context
// since we can drag from anywhere in a node
// instead of just its x/y corner, we need to save
// the offset of the mouse when we start dragging.
var offsetx, offsety;
// Padding and border style widths for mouse offsets
var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop;
// initialize our canvas, add a ghost canvas, set draw loop
// then add everything we want to intially exist on the canvas
function init() {
// canvas = fill_canvas();
canvas = document.getElementById('canvas');
HEIGHT = canvas.height;
WIDTH = canvas.width;
ctx = canvas.getContext('2d');
ghostcanvas = document.createElement('canvas');
ghostcanvas.height = HEIGHT;
ghostcanvas.width = WIDTH;
gctx = ghostcanvas.getContext('2d');
//fixes a problem where double clicking causes text to get selected on the canvas
canvas.onselectstart = function () { return false; }
// fixes mouse co-ordinate problems when there's a border or padding
// see getMouse for more detail
if (document.defaultView && document.defaultView.getComputedStyle) {
stylePaddingLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingLeft'], 10) || 0;
stylePaddingTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['paddingTop'], 10) || 0;
styleBorderLeft = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderLeftWidth'], 10) || 0;
styleBorderTop = parseInt(document.defaultView.getComputedStyle(canvas, null)['borderTopWidth'], 10) || 0;
}
// make draw() fire every INTERVAL milliseconds
setInterval(draw, INTERVAL);
// set our events. Up and down are for dragging,
// double click is for making new boxes
canvas.onmousedown = myDown;
canvas.onmouseup = myUp;
canvas.ondblclick = myDblClick;
// add custom initialization here:
// add an orange rectangle
addRect(200, 200, 200, 200, '#FFC02B');
// add a smaller blue rectangle
addRect(25, 90, 250, 150 , '#2BB8FF');
}
//wipes the canvas context
function clear(c) {
c.clearRect(0, 0, WIDTH, HEIGHT);
}
// While draw is called as often as the INTERVAL variable demands,
// It only ever does something if the canvas gets invalidated by our code
function draw() {
if (canvasValid == false) {
clear(ctx);
// Add stuff you want drawn in the background all the time here
// draw all boxes
var l = boxes.length;
for (var i = 0; i < l; i++) {
drawshape(ctx, boxes[i], boxes[i].fill);
}
// draw selection
// right now this is just a stroke along the edge of the selected box
if (mySel != null) {
ctx.strokeStyle = mySelColor;
ctx.lineWidth = mySelWidth;
ctx.strokeRect(mySel.x,mySel.y,mySel.w,mySel.h);
}
// Add stuff you want drawn on top all the time here
//You might want to change the size of the image
//In that case use ctx.drawImage(im,0,0,width,height);
//***************************
ctx.drawImage(im,0,0);
//***************************
canvasValid = true;
}
}
// Draws a single shape to a single context
// draw() will call this with the normal canvas
// myDown will call this with the ghost canvas
function drawshape(context, shape, fill) {
context.fillStyle = fill;
// We can skip the drawing of elements that have moved off the screen:
if (shape.x > WIDTH || shape.y > HEIGHT) return;
if (shape.x + shape.w < 0 || shape.y + shape.h < 0) return;
context.fillRect(shape.x,shape.y,shape.w,shape.h);
}
// Happens when the mouse is moving inside the canvas
function myMove(e){
if (isDrag){
getMouse(e);
mySel.x = mx - offsetx;
mySel.y = my - offsety;
// something is changing position so we better invalidate the canvas!
invalidate();
}
}
// Happens when the mouse is clicked in the canvas
function myDown(e){
getMouse(e);
clear(gctx);
var l = boxes.length;
for (var i = l-1; i >= 0; i--) {
// draw shape onto ghost context
drawshape(gctx, boxes[i], 'black');
// get image data at the mouse x,y pixel
var imageData = gctx.getImageData(mx, my, 1, 1);
var index = (mx + my * imageData.width) * 4;
// if the mouse pixel exists, select and break
if (imageData.data[3] > 0) {
mySel = boxes[i];
offsetx = mx - mySel.x;
offsety = my - mySel.y;
mySel.x = mx - offsetx;
mySel.y = my - offsety;
isDrag = true;
canvas.onmousemove = myMove;
invalidate();
clear(gctx);
return;
}
}
// havent returned means we have selected nothing
mySel = null;
// clear the ghost canvas for next time
clear(gctx);
// invalidate because we might need the selection border to disappear
invalidate();
}
function myUp(){
isDrag = false;
canvas.onmousemove = null;
}
// adds a new node
function myDblClick(e) {
getMouse(e);
// for this method width and height determine the starting X and Y, too.
// so I left them as vars in case someone wanted to make them args for something and copy this code
var width = 150;
var height = 100;
addRect(mx - (width / 2), my - (height / 2), width, height, '#77DD44');
}
function invalidate() {
canvasValid = false;
}
// Sets mx,my to the mouse position relative to the canvas
// unfortunately this can be tricky, we have to worry about padding and borders
function getMouse(e) {
var element = canvas, offsetX = 0, offsetY = 0;
if (element.offsetParent) {
do {
offsetX += element.offsetLeft;
offsetY += element.offsetTop;
} while ((element = element.offsetParent));
}
// Add padding and border style widths to offset
offsetX += stylePaddingLeft;
offsetY += stylePaddingTop;
offsetX += styleBorderLeft;
offsetY += styleBorderTop;
mx = e.pageX - offsetX;
my = e.pageY - offsetY
}
// If you dont want to use <body onLoad='init()'>
// You could uncomment this init() reference and place the script reference inside the body tag
init();
<!DOCTYPE html>
<html>
<head>
<title>js</title>
</head>
<body>
<canvas id="canvas" width="400" height="300">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
<!-- This image will be displayed on the canvas -->
<img id="myImage" src="image.jpg" style = "display: none">
</body>
</html>
I'm trying to limit boundaries and I'm running into issues. I'm upscaling an image from another canvas and then implementing zoom and pan. My issue (code below) is with limiting/capping the offsetx/y so that you never see the whitespace; only parts of the image.
Pardon the mess! Any help is appreciated! :P
var zoomIntensity = 0.2;
var canvas = document.getElementById("canvas");
var canvas2 = document.getElementById("canvas2");
var context = canvas.getContext("2d");
var context2 = canvas2.getContext("2d");
var width = 200;
var height = 200;
var scale = 1;
var originx = 0;
var originy = 0;
var offset = {x:0, y:0};
//fill smaller canvas with random pixels
for(var x = 0; x < 100; x++)
for(var y = 0; y < 100; y++)
{
var rando = function(){return Math.floor(Math.random() * 9)};
var val = rando();
context2.fillStyle = "#" + val + val + val;
context2.fillRect(x,y,1,1);
}
//draw the larger canvas
function draw()
{
context.imageSmoothingEnabled = false;
// Clear screen to white.
context.fillStyle = "white";
context.fillRect(originx - offset.x, originy - offset.y, width/scale, height/scale);
context.drawImage(canvas2, 0,0, width, height);
}
// Draw loop at 60FPS.
setInterval(draw, 1000/60);
canvas.onmousewheel = function (event){
event.preventDefault();
// Get mouse offset.
var mousex = event.clientX - canvas.offsetLeft;
var mousey = event.clientY - canvas.offsetTop;
// Normalize wheel to +1 or -1.
var wheel = event.wheelDelta/120;
// Compute zoom factor.
var zoom = Math.exp(wheel*zoomIntensity);
// Translate so the visible origin is at the context's origin.
context.translate(originx - offset.x, originy - offset.y); //offset is panning
//make sure we don't zoom out further than normal scale
var resultingScale = scale * zoom;
if(resultingScale < 1)
zoom = 1/scale;
// Compute the new visible origin. Originally the mouse is at a
// distance mouse/scale from the corner, we want the point under
// the mouse to remain in the same place after the zoom, but this
// is at mouse/new_scale away from the corner. Therefore we need to
// shift the origin (coordinates of the corner) to account for this.
originx -= mousex/(scale*zoom) - mousex/scale;
originy -= mousey/(scale*zoom) - mousey/scale;
// Scale it (centered around the origin due to the trasnslate above).
context.scale(zoom, zoom);
// Offset the visible origin to it's proper position.
context.translate(-originx + offset.x, -originy + offset.y); //offset is panning
// Update scale and others.
scale *= zoom;
}
document.onkeydown = function (evt)
{
var offsetx = 0;
var offsety = 0;
switch(evt.which)
{
case 37: //left
offsetx = 1;
break;
case 38: //up
offsety = 1;
break;
case 39: //right
offsetx = -1
break;
case 40: //down
offsety = -1;
break;
}
offsetx /= scale;
offsety /= scale;
offset.x += offsetx;
offset.y += offsety;
context.translate(offsetx,offsety);
}
<canvas id="canvas" width="200" height="200"></canvas>
<canvas id="canvas2" width="100" height="100"></canvas>
Using transformation matrix to constrain a view
To constrain the position you need to transform the corner coordinates of the image to screen coordinates. As getting the transform is still not standard across browsers the demo below holds a copy of the transform.
The object view holds the canvas view. When you use the function view.setBounds(top,left,right,bottom); the view will be locked to that area (the image you are viewing 0,0,100,100)
The scale and position (origin) will be constrained to keep the bounds outside or one the edge of the canvas context set by view.setContext(context).
The function scaleAt(pos,amount); will scale at a specified pos (eg mouse position)
To set the transform use view.apply() this will update the view transform and set the context transform.
The are a few other functions that may prove handy see code.
Demo uses mouse click drag to pan and wheel to zoom.
Demo is a copy of the OP's example width modifications to answer question.
// use requestAnimationFrame when doing any form of animation via javascript
requestAnimationFrame(draw);
var zoomIntensity = 0.2;
var canvas = document.getElementById("canvas");
var canvas2 = document.getElementById("canvas2");
var context = canvas.getContext("2d");
var context2 = canvas2.getContext("2d");
var width = 200;
var height = 200;
context.font = "24px arial";
context.textAlign = "center";
context.lineJoin = "round"; // to prevent miter spurs on strokeText
//fill smaller canvas with random pixels
for(var x = 0; x < 100; x++){
for(var y = 0; y < 100; y++) {
var rando = function(){return Math.floor(Math.random() * 9)};
var val = rando();
if(x === 0 || y === 0 || x === 99 || y === 99){
context2.fillStyle = "#FF0000";
}else{
context2.fillStyle = "#" + val + val + val;
}
context2.fillRect(x,y,1,1);
}
}
// mouse holds mouse position button state, and if mouse over canvas with overid
var mouse = {
pos : {x : 0, y : 0},
worldPos : {x : 0, y : 0},
posLast : {x : 0, y : 0},
button : false,
overId : "", // id of element mouse is over
dragging : false,
whichWheel : -1, // first wheel event will get the wheel
wheel : 0,
}
// View handles zoom and pan (can also handle rotate but have taken that out as rotate can not be contrained without losing some of the image or seeing some of the background.
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 scale = 1; // current scale
const bounds = {
topLeft : 0,
left : 0,
right : 200,
bottom : 200,
}
var useConstraint = true; // if true then limit pan and zoom to
// keep bounds within the current context
var maxScale = 1;
const workPoint1 = {x :0, y : 0};
const workPoint2 = {x :0, y : 0};
const wp1 = workPoint1; // alias
const wp2 = workPoint2; // alias
var ctx;
const pos = { // current position of origin
x : 0,
y : 0,
}
var dirty = true;
const API = {
canvasDefault () { ctx.setTransform(1,0,0,1,0,0) },
apply(){
if(dirty){ this.update() }
ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
},
getScale () { return scale },
getMaxScale () { return maxScale },
matrix, // expose the matrix
invMatrix, // expose the inverse matrix
update(){ // call to update transforms
dirty = false;
m[3] = m[0] = scale;
m[1] = m[2] = 0;
m[4] = pos.x;
m[5] = pos.y;
if(useConstraint){
this.constrain();
}
this.invScale = 1 / scale;
// calculate the inverse transformation
var 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;
},
constrain(){
maxScale = Math.min(
ctx.canvas.width / (bounds.right - bounds.left) ,
ctx.canvas.height / (bounds.bottom - bounds.top)
);
if (scale < maxScale) { m[0] = m[3] = scale = maxScale }
wp1.x = bounds.left;
wp1.y = bounds.top;
this.toScreen(wp1,wp2);
if (wp2.x > 0) { m[4] = pos.x -= wp2.x }
if (wp2.y > 0) { m[5] = pos.y -= wp2.y }
wp1.x = bounds.right;
wp1.y = bounds.bottom;
this.toScreen(wp1,wp2);
if (wp2.x < ctx.canvas.width) { m[4] = (pos.x -= wp2.x - ctx.canvas.width) }
if (wp2.y < ctx.canvas.height) { m[5] = (pos.y -= wp2.y - ctx.canvas.height) }
},
toWorld(from,point = {}){ // convert screen to world coords
var xx, yy;
if(dirty){ this.update() }
xx = from.x - m[4];
yy = from.y - m[5];
point.x = xx * im[0] + yy * im[2];
point.y = xx * im[1] + yy * im[3];
return point;
},
toScreen(from,point = {}){ // convert world coords to screen coords
if(dirty){ this.update() }
point.x = from.x * m[0] + from.y * m[2] + m[4];
point.y = from.x * m[1] + from.y * m[3] + m[5];
return point;
},
scaleAt(at, amount){ // at in screen coords
if(dirty){ this.update() }
scale *= amount;
pos.x = at.x - (at.x - pos.x) * amount;
pos.y = at.y - (at.y - pos.y) * amount;
dirty = true;
},
move(x,y){ // move is in screen coords
pos.x += x;
pos.y += y;
dirty = true;
},
setContext(context){
ctx = context;
dirty = true;
},
setBounds(top,left,right,bottom){
bounds.top = top;
bounds.left = left;
bounds.right = right;
bounds.bottom = bottom;
useConstraint = true;
dirty = true;
}
};
return API;
})();
view.setBounds(0,0,canvas2.width,canvas2.height);
view.setContext(context);
//draw the larger canvas
function draw(){
view.canvasDefault(); // se default transform to clear screen
context.imageSmoothingEnabled = false;
context.fillStyle = "white";
context.fillRect(0, 0, width, height);
view.apply(); // set the current view
context.drawImage(canvas2, 0,0);
view.canvasDefault();
if(view.getScale() === view.getMaxScale()){
context.fillStyle = "black";
context.strokeStyle = "white";
context.lineWidth = 2.5;
context.strokeText("Max scale.",context.canvas.width / 2,24);
context.fillText("Max scale.",context.canvas.width / 2,24);
}
requestAnimationFrame(draw);
if(mouse.overId === "canvas"){
canvas.style.cursor = mouse.button ? "none" : "move";
}else{
canvas.style.cursor = "default";
}
}
// add events to document so that mouse is captured when down on canvas
// This allows the mouseup event to be heard no matter where the mouse has
// moved to.
"mousemove,mousedown,mouseup,mousewheel,wheel,DOMMouseScroll".split(",")
.forEach(eventName=>document.addEventListener(eventName,mouseEvent));
function mouseEvent (event){
mouse.overId = event.target.id;
if(event.target.id === "canvas" || mouse.dragging){ // only interested in canvas mouse events including drag event started on the canvas.
mouse.posLast.x = mouse.pos.x;
mouse.posLast.y = mouse.pos.y;
mouse.pos.x = event.clientX - canvas.offsetLeft;
mouse.pos.y = event.clientY - canvas.offsetTop;
view.toWorld(mouse.pos, mouse.worldPos); // gets the world coords (where on canvas 2 the mouse is)
if (event.type === "mousemove"){
if(mouse.button){
view.move(
mouse.pos.x - mouse.posLast.x,
mouse.pos.y - mouse.posLast.y
)
}
} else if (event.type === "mousedown") { mouse.button = true; mouse.dragging = true }
else if (event.type === "mouseup") { mouse.button = false; mouse.dragging = false }
else if(event.type === "mousewheel" && (mouse.whichWheel === 1 || mouse.whichWheel === -1)){
mouse.whichWheel = 1;
mouse.wheel = event.wheelDelta;
}else if(event.type === "wheel" && (mouse.whichWheel === 2 || mouse.whichWheel === -1)){
mouse.whichWheel = 2;
mouse.wheel = -event.deltaY;
}else if(event.type === "DOMMouseScroll" && (mouse.whichWheel === 3 || mouse.whichWheel === -1)){
mouse.whichWheel = 3;
mouse.wheel = -event.detail;
}
if(mouse.wheel !== 0){
event.preventDefault();
view.scaleAt(mouse.pos, Math.exp((mouse.wheel / 120) *zoomIntensity));
mouse.wheel = 0;
}
}
}
div { user-select: none;} /* mouse prevent drag selecting content */
canvas { border:2px solid black;}
<div>
<canvas id="canvas" width="200" height="200"></canvas>
<canvas id="canvas2" width="100" height="100"></canvas>
<p>Mouse wheel to zoom. Mouse click drag to pan.</p>
<p>Zoomed image constrained to canvas</p>
</div>
I am trying to implement a drag and drop on a canvas representing 3 disks.
I would like to change with mouse the position of each mass. My main problem is that I am constrained by the length of axe for each of these 3 spheres.
For the moment, I have implemented the following function when mouse is moving inside the canvas (value of indexMass indicates which mass is moved: 1, 2 or 3 and t1, t2, t3 represents respectively the angle of mass 1, 2, 3):
// Happens when the mouse is moving inside the canvas
function myMove(event) {
if (isDrag) {
var x = event.offsetX;
var y = event.offsetY;
if (indexMass == 1)
{ // Update theta1 value
t1 = t1 + 0.1*Math.atan(y/x);
}
else if (indexMass == 2)
{ // Update theta2 value
t2 = t2 + 0.1*Math.atan(y/x);
}
else if (indexMass == 3)
{ // Update theta3 value
t3 = t3 + 0.1*Math.atan(y/x);
}
// Update drawing
DrawPend(canvas);
}
}
As you can see, I did for each angle:
t = t + 0.1*Math.atan(y/x);
with:
var x = event.offsetX;
var y = event.offsetY;
But this effect is not very nice. Once the sphere is selected with mouse (on mouse click), I would like the cursor to be stuck with this sphere or the sphere to follow the "delta" of the mouse coordinates when I am not on sphere any more.
Update 1
#Blindman67: thanks for your help, your code snippet is pretty complex for me, I didn't understand it all. But I am on the right way.
I am starting by the first issue: make rotate the selected disk with mouse staying very closed to it or over it, when dragging.
For the moment, I have modified my function myMove (which is called when I have clicked down and move the mouse for dragging) like:
// Happens when the mouse is moving inside the canvas
function myMove(event) {
// If dragging
if (isDrag) {
// Compute dx and dy before calling DrawPend
var lastX = parseInt(event.offsetX - mx);
var lastY = parseInt(event.offsetY - my);
var dx = lastX - window['x'+indexMass];
var dy = lastY - window['y'+indexMass];
// Change angle when dragging
window['t'+indexMass] = Math.atan2(dy, dx);
// Update drawing
DrawPend(canvas);
// Highlight dragging disk
fillDisk(indexMass, 'pink');
}
}
where indexMass is the index of dragged disk and window['x'+indexMass] , window['y'+indexMass] are the current coordinates of the selected disk center.
After, I compute the dx, dy respectively from coordinates mouse clicked when starting drag (mx, my returned by getMousePos function) and mouse coordinates with moving.
Finally, I change the angle of disk by set, for global variable (theta of selected disk), i.e window['t'+indexMass]:
// Change angle when dragging
window['t'+indexMass] = Math.atan2(dy, dx);
I have took your part of code with Math.atan2.
But the result of this function doesn't make a good animation with mouse dragging, I would like to know where this could come from.
Right now, I would like to implement only the dragging without modifying the length of axis, I will see more later for this functionality.
Update 2
I keep going on to find a solution about the dragging of a selected mass with mouse.
For trying a synthesis of what I have done previously, I believe the following method is good but this dragging method is not working very well: the selected disk doesn't follow correctly the mouse and I don't know why.
In myMove function (function called when I start dragging), I decided to:
Compute the dx, dy between the mouse coordinates and the selected disk coordinates, i.e:
var dx = parseInt(event.offsetX - window['x'+indexMass]);
var dy = parseInt(event.offsetY - window['y'+indexMass]);
indexMass represents the index of the selected disk.
Increment the position of selected disk (stored in temporary variables tmpX, tmpY) by dx, dy.
Compute the new angle theta (identified in code by global variable window['t'+indexMass]
Compute the new positions of selected disk with this new value of theta, i.e for example with disk1 (indexMass=1 and theta = t1):
x1= x0 +l1 * sin(t1)
y1= y0 +l1 * sin(t1)
I want to draw readers' attention to the fact that I want dragging with mouse not to modify the lengths of axes with mouse, this is a constraint.
Here's the entire myMove function (called when drag is starting) :
// Happens when the mouse is moving inside the canvas
function myMove(event) {
// If dragging
if (isDrag) {
console.log('offsetX', event.offsetX);
console.log('offsetY', event.offsetY);
var dx = parseInt(event.offsetX - window['x'+indexMass]);
var dy = parseInt(event.offsetY - window['y'+indexMass]);
console.log('dx', dx);
console.log('dy', dy);
// Temp variables
var tmpX = window['x'+indexMass];
var tmpY = window['y'+indexMass];
// Increment temp positions
tmpX += dx;
tmpY += dy;
// Compute new angle for indexMass
window['t'+indexMass] = Math.atan2(tmpX, tmpY);
console.log('printf', window['t'+indexMass]);
// Compute new positions of disks
dragComputePositions();
// Update drawing
DrawPend(canvas);
// Highlight dragging disk
fillDisk(indexMass, 'pink');
}
}
You can not move the OS mouse position. You can hide the mouse canvas.style.cursor = "none"; and then draw a mouse on the canvas your self but it will lag behind by one frame because when you get the mouse coordinates the OS has already placed the mouse at that position, and if you use requestAnimationFrame (RAF) the next presentation of the canvas will be at the next display refresh interval. If you don't use RAF you may or may not present the canvas on the current display refresh, but you will get occasional flicker and shearing.
To solve the problem (which is subjective) draw a line from the rotation point through the ball to the mouse position this will at least give the user some feedback as to what is happening.
I would also add some handles to the balls so you could change the mass (volume of sphere * density) and the length of axis.. The resize cursors are a problem as the will not match the direction of required movement when the angles have changes. You would need to find one closest to the correct angle or render a cursor to a canvas and use that.
Example code shows what I mean. (does not include sim) Move mouse over balls to move, when over you will also see two circles appear to change distance and radius (mass)
/*-------------------------------------------------------------------------------------
answer code
---------------------------------------------------------------------------------------*/
var balls = [];
var startX,startY;
var mouseOverBallIndex = -1;
var mouseOverDist = false;
var mouseOverMass = false;
const DRAG_CURSOR = "move";
const MASS_CURSOR = "ew-resize";
const DIST_CURSOR = "ns-resize";
var dragging = false;
var dragStartX = 0;
var dragStartY = 0;
function addBall(dist,radius){
balls.push({
dist : dist,
radius : Math.max(10,radius),
angle : -Math.PI / 2,
x : 0,
y : 0,
mass : (4/3) * radius * radius * radius * Math.PI,
});
}
function drawBalls(){
var i = 0;
var len = balls.length;
var x,y,dist,b,minDist,index,cursor;
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
ctx.fillStyle = "blue"
ctx.beginPath();
x = startX;
y = startY;
ctx.moveTo(x, y)
for(; i < len; i += 1){
b = balls[i];
x += Math.cos(b.angle) * b.dist;
y += Math.sin(b.angle) * b.dist;
ctx.lineTo(x, y);
b.x = x;
b.y = y;
}
ctx.stroke();
minDist = Infinity;
index = -1;
for(i = 0; i < len; i += 1){
b = balls[i];
ctx.beginPath();
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
ctx.fill();
if(!dragging){
x = b.x - mouse.x;
y = b.y - mouse.y;
dist = Math.sqrt(x * x + y * y);
if(dist < b.radius + 5 && dist < minDist){
minDist = dist;
index = i;
}
}
}
if(!dragging){
mouseOverBallIndex = index;
if(index !== -1){
cursor = DRAG_CURSOR;
b = balls[index];
ctx.fillStyle = "Red"
ctx.beginPath();
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
ctx.fill();
dx = b.x - Math.cos(b.angle) * b.radius;
dy = b.y - Math.sin(b.angle) * b.radius;
x = dx - mouse.x;
y = dy - mouse.y;
dist = Math.sqrt(x * x + y * y);
ctx.beginPath();
if(dist < 6){
ctx.strokeStyle = "Yellow"
mouseOverDist = true;
ctx.arc(dx, dy, 12, 0, Math.PI * 2);
cursor = DIST_CURSOR;
}else{
ctx.strokeStyle = "black"
mouseOverDist = false;
ctx.arc(dx, dy, 5, 0, Math.PI * 2);
}
ctx.stroke();MASS_CURSOR
dx = b.x - Math.cos(b.angle + Math.PI/2) * b.radius;
dy = b.y - Math.sin(b.angle + Math.PI/2) * b.radius;
x = dx - mouse.x;
y = dy - mouse.y;
dist = Math.sqrt(x * x + y * y);
ctx.beginPath();
if(dist < 6){
ctx.strokeStyle = "Yellow"
mouseOverMass = true;
ctx.arc(dx, dy, 12, 0, Math.PI * 2);
cursor = MASS_CURSOR;
}else{
ctx.strokeStyle = "black"
mouseOverMass = false;
ctx.arc(dx, dy, 5, 0, Math.PI * 2);
}
ctx.stroke();
canvas.style.cursor = cursor;
}else{
canvas.style.cursor = "default";
}
}else{
b = balls[mouseOverBallIndex];
ctx.fillStyle = "Yellow"
ctx.beginPath();
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
ctx.fill();
}
}
function display(){ // put code in here
var x,y,b
if(balls.length === 0){
startX = canvas.width/2;
startY = canvas.height/2;
addBall((startY * 0.8) * (1/4), startY * 0.04);
addBall((startY * 0.8) * (1/3), startY * 0.04);
addBall((startY * 0.8) * (1/2), startY * 0.04);
}
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
if((mouse.buttonRaw & 1) && mouseOverBallIndex > -1){
b = balls[mouseOverBallIndex];
if(dragging === false){
dragging = true;
dragStartX = balls[mouseOverBallIndex].x;
dragStartY = balls[mouseOverBallIndex].y;
}else{
b = balls[mouseOverBallIndex];
if(mouseOverBallIndex === 0){
x = startX;
y = startY;
}else{
x = balls[mouseOverBallIndex-1].x
y = balls[mouseOverBallIndex-1].y
}
if(mouseOverDist){
var dist = Math.sqrt(Math.pow(x-mouse.x,2)+Math.pow(y-mouse.y,2));
b.dist = dist + b.radius;
}else
if(mouseOverMass){
var dist = Math.sqrt(Math.pow(dragStartX-mouse.x,2)+Math.pow(dragStartY-mouse.y,2));
b.radius = Math.max(10,dist);
b.mass = dist * dist * dist * (4/3) * Math.PI;
}else{
b.angle = Math.atan2(mouse.y - y, mouse.x - x);
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "grey";
ctx.moveTo(x,y);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
}
}
}else if(dragging){
dragging = false;
}
drawBalls();
}
/*-------------------------------------------------------------------------------------
answer code END
---------------------------------------------------------------------------------------*/
/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars
var canvas, ctx, mouse;
var globalTime = 0;
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
c.id = CANVAS_ELEMENT_ID;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; balls.length = 0; }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === U) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
}
}
return mouse;
})();
var done = function(){
window.removeEventListener("resize",resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = U;
L("All done!")
}
resizeCanvas(); // create and size canvas
mouse.start(canvas,true); // start mouse on canvas and block context menu
window.addEventListener("resize",resizeCanvas); // add resize event
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
// continue until mouse right down
if (!(mouse.buttonRaw & 2)) { requestAnimationFrame(update); } else { done(); }
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/
(Posted a solution from the question author to move it to the answer space).
Problem solved! I forgot to take into account the position of "indexMass-1" disk to compute the new angle with Math.atan2 function.
I am creating an interactive game and am very new to programming. I was wondering how exactly to increment a variable and then put it into an alert box. I want to find the calculation of foodCaught and foodWasted and display it in an alert box at the end of my game. Also how do I make it so it is on a timer and at the end of this timer the alert box with foodCaught and foodWasted appears?
$(document).ready(function(){
var cnv = $("#myCanvas")[0];
var ctx = cnv.getContext("2d");
var catcherX = ctx.canvas.width/2;
var catcherY = ctx.canvas.height - 100; // set the initial location of the catcher's y position
var numFoods = 5;
var catcherSpeed = 30;
var foodCaught = 0;
var foodWasted = 0;
function Food(){ // the name of the constructor usually begins with a captial letter
this.radius = 30;
this.x = Math.floor(Math.random()*ctx.canvas.width);
this.y = 0 - this.radius;
this.speed = 1+ Math.floor(Math.random()*5);
var imageToUse = new Image();
this.width = 50; // default values
this.height = 50; // default values
var randomNum = Math.floor(Math.random()*2); // create a random number to choose the image
if(randomNum == 0){
imageToUse.src = "corn.png";
this.width = 27; // width of corn.png
this.height = 100; // height of corn.png
} else if(randomNum == 1){
imageToUse.src = "straw.png"
this.width = 83; // width of straw.png
this.height = 100; // height of straw.png
}
this.moveFood = function(){
if(this.y > ctx.canvas.height){
this.x = Math.floor(Math.random()*ctx.canvas.width);
this.y = 0;
footWasted += 1;
}
this.y += this.speed; // add speed to location
}
this.drawFood = function() {
ctx.drawImage(imageToUse, this.x, this.y);
}
this.intersectFood = function(targetX, targetY, targetR) {
if(this.x + this.width > targetX && this.x < targetX + targetR && this.y + this.height > targetY && this.y < targetY + targetR){
foodCaught += 1;
return true;
}
/*
var distanceX = this.x - targetX;
var distanceY = this.y - targetY;
var distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
if(distance < targetR + this.radius){
return true;
}
*/
}
}
// create an Array of Foods
var FoodArray = new Array();
for(var i=0; i<numFoods; i++) {
FoodArray[i] = new Food();
}
// get mouse Postion
$(document).keydown(function(e){ // attach the event to the entire document
switch(e.keyCode){
case 37: // left
catcherX-= catcherSpeed;
break;
case 39: // right
catcherX+= catcherSpeed;
break;
}
});
var interval = setInterval(gameLoop,10); // call draw every 10 milliseconds
var counter = 0;
function gameLoop(){
ctx.clearRect(0,0, ctx.canvas.width, ctx.canvas.height); //clears previous circles
for(var i=0; i < FoodArray.length; i++) {
FoodArray[i].moveFood();
FoodArray[i].drawFood();
if(FoodArray[i].intersectFood(catcherX, catcherY, 15)){
FoodArray.splice(i,1);
console.log(i);
}
}
// draw catcher
ctx.beginPath();
ctx.fillStyle="#119933";
ctx.arc(catcherX,catcherY,15,0,Math.PI*2,true);
ctx.closePath();
ctx.fill();
Updated Answer
After your game has ended, do:
alert("foodCaught = " + foodCaught + "<br>foodWasted = " + foodWasted);
Just make sure that this alert is withing the scope of foodCaught and foodWasted variables.
EDIT:
Your game is actually never ending. The setInterval that you keeps calling gameLoop() every 10 milliseconds, ENDLESSLY!
To keep a track of foodCaught and foodWasted variables, add a console.log() statement at the end of gameLoop() function as shown in the code below:
function gameLoop(){
ctx.clearRect(0,0, ctx.canvas.width, ctx.canvas.height); //clears previous circles
for(var i=0; i < FoodArray.length; i++) {
FoodArray[i].moveFood();
FoodArray[i].drawFood();
if(FoodArray[i].intersectFood(catcherX, catcherY, 15)){
FoodArray.splice(i,1);
//console.log(i);
}
}
// draw catcher
ctx.beginPath();
ctx.fillStyle="#119933";
ctx.arc(catcherX,catcherY,15,0,Math.PI*2,true);
ctx.closePath();
ctx.fill();
console.log("foodCaught = " + foodCaught + "\nfoodWasted = " + foodWasted);
}
With this, you will get the values of foodCaught and foodWasted variables everytime the gameLoop function is called.
If you are unaware of how to use the console, checkout this post.
EDIT2:
I have an idea about ending this game. In the updated code below, I have set the game to run for 10 seconds (just as an example so you can change it quickly) using setTimeout(). You have change it to any duration that you want. In setTimeout(), I am also alerting the values that you need.
Here is the updated Javascript/jQuery code:
$(document).ready(function(){
var cnv = $("#myCanvas")[0];
var ctx = cnv.getContext("2d");
var catcherX = ctx.canvas.width/2;
var catcherY = ctx.canvas.height - 100; // set the initial location of the catcher's y position
var numFoods = 5;
var catcherSpeed = 30;
var foodCaught = 0;
var foodWasted = 0;
function Food(){ // the name of the constructor usually begins with a captial letter
this.radius = 30;
this.x = Math.floor(Math.random()*ctx.canvas.width);
this.y = 0 - this.radius;
this.speed = 1+ Math.floor(Math.random()*5);
var imageToUse = new Image();
this.width = 50; // default values
this.height = 50; // default values
var randomNum = Math.floor(Math.random()*2); // create a random number to choose the image
if(randomNum == 0){
imageToUse.src = "corn.png";
this.width = 27; // width of corn.png
this.height = 100; // height of corn.png
} else if(randomNum == 1){
imageToUse.src = "straw.png"
this.width = 83; // width of straw.png
this.height = 100; // height of straw.png
}
this.moveFood = function(){
if(this.y > ctx.canvas.height){
this.x = Math.floor(Math.random()*ctx.canvas.width);
this.y = 0;
foodWasted += 1;
}
this.y += this.speed; // add speed to location
}
this.drawFood = function() {
ctx.drawImage(imageToUse, this.x, this.y);
}
this.intersectFood = function(targetX, targetY, targetR) {
if(this.x + this.width > targetX && this.x < targetX + targetR && this.y + this.height > targetY && this.y < targetY + targetR){
foodCaught += 1;
return true;
}
/*
var distanceX = this.x - targetX;
var distanceY = this.y - targetY;
var distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
if(distance < targetR + this.radius){
return true;
}
*/
}
}
function gameLoop(){
ctx.clearRect(0,0, ctx.canvas.width, ctx.canvas.height); //clears previous circles
for(var i=0; i < FoodArray.length; i++) {
FoodArray[i].moveFood();
FoodArray[i].drawFood();
if(FoodArray[i].intersectFood(catcherX, catcherY, 15)){
FoodArray.splice(i,1);
}
}
// draw catcher
ctx.beginPath();
ctx.fillStyle="#119933";
ctx.arc(catcherX,catcherY,15,0,Math.PI*2,true);
ctx.closePath();
ctx.fill();
}
$(document).keydown(function(e){ // attach the event to the entire document
switch(e.keyCode){
case 37: // left
catcherX-= catcherSpeed;
break;
case 39: // right
catcherX+= catcherSpeed;
break;
}
});
// create an Array of Foods
var FoodArray = new Array();
for(var i=0; i<numFoods; i++) {
FoodArray[i] = new Food();
}
var interval = setInterval(gameLoop,10); // call draw every 10 milliseconds
setTimeout(function(){
clearInterval(interval);
alert("Time UP!\n\nfoodCaught = " + foodCaught + "\nfoodWasted = " + foodWasted);
}, 10000);
});
I m Drawing the image on canvas with this code
and it successfully draw the image on canvas now i want to move the image on canvas for that i write the code i check that if the right key of my keyboard is pressed i will increment the x coordinate of an image if left key is pressed i will decrement the x coordinate but image is not moving on the canvas
player = new Image();
player.src = "game_character.png";
context.drawImage(player,player.x * wallDim + wallDim ,player.y * wallDim + wallDim ,50,50);
how to move an image on canvas
var handleInput = function(event, keyState) {
switch(event.which) {
case 37: { // Left Arrow
keyDown.arrowLeft = keyState;
break;
}
case 38: { // Up Arrow
keyDown.arrowUp = keyState;
break;
}
case 39: { // Right Arrow
keyDown.arrowRight = keyState;
break;
}
case 40: { // Down Arrow
keyDown.arrowDown = keyState;
break;
}
}
}
/**
* physics
*
* This function contains the basic logic for the maze.
*/
var physics = function() {
console.log("physics ");
console.log("first condition "+keyDown.arrowRight +player.x+1);
if(keyDown.arrowLeft && player.x-1 >= 0 && map[player.y][player.x-1] != 1) {
player.x--;
redraw = true;
}
if(keyDown.arrowUp && player.y-1 >= 0 && map[player.y-1][player.x] != 1) {
player.y--;
redraw = true;
}
if(keyDown.arrowRight && player.x+1 < map[0].length && map[player.y][player.x+1] != 1) {
console.log("arrow right");
player.x++;
redraw = true;
}
if(keyDown.arrowDown && player.y+1 < map.length && map[player.y+1][player.x] != 1) {
player.y++;
redraw = true;
}
if(keyDown.arrowRight && player.x+1 >= map[0].length)
{
player.x++;
document.getElementById("canvas_div").style.display="none";
document.getElementById("end_screen_div").style.display="block";
//alert("completed");
}
}
/**
* draw
*
* This function simply draws the current state of the game.
*/
var draw = function() {
// Don't redraw if nothing has changed
if(!redraw)
return;
context.clearRect(0, 0, cols, rows);
context.beginPath();
// Draw the maze
for(var a = 0; a < rows; a++) {
for(var b = 0; b < cols; b++) {
switch(map[a][b]) {
case C.EMPTY: context.fillStyle = colors.empty; break;
case C.WALL: context.fillStyle = colors.wall; break;
}
context.fillRect(b * wallDim, a * wallDim, wallDim, wallDim); // x, y, width, height
}
}
// Draw the player
/* context.fillStyle = colors.player;
context.arc(
player.x * wallDim + wallDim / 2, // x position
player.y * wallDim + wallDim / 2, // y position
wallDim / 2, // Radius
0, // Starting angle
Math.PI * 2, // Ending angle
true // antiClockwise
);*/
player = new Image();
player.src = "game_character.png";
context.drawImage(player,player.x * wallDim + wallDim ,player.y * wallDim + wallDim ,50,50);
var firstplayer=new Image();
firstplayer.src="top_character01.png";
context.drawImage(firstplayer,680,0,60,60);
var secondplayer= new Image();
secondplayer.src="top_character02.png";
context.drawImage(secondplayer,750,0,60,60);
context.fill();
context.closePath();
redraw = false;
}
In your draw method, you reinitialize the player each time :
player = new Image();
player.src = "game_character.png";
So you erase the player.x modified by your event handler.
You should initialize the player only once, outside the draw function. You can move the initialization like this :
var player = new Image();
player.src = "game_character.png";
var draw = function() {
There is absolutely no need to call player.src = "game_character.png"; inside the draw function.
As a general rule, when dealing with animation, try to remove all what you can from the draw function, which should be as fast as possible.
You will need to redraw the canvas each time. Something like this:
function init()
{
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
x = canvas.width / 2; //align to centre of the screen
y = canvas.height / 2; //same as above
speed = 5; //speed for the player to move at
width = 50; //width of the player
height = 50; //height of the player
playerimage = new Image();
playerimage.src = "path/to/image/for/player"; //path to the image to use for the player
canvas.addEventListener("keypress", update);
}
function update(event)
{
if (event.keyCode == 38)
{
y -= speed; //going up
}
if (event.keyCode == 40)
{
y += speed; //going down
}
if (event.keyCode == 37)
{
x -= speed; //going left
}
if (event.keyCode == 39)
{
x += speed; //going right
}
render();
}
function render()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(playerimage, x, y, width, height);
}
I haven't tested it, so I don't know whether it works and there may be some mistakes here and there. It should work though! If nothing else, it will (hopefully) give you an idea of one way in which you can go about doing it...