I want to keydown trigger the movement of a block and then with another keydown, add another moving block. And if I keydown again, then another moving block appears. As it is now, the keydown stops the movement of the first block. I would like to have all the blocks moving simultaneously. Here's the code. It's also available on jsfiddle http://jsfiddle.net/7eUEE/
Thank you!
var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
window.addEventListener("keydown", addnew, true);
window.addEventListener("keydown", move, true);
var list = [];
var id = 0;
var color = 'blue';
var x = 0;
var y = 0;
window.onkeydown = function(s){
s.preventDefault();
if(!s){
s = window.event;
}
var keycode;
if(s.which){
keycode = s.which;
}else{
keycode = s.keyCode;
}
}
function box(id, color, x ,y){
box.id = id;
box.color = color;
box.x = x;
box.y = y;
}
function addnew(e){
e.preventDefault();
if(!e){
e = window.event;
}
//space
if(e.keyCode == 32){
list[id] = new box(id, color, x, y);
id++;
y = y + 100;
box.y = y;
box.color = 'red';
console.log(box.y);
}
}
list[0] = box(id, color, x ,y);
function move(e){
e.preventDefault();
if(!e){
e = window.event;
}
//right
if(e.keyCode == 39){
list[id];
setInterval(loopdraw);
}
}
var loopdraw = function draw(){
box.x = box.x + 1;
console.log(box.x);
ctx.beginPath();
ctx.fillStyle = box.color;
ctx.fillRect(box.x, box.y, 50, 50);
ctx.closePath();
}
Your Box objects don't behave like objects, because you use them in 3 ways:
class: list[id] = new box(id, color, x, y);
function: list[0] = box(id, color, x ,y);
object: box.y = y;, box.color = 'red'; etc
The list part is right: keep a collection of (moving) boxes. The loop part isn't, because you don't use the list. You only 'loop' 1 box.
I've updated the fiddle: http://jsfiddle.net/rudiedirkx/7eUEE/1/ and I've renamed the box class to Box, because there might be Box objects named box.
The important parts:
// Create initial box
list[id++] = new Box(id, color, x ,y);
// Create more boxes
list[id++] = new Box(id, 'red', x, y);
// Loop through existing boxes animation
function loopdraw() {
list.forEach(function(box) {
// Draw
Related
i want to draw lines with convas but my previous lines was deleted when i try create a new line. I wark with convas the first time and i will be happy if you can say me about my mistakes and solutions of the problem
const convas = document.querySelector(".v");
const ctx = convas.getContext("2d");
let startPositionLine = { x: 0, y: 0 };
let endPositionLine = { x: 0, y: 0 };
let { xStart, yStart } = startPositionLine;
let { xEnd, yEnd } = endPositionLine;
function moveMouseEvent(e) {
xEnd = e.offsetX;
yEnd = e.offsetY;
ctx.beginPath();
ctx.clearRect(0, 0, convas.width, convas.height);
ctx.moveTo(xStart, yStart);
ctx.lineTo(xEnd, yEnd);
ctx.stroke();
}
convas.onmousedown = (e) => {
ctx.beginPath();
xStart = e.offsetX;
yStart = e.offsetY;
ctx.stroke();
convas.onmousemove = (e) => moveMouseEvent(e);
};
convas.onmouseup = () => {
convas.onmousemove = null;
};
I mentioned this in my comment to your post above, but thought I would just give an example you can run and test here. So you are correct in what you are doing, but you just need to save each drawn line to an array AS YOU DRAW THEM. You will see the code for this in the mousedown event handler here. Then you need to redraw those saved lines each time after you clear the canvas in mousemove event handler.
const canvas = document.getElementById('canvas');
var canvasrect = canvas.getBoundingClientRect();
var Xstart = 0;
var Ystart = 0;
var Xend = 0
var Yend = 0
var isDrawing = false;
var linesArray = [{}];
// MOUSE CLICK / BUTTON DOWN LISTENER
canvas.addEventListener('mousedown', e => {
e.preventDefault();
e.stopPropagation();
canvasrect = canvas.getBoundingClientRect();
Xstart = e.clientX - canvasrect.left;
Ystart = e.clientY - canvasrect.top;
// We need to know if the user is actuallydrawing or not.
// Each time the user clicks their canvas we toggle whether to start or stop drawing.
if (isDrawing) {
// If this is the end of a line, save the end coordinates to the latest array element.
linesArray[linesArray.length - 1].xe = Xend;
linesArray[linesArray.length - 1].ye = Yend;
isDrawing = false;
} else {
// If this is the start of a new line, save the start coordinates in a new array element along with end coordinate placeholders.
linesArray.push({
xs: Xstart,
ys: Ystart,
xe: 0,
ye: 0
})
isDrawing = true;
}
});
// MOUSE MOVE
canvas.addEventListener('mousemove', e => {
e.preventDefault();
e.stopPropagation();
// get the current mouse x & y locations along with any scrolling and window resizing offsets.
Xend = e.clientX - canvasrect.left;
Yend = e.clientY - canvasrect.top;
if (isDrawing === true) {
var ctx = canvas.getContext('2d');
//clear the canvas
ctx.clearRect(0, 0, canvasrect.width, canvasrect.height);
// Draw a line from the initial click coordinates to the current mouse pointer coordinates.
ctx.strokeStyle = '#fe0101';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(Xstart, Ystart);
ctx.lineTo(Xend, Yend);
ctx.stroke();
// now redraw the previous lines saved in the linesArray
if (linesArray.length >= 1) {
for (let i = 0; i < linesArray.length; i++) {
if (linesArray[i].xe != 0) {
ctx.strokeStyle = '#fe0101';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(linesArray[i].xs, linesArray[i].ys);
ctx.lineTo(linesArray[i].xe, linesArray[i].ye);
ctx.stroke();
}
}
}
}
});
<canvas id="canvas" width="500" height="500" style="background-color:lightGray"></canvas>
var canvas = document.getElementById('mainCanvas');
var ctx = canvas.getContext('2d');
var status = document.getElementById('status');
var click = document.getElementById('click');
var hover = document.getElementById('hover');
canvas.style.backgroundColor = 'red';
var mouseX;
var mouseY;
var item1 = new item('item1', ctx);
item1.drawItem(50, 50);
var item2 = new item('item2', ctx);
item2.drawItem(150, 200);
var item3 = new item('item3', ctx);
item3.drawItem(200, 250);
var items = [item1, item2, item3];
canvas.addEventListener('mousemove', mouseMoveHandler, false);
canvas.addEventListener('click', mouseClickHandler, false);
function mouseMoveHandler(event) {
mouseX = event.clientX - canvas.getBoundingClientRect().left;
mouseY = event.clientY - canvas.getBoundingClientRect().top;
for (var i = 0; i < items.length; i++) {
if (mouseX > items[i].x & mouseY > items[i].y &
mouseX < items[i].x + items[i].width & mouseY < items[i].y + items[i].height) {
hover.innerHTML = items[i].name;
} else {
hover.innerHTML = 'No';
}
}
}
function mouseClickHandler(event) {
click.innerHTML = mouseX + ' | ' + mouseY;
}
function item(name, ctx) {
this.x = undefined;
this.y = undefined;
this.width = 50;
this.height = 50;
this.name = name;
this.drawItem = function(x, y) {
ctx.fillRect(this.x = x, this.y = y, this.width, this.height);
}
}
<div class='canvas'>
<canvas id='mainCanvas' width='500' height='300'></canvas>
</div>
<p id='click'>0 | 0</p>
<p id='hover'>No</p>
What I'm trying to accomplish:
When the mouse hovers each square (or item) in the canvas, I want the name of the square (assigned as the first parameter when creating each item) to display as the hover element.
What I'm getting:
It somewhat works, but only with the last square. Whenever I hover over any other square that isn't the last one, the 'hover' element does not display the name of the square the mouse is over. I even created and added a new item to the 'items' array and it worked with the new one, but not with the others.
Your problem is that the for loop keeps running after you find a hit. Lets say you mouse item2. Your code will work and display 'item2'. Then the loop goes to the next step and checks if you hover over item3. You don't, so the code displays 'no'.
The solution is to stop the loop once you have a hit
if (mouseX > items[i].x &
mouseY > items[i].y &
mouseX < items[i].x + items[i].width &
mouseY < items[i].y + items[i].height
) {
hover.innerHTML = items[i].name;
break;
} else {
hover.innerHTML = 'No';
}
I'm trying to build an html canvas pad that will allow a user to drag and drop a dot on the pad, which will then return two values (one for Y axis, and one for Y axis), which I can use to trigger effects using the web audio API.
I've already sorted out the web Audio API portion of the problem.
The User:
Clicks and drags the dot to anywhere on the X/Y grid
On Drop we will have an X & Y value (perhaps in hidden range inputs), that trigger eventListeners.
The X value eventListener affects the wet/dry of the delay effect
The Y value eventListener affects the delay_time of the delay effect
so far I've been able to create and render the canvas and circle, and add event listeners on the svg element and the window. With the idea being that I can detect when an event occurs inside the canvas and when that click event leaves the canvas.
// Draw SVG pad
function drawDelayPad() {
var canvas = document.getElementById('delayPad');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
var rectangle = new Path2D();
rectangle.rect(1, 1, 200, 200);
var circle = new Path2D();
circle.moveTo(150, 150);
circle.arc(100, 35, 10, 0 , 2 * Math.PI);
ctx.stroke(rectangle);
ctx.fill(circle);
}
}
// Listener on canvas
var canvas = document.getElementById('delayPad');
canvas.addEventListener("mousedown", function(){
console.log("click inside our canvas")
})
// Listener on document to check if we're outside the canvas
window.addEventListener("mouseup", function(){
console.log("outside our canvas")
});
So I think what I need to determine now is that when a click event does occur inside of the canvas, how far it is from the cirle, and if it does fall within the bounds of the circle, I should redraw it as long as the mousedown event is active.
Any help would be greatly appreciated.
I've found a nice little solution that kind of confirms my suspicions surrounding a hit counter! All credit really goes to rectangleWorld since I was for the most part just able to modify the example they had available.
Here's a codepen
// Draw SVG pad
function canvasApp(canvasID) {
var theCanvas = document.getElementById(canvasID);
var context = theCanvas.getContext("2d");
init();
var numShapes;
var shapes;
var dragIndex;
var dragging;
var mouseX;
var mouseY;
var dragHoldX;
var dragHoldY;
function init() {
numShapes = 1;
shapes = [];
makeShapes();
drawScreen();
theCanvas.addEventListener("mousedown", mouseDownListener, false);
}
function makeShapes() {
var i;
var tempX;
var tempY;
var tempRad;
var tempR;
var tempG;
var tempB;
var tempColor;
var tempShape;
for (i = 0; i < numShapes; i++) {
// My canvas element is 240x240
tempRad = 10;
tempX = 0 + tempRad;
tempY = 240 - tempRad;
tempR = Math.floor(Math.random() * 255);
tempG = Math.floor(Math.random() * 255);
tempB = Math.floor(Math.random() * 255);
tempColor = "rgb(" + tempR + "," + tempG + "," + tempB + ")";
tempShape = {
x: tempX,
y: tempY,
rad: tempRad,
color: tempColor
};
shapes.push(tempShape);
}
}
function mouseDownListener(evt) {
var i;
//We are going to pay attention to the layering order of the objects so that if a mouse down occurs over more than object,
//only the topmost one will be dragged.
var highestIndex = -1;
//getting mouse position correctly, being mindful of resizing that may have occured in the browser:
var bRect = theCanvas.getBoundingClientRect();
mouseX = (evt.clientX - bRect.left) * (theCanvas.width / bRect.width);
mouseY = (evt.clientY - bRect.top) * (theCanvas.height / bRect.height);
//find which shape was clicked
for (i = 0; i < numShapes; i++) {
if (hitTest(shapes[i], mouseX, mouseY)) {
dragging = true;
if (i > highestIndex) {
//We will pay attention to the point on the object where the mouse is "holding" the object:
dragHoldX = mouseX - shapes[i].x;
dragHoldY = mouseY - shapes[i].y;
highestIndex = i;
dragIndex = i;
}
}
}
if (dragging) {
window.addEventListener("mousemove", mouseMoveListener, false);
}
theCanvas.removeEventListener("mousedown", mouseDownListener, false);
window.addEventListener("mouseup", mouseUpListener, false);
//code below prevents the mouse down from having an effect on the main browser window:
if (evt.preventDefault) {
evt.preventDefault();
} //standard
else if (evt.returnValue) {
evt.returnValue = false;
} //older IE
return false;
}
function mouseUpListener(evt) {
theCanvas.addEventListener("mousedown", mouseDownListener, false);
window.removeEventListener("mouseup", mouseUpListener, false);
if (dragging) {
dragging = false;
window.removeEventListener("mousemove", mouseMoveListener, false);
}
}
function mouseMoveListener(evt) {
var posX;
var posY;
var shapeRad = shapes[dragIndex].rad;
var minX = shapeRad;
var maxX = theCanvas.width - shapeRad;
var minY = shapeRad;
var maxY = theCanvas.height - shapeRad;
//getting mouse position correctly
var bRect = theCanvas.getBoundingClientRect();
mouseX = (evt.clientX - bRect.left) * (theCanvas.width / bRect.width);
mouseY = (evt.clientY - bRect.top) * (theCanvas.height / bRect.height);
// Divide by width of canvas and multiply to get percentage out of 100
var DelayTime = ((mouseX / 240) * 100);
// Invert returned value to get percentage out of 100
var DelayFeedback = (100 - (mouseY / 240) * 100);
// Set delay time as a portion of 2seconds
delayEffect.delayTime.value = DelayTime / 100 * 2.0;
// set delay feedback gain as value of random number
delayFeedback.gain.value = (DelayFeedback / 100 * 1.0);
//clamp x and y positions to prevent object from dragging outside of canvas
posX = mouseX - dragHoldX;
posX = (posX < minX) ? minX : ((posX > maxX) ? maxX : posX);
posY = mouseY - dragHoldY;
posY = (posY < minY) ? minY : ((posY > maxY) ? maxY : posY);
shapes[dragIndex].x = posX;
shapes[dragIndex].y = posY;
drawScreen();
}
function hitTest(shape, mx, my) {
var dx;
var dy;
dx = mx - shape.x;
dy = my - shape.y;
//a "hit" will be registered if the distance away from the center is less than the radius of the circular object
return (dx * dx + dy * dy < shape.rad * shape.rad);
}
function drawShapes() {
var i;
for (i = 0; i < numShapes; i++) {
context.fillStyle = shapes[i].color;
context.beginPath();
context.arc(shapes[i].x, shapes[i].y, shapes[i].rad, 0, 2 * Math.PI, false);
context.closePath();
context.fill();
}
}
function drawScreen() {
context.fillStyle = "#000000";
context.fillRect(0, 0, theCanvas.width, theCanvas.height);
drawShapes();
}
}
window.addEventListener("load", windowLoadHandler, false);
function windowLoadHandler() {
canvasApp('delayPad');
}
There are still a few shortcomings, for instance the mouseMoveListener, although constricting the movement of the circle, will continue to increase your x & y values. Meaning you'll either have to use your existing listeners to check when the drag event has exited the circle, or much more simply, you could set an upper limit to your X and Y values.
You'll have to create an object which will store your x and y values.
In below example I called it pad.
This object will serve both your canvas visualization, and your audio processing.
These are both outputs (respectively visual and audio), while the input will be user gesture (e.g mousemove).
The inputs update the pad object, while outputs read it.
[Note]: This example will only work in newest Chrome and Firefox since it uses MediaElement.captureStream() which is not yet widely implemented.
const viz_out = canvas.getContext('2d');
let aud_out, mainVolume;
// our pad object holding the coordinates
const pad = {
x: 0,
y: 0,
down: false,
rad: 10
};
let canvRect = canvas.getBoundingClientRect();
function mousemove(event) {
if (!aud_out || !pad.down) {
return;
}
pad.x = event.clientX - canvRect.left;
pad.y = canvRect.height - (event.clientY - canvRect.top); // inverts y axis
// all actions are splitted
updateViz();
updateAud();
updateLog();
}
viz_out.setTransform(1, 0, 0, -1, 0, 300) // invert y axis on the canvas too
// simply draws a circle where at our pad's coords
function updateViz() {
viz_out.clearRect(0, 0, canvas.width, canvas.height);
viz_out.beginPath();
viz_out.arc(pad.x, pad.y, pad.rad, 0, Math.PI * 2);
viz_out.fill();
}
// You'll do it as you wish, here it just modifies a biquadFilter
function updateAud() {
const default_freq = 350;
const max_freq = 6000;
const y_ratio = pad.y / 300;
aud_out.frequency.value = (default_freq + (max_freq * y_ratio)) - default_freq;
aud_out.Q.value = (pad.x / 300) * 10;
mainVolume.value = 1 + ((pad.y + pad.x) / 75);
}
function updateLog() {
log.textContent = `x:${~~pad.x} y:${~~pad.y}`;
}
canvas.addEventListener('mousedown', e => pad.down = true);
canvas.addEventListener('mouseup', e => pad.down = false);
canvas.addEventListener('mousemove', mousemove);
btn.onclick = e => {
btn.textContent = 'stop';
startLoadingAudio();
btn.onclick = e => {
mainVolume.value = 0;
}
}
window.onscroll = window.onresize = e => canvRect = canvas.getBoundingClientRect();
function startLoadingAudio() {
const audio = new Audio();
audio.loop = true;
audio.muted = true;
audio.onloadedmetadata = e => {
audio.play();
const stream = audio.captureStream ? audio.captureStream() : audio.mozCaptureStream();
initAudioProcessor(stream);
updateLog();
window.onscroll();
updateViz();
}
// FF will "taint" the stream, even if the media is served with correct CORS...
fetch("https://dl.dropboxusercontent.com/s/8c9m92u1euqnkaz/GershwinWhiteman-RhapsodyInBluePart1.mp3").then(resp => resp.blob()).then(b => audio.src = URL.createObjectURL(b));
function initAudioProcessor(stream) {
var a_ctx = new AudioContext();
var gainNode = a_ctx.createGain();
var biquadFilter = a_ctx.createBiquadFilter();
var source = a_ctx.createMediaStreamSource(stream);
source.connect(biquadFilter);
biquadFilter.connect(gainNode);
gainNode.connect(a_ctx.destination);
aud_out = biquadFilter;
mainVolume = gainNode.gain;
biquadFilter.type = "bandpass";
}
}
canvas {
border: 1px solid;
}
<button id="btn">
start
</button>
<pre id="log"></pre>
<canvas id="canvas" width="300" height="300"></canvas>
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.
I need to do an action onclick of a particular (point) or a rectangle in a canvas.
Example:
$(document).ready(function(){
var canvas = $('#myCanvas').get(0);
if (!canvas.getContext) { return; }
var ctx = canvas.getContext('2d');
ctx.fillRect(150,140,8,8);
ctx.fillRect(200,120,8,8);
ctx.fillRect(200,160,8,8);
});
I need to connect two points with a line and another two points with a curve using javascript .How can i do this?
You need to maintain the regions yourselves. There are no objects on a canvas, only pixels and the browser does not know anything about it.
Demo here
You can do something like this (simplified):
// define the regions - common for draw/redraw and check
var rect1 = [150,140,8,8];
var rect2 = [200,120,8,8];
var rect3 = [200,160,8,8];
var regions = [rect1, rect2, rect3];
Now on your init you can use the same array to render all the rectangles:
$(document).ready(function(){
var canvas = $('#myCanvas').get(0);
if (!canvas.getContext) { return; }
var ctx = canvas.getContext('2d');
//use the array also to render the boxes
for (var i = 0, r; r = regions[i]; i++) {
ctx.fillRect(r[0],r[1],r[2],r[3]);
}
});
On the click event you check the array to see if the mouse coordinate (corrected for canvas) is inside any of the rectangles:
$('#myCanvas').on('click', function(e){
var pos = getMousePos(this, e);
// check if we got a hit
for (var i = 0, r; r = regions[i]; i++) {
if (pos.x >= r[0] && pos.x <= (r[0] + r[2]) &&
pos.y >= r[1] && pos.y <= (r[1] + r[3])) {
alert('Region ' + i + ' was hit');
}
}
});
//get mouse position relative to canvas
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
Also remember to redraw the canvas if the window is re-sized or for other reason clears the canvas (browser dialogs etc.).
To connect the boxes you need to store the first hit position and when you get a second hit draw a line between them.
Demo with lines here
Add to the global vars and also make canvas and context available from global (see fiddle for related modifications in onready):
var x1 = -1, y1;
var canvas = myCanvas;
var ctx = canvas.getContext('2d');
And in the click event:
$('#myCanvas').on('click', function(e){
var pos = getMousePos(this, e);
for (var i = 0, r; r = regions[i]; i++) {
if (pos.x >= r[0] && pos.x <= (r[0] + r[2]) &&
pos.y >= r[1] && pos.y <= (r[1] + r[3])) {
//first hit? then store the coords
if (x1 === -1) {
x1 = pos.x;
y1 = pos.y;
} else {
//draw line from first point to this
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
x1 = -1; //reset (or keep if you want continuous lines).
};
}
}
});