I have a bunch of canvas elements in my html file like so:
<canvas id="one"></canvas>
<canvas id="two"></canvas>
I'm using the arrow keys to move an object around #one, but I want to have that object "appear" in #two when it hits a point on #one, e.g. x=150 and y=55.
I tried using the jQuery focus() method when that coordinate was triggered, but the object stayed in #one. Any suggestions?
To make a canvas element focus-able, and therefor able to capture key-strokes, you simply add a tabIndex attribute to the element:
Now you can TAB between the elements and bind event-listeners to the canvas element directly.
Inside the handler you simply draw to the canvas you want based on whatever criteria you require.
Example
var canvases = document.querySelectorAll("canvas"),
i = 0;
while(canvas = canvases[i++]) {
canvas.width = 200;
canvas.tabIndex = 0;
canvas.addEventListener("keyup", process);
}
function process(e) {
var ctx = this.getContext("2d");
ctx.clearRect(0, 0, 200, 150);
ctx.fillText("KEY HERE: " + e.keyCode, 10, 10);
}
canvas {border:1px solid #999;margin:0 2px 2px 0; display:inline-block}
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
Rather than changing focus when that coordinate is hit, I think you need to change the <canvas> element (and associated context) you're drawing to, e.g.:
var theCanvas = document.getElementById("one");
var context = theCanvas.getContext("2d");
... your canvas drawing code here ...
if (posX < 150 && posY < 55) {
//clear the top canvas
context.clearRect(0, 0, theCanvas.width, theCanvas.height);
// switch to the second canvas
theCanvas = document.getElementById("two");
context = theCanvas.getContext("2d");
}
Here's a fiddle that borrows code from rectangleworld.com. Drag the circle into the upper left corner of the top canvas, and it should appear in the lower canvas.
JSFiddle
There is no such thing as focus on a canvas for this purpose.
Your both canvases need to listen for keypresses, while your code needs to decide what to draw on which canvas.
var canvas1 = document.getElementById("one");
var context1 = canvas1.getContext("2d");
var canvas2 = document.getElementById("two");
var context2 = canvas2.getContext("2d");
canvas1.width = canvas1.height = canvas2.width = canvas2.height = 50;
var obj = {x: 5, y: 5, w: 5};
function draw() {
canvas1.width = canvas2.width = canvas2.width;
context1.beginPath();
context1.rect(obj.x, obj.y, obj.w, obj.w);
context1.fillStyle = 'green';
context1.fill();
context2.beginPath();
context2.rect(obj.x - canvas1.width, obj.y, obj.w, obj.w);
context2.fillStyle = 'blue';
context2.fill();
}
window.addEventListener('keydown', function() {
if(++obj.x > canvas1.width + canvas2.width) obj.x = 5;
draw();
});
draw();
canvas {border: 1px solid black; margin: 10px;}
<canvas id="one"></canvas>
<canvas id="two"></canvas>
<p>Click inside this window to get focus and then <br>press any key to move the object to the right</p>
Of course, this can be optimized not to redraw both canvases on each tick, but you get the idea.
Related
How to select multiple squares and on click change their background color on a canvas with javascript?
So far I am able to "draw" the canvas and the coordinate grid. The part I am not able to figure out is how to change the background color of a selected square and how to do the same thing with multiple squares (selecting them with the mouse)?
function draw() {
var canvas = document.getElementById('canvas');
if (canvas.getContext) {
var context = canvas.getContext('2d');
for(var x=0.5;x<500;x+=10) {
context.moveTo(x,0);
context.lineTo(x,500);
}
for(var y=0.5; y<500; y+=10) {
context.moveTo(0,y);
context.lineTo(500,y);
}
context.strokeStyle='grey';
context.stroke();
}
}
function color(event) {
var x = event.clientX - 10;
var y = event.clientY - 10;
console.log(x + ' ' + y);
// how to color the selected squares?
}
#canvas { border: 1px solid black;}
<body onload='draw()'>
<div id=container>
<canvas id='canvas' width='500' height='500' onclick="color(event)"></canvas>
</div>
</body>
It is a good idea to maintain a state. Here I start off creating an two dimensional array (an array of arrays) that holds an object. The state of the object can be selected=true|false.
In the draw() function I iterate the array and set the fillStyle according to the state of the object.
In the color() function I calculate the index of an object and set the selected property to true. And then I draw the canvas again.
var squares = [...new Array(50)].map((a,i) => [...new Array(50)].map((a,i) => {return {selected: false};}));
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
function draw() {
if (canvas.getContext) {
context.fillStyle = 'black';
context.fillRect(0, 0, 501, 501);
squares.forEach((line, i) => {
line.forEach((square, j) => {
context.fillStyle = (square.selected) ? 'red' : 'white';
context.fillRect(10*j+1, 10*i+1, 9, 9);
});
});
}
}
function color(event) {
var x = Math.floor((event.clientX - 10) / 10);
var y = Math.floor((event.clientY - 10) / 10);
squares[y][x].selected = true;
draw();
}
#canvas {
/*border: 1px solid black;*/
}
<body onload='draw()'>
<div id=container>
<canvas id='canvas' width='501' height='501' onclick="color(event)"></canvas>
</div>
</body>
I have this example of a canvas as a div background (I forked it from another example, don't remember where I found it):
http://jsfiddle.net/sevku/6jace59t/18/
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
var divHeight = document.getElementById('canvas').clientHeight;
var divWidth = document.getElementById('canvas').clientWidth;
function assignToDiv(){ // this kind of function you are looking for
dataUrl = canvas.toDataURL();
document.getElementsByTagName('div')[0].style.background='url('+dataUrl+')'
}
function draw() { // replace with your logic
ctx.fillStyle = "rgb(100, 250, 100)";
ctx.fillRect (10, 10, divWidth-20, divHeight-20);
}
draw()
assignToDiv()
My problem; If I put the dimensions of the div 300 x 150, the canvas does what it is supposed to do. But if I change the dimensions, the canvas is supposed to adapt to the div dimensions. What did I do wrong that this doesn't happen?
PS: I'm a beginner, so please forgive me stupid questions.
It's because if you don't give canvas width and height, it's default to 300x150, so after you get the width and height from div, you should use them to set your canvas' dimensions as well.
Another point worth notice is that you use div.style.background property to set the background image, however, as there's many background related properties (e.g: background-repeat in your jsfiddle, background-position, background-size...), the background can set all of them at once.
When you use div.style.background='url('+dataUrl+')';. It overrides all other background-related properties to initial.
If you want to preserve those properties, you may either reset them after you set style.background, or you can use div.style.backgroundImage to change the background image without affect other background related properties.
jsfiddle
var canvas = document.getElementsByTagName('canvas')[0];
var ctx = canvas.getContext('2d');
var divHeight = document.getElementById('canvas').clientHeight;
var divWidth = document.getElementById('canvas').clientWidth;
// VVVV After you get WxH, set the canvas's dimension too.
canvas.width = divWidth;
canvas.height = divWidth;
var div1 = document.getElementById('canvas');
var div2 = document.getElementById('canvas2');
function assignToDiv(div){ // this kind of function you are looking for
var dataUrl = canvas.toDataURL();
div.style.background='url('+dataUrl+')'; // This line will overwrite your background settings.
div.style.backgroundRepeat = 'repeat-x'; // Use this to set background related properties after above.
}
function assignToDivAlt(div){ // this kind of function you are looking for
var dataUrl = canvas.toDataURL();
div.style.backgroundImage = 'url('+dataUrl+')'; // Only set the background-image would have same effect.
}
function draw() { // replace with your logic
ctx.fillStyle = "rgb(100, 250, 100)";
// If you don't set WH, then the canvas would be 300x150, and those
// you drawed but out of boundary are clipped.
ctx.fillRect (10, 10, divWidth-20, divHeight-20);
}
draw()
assignToDiv(div1);
assignToDiv(div2);
canvas {display:none;}
div {
width:600px;
height:550px;
border:1px solid grey;
background-repeat:repeat-x;
}
<canvas></canvas>
<div id="canvas"></div>
<div id="canvas2"></div>
I have a canvas that is drawing an image and clipping to create the effect that the image is being revealed. I have the code working properly I have tried using a debouce method and also rAF to increase the canvas rendering performance but I only saw small gains if any.
I suspect the way I am iterating through my array of x and y coordinates could be the issue.
It seems to lag quite a bit when it is out putting the array in console about the same rate as the circle appear on the screen.
Here is the redraw function:
function redraw(mouse) {
m.push(mouse);
m.forEach(function (a) {
ctx.drawImage(img, 0, 0);
ctx.beginPath();
ctx.rect(0, 0, 500, 500);
ctx.arc(a.x, a.y, 70, 0, Math.PI * 2, true);
ctx.clip();
ctx.fillRect(0, 0, 500, 500)
})
}
I guess what I am looking for is some advice to speed up my code so the rendering of the circles seems more like drawing.
Here is the working demo -> http://jsfiddle.net/naeluh/4h7GR/
There are several issues here :
• Your mouse code is a nightmare, traversing the DOM on every move.
• You are redrawing everything on each move.
So i suggest a way more efficient solution :
• stack two canvases, the one below is your image, the one on top is the mask.
• Deal efficiently with the mouse.
• Only clear part of the mask canvas on mouse move : just one circle drawn on the mask canvas for each move.
(for that i used a globalCompositeOperation = 'destination-out' )
Result is perfectly smooth either on Firefox, Chrome, or Safari .
(tested on mac OS).
the fiddle :
(you have to click to clear)
http://jsfiddle.net/gamealchemist/4h7GR/22/
html
<canvas style='position: absolute; top: 0;left: 0;' id="canvas1" width="500" height="500"></canvas>
<canvas style='position: absolute;top: 0;left: 0;' id="canvas2" width="500" height="500"></canvas>
js
var can = document.getElementById("canvas1");
var ctx = can.getContext("2d");
var can2 = document.getElementById("canvas2");
var ctx2 = can2.getContext("2d");
var img = new Image();
img.onload = function () { ctx.drawImage(img,0,0); };
img.src = "http://placekitten.com/500/500";
ctx2.fillStyle='#000';
ctx2.fillRect(0,0,500,500);
ctx2.globalCompositeOperation = 'destination-out';
function clearThis(x,y) {
console.log('toto');
ctx2.fillStyle='#F00000';
ctx2.beginPath();
ctx2.arc(x, y, 70, 0, Math.PI * 2, true);
ctx2.fill();
}
var mouse = {
x: 0,
y: 0,
down: false
};
function setupMouse(canvas, onMouseMove, preventDefault) {
var rectLeft, rectTop;
var hook = canvas.addEventListener.bind(canvas);
var mouseDown = updateMouseStatus.bind(null, true);
var mouseUp = updateMouseStatus.bind(null, false);
hook('mousedown', mouseDown);
hook('mouseup', mouseUp);
hook('mousemove', updateCoordinates);
hook('scroll', updateRect);
// var mouseOut = function() { mouse.down=false ; } ;
// hook('mouseout', mouseOut);
function updateMouseStatus(b, e) {
mouse.down = b;
updateCoordinates(e);
if (preventDefault) {
e.stopPropagation();
e.preventDefault();
}
}
function updateCoordinates(e) {
mouse.x = (e.clientX - rectLeft);
mouse.y = (e.clientY - rectTop);
onMouseMove(mouse.x, mouse.y);
}
function updateRect() {
var rect = canvas.getBoundingClientRect();
rectLeft = rect.left;
rectTop = rect.top;
}
updateRect();
};
setupMouse(can2, clearThis, true);
The Above Code will do Fine .. But nEed some Editing
I have Edited the Code in Fiddle ..and i beleive there Is some Improvement in perforamnce
So I looked a little more and found a bug as expected.
The main problem is the accumulation of the drawing path.
Why Need to add clip and fillRect at every go ..Do it at last... the Major issue solved,Like
can.addEventListener("mousemove", function (e) {
var mouse = getMouse(e, can);
requestAnimationFrame(function () {
redraw(mouse);
ctx.clip();
ctx.fillRect(0, 0, 500, 500);
console.log(mouse);
});
}, false);
2.The Updated JSFiidle is
UpdatedFiddle
I have a Sharepoint page in which i want to show a hierarchical diagram with boxes.According to my requirement those boxes should work as links to other sharepoint pages in the same site.
Since sharepoint's default designer tools doesn't support designing such diagrams, I created a page with html5 canvas and the element i wanted inside that.
Inside the canvas i created few boxes and lines to connect them.And i added texts inside the boxes.Then i used a mouse listener to check whether the mouse pointer hovers over a box and if so changed the pointer icon and the link to be redirected to.
I added the canvas tag inside the sharepoint page by "Edit Source" and i added the javascript part using 'Embed Code'
Now the code works perfectly in IE and Firefox.
In chrome although the boxes,lines and text are drawn according to the coordinates i gave in the code but But when i hover the mouse over them it gives different coordinates for mouse listener in different browser sizes.So the mouse pointer doesn't change at correct locations ie: over the boxes.
This doesn't happen in firefox or IE. They changes the mouse pointer when it comes over the boxes and links to the pages perfectly.
Why does it change when i use chrome?
And why does it only affect to the mouse listener coordinates.
This is the code i used.(I have removed the repetitive parts which draws other boxes)
Same in jsfiddle
<canvas id="myCanvas" height="500" width="960" style="border: 1px solid;"><img src="" alt=""/> </canvas>
<script>
var canvas = document.getElementById("myCanvas");
var ctx;
var rNBDX = 50; var rNBDY = 150;
var rectWidth = 200;
var rectHeight = 100;
var cornerRadius = 20;
var linkNBD="https://google.com";
var textNBD1 ="Google";
var linkHeight=20;
var linkNum = 0;
function draw(){
canvas = document.getElementById("myCanvas");
if(canvas.getContext){
ctx=canvas.getContext("2d");
//Drawing Lines
ctx.lineWidth = 3;
ctx.strokeStyle = '#000000';
ctx.moveTo(380, 100);
ctx.lineTo(380, 125);
ctx.stroke();
//Drawing Rectangles
ctx.fillStyle="#0b61d0";
ctx.strokeStyle="#0b61d0";
ctx.lineJoin = "round";
ctx.lineWidth = cornerRadius;
ctx.strokeRect(rNBDX+(cornerRadius/2), rNBDY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
ctx.fillRect(rNBDX+(cornerRadius/2), rNBDY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
//Drawing the Texts
ctx.font='24px Segoe UI Light';
ctx.fillStyle = "#FFFFFF";
ctx.fillText(textNBD1,(rNBDX+rectWidth/2)-(ctx.measureText(textNBD1).width)/2,rNBDY+rectHeight/2);
//Add mouse listeners
canvas.addEventListener("mousemove", on_mousemove, false);
canvas.addEventListener("click", on_click, false);
}
}
function on_mousemove (ev) {
var x, y;
if (ev.layerX || ev.layerX == 0) {
x = ev.layerX;
y = ev.layerY;
}
x-=canvas.offsetLeft;
y-=canvas.offsetTop;
if(x>=rNBDX && x <= (rNBDX + rectWidth) && y>=rNBDY && y<= (rNBDY+rectHeight)){
document.body.style.cursor = "pointer";
linkNum=1;
}
else{
document.body.style.cursor = "";
}
}
function on_click(e) {
switch (linkNum)
{
case 1:
window.location = linkNBD;
break;
}
}
draw();
</script>
Try adjusting the mouse coordinates like this:
function on_mousemove (ev) {
var x, y,
rect = canvas.getBoundingClientRect();
x = ev.clientX - rect.left + 1;
y = ev.clientY - rect.top + 1;
...
You will have to add (as in the example) the width of the left/top border though as getBoundingClientRect does not include those (you can calculate this dynamically using getComputedStyle and getPropertyValue of that for the borders).
I am trying to draw over a canvas by clicking and dragging the mouse. My problem is that, apart from the fact that the line has a very poor quality (I want a more pronounced border) it only respects the mouse position when this is at 0,0. As I move the mouse to the lower corner, the line increments its distance from it as much as when I am in the middle of the canvas, the line is already out of it.
I have my code at: http://jsfiddle.net/ajTkP/12/
I will also post it here:
var MDown = false;
var Color = 'blue';
var Canvas = document.getElementById('canvas');
var Context = Canvas.getContext('2d');
Canvas.onselectstart = function() { return false; };
Canvas.unselectable = "on";
Canvas.style.MozUserSelect = "none";
Canvas.onmousedown = function(e) {
MDown = true;
Context.strokeStyle = Color;
Context.lineWidth = 3;
Context.lineCap = 'round';
Context.beginPath();
Context.moveTo(e.pageX - Position(Canvas).left, e.pageY - 5);
}
Canvas.onmouseup = function() { MDown = false; };
Canvas.onmousemove = function(e) {
if (MDown) {
Context.lineTo(e.pageX - Position(Canvas).left, e.pageY - 5);
Context.stroke();
}
}
function Position(el) {
var position = {left: 0, top: 0};
if (el) {
if (!isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
position.left += el.offsetLeft;
position.top += el.offsetTop;
}
}
return position;
}
Thanks for your help!
You need to set an explicit width and height on the canvas. The default dimensions of a canvas are a width of 300 and a height of 150 (see the spec here). By setting the width and height via CSS you are just stretching the canvas.
Either do:
<canvas id="canvas" width="300" height="200"></canvas>
or set the width/height via JavaScript:
canvas.width = 300;
canvas.height = 200;
See the updated jsfiddle: http://jsfiddle.net/ajTkP/13/
It looks like jimr beat me to the punch about the canvas height and width.
The poor quality of the line though is due to how you're drawing the line. You'll notice that you're calling stroke() on every onmousemove event. Keep in mind that it's keeping track of the path of the line from when you beginPath() to when you closePath(), so you're basically stroking the same line multiple times (every time your mouse moves). This is what's giving you the aliased (blocky-looking) lines, instead of the smooth anti-aliased lines you're expecting.