increase performance on html canvas mousemove image mask - javascript

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

Related

How to change focus to new HTML5 Canvas element?

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.

Moving All Images In A Clipped Canvas

Ok so I have just looked into the Canvas element but I have hit a snag.
I found a demo that moves three images over the top of one another using arrow keys.
However, I clipped one of them and the underlying image does not move now even though the above clipped image does.
HTML:
<canvas id="parallax-canvas">
Sorry, your browser does not support HTML5 Canvas.
</canvas>
CSS:
.parallax-canvas {
width: 400px;
height: 300px;
}
JavaScript:
$(document).ready(function() {
var w = $("#parallax-canvas").width();
var h = $("#parallax-canvas").height();
var sky = new Image();
sky.src = "assets/img/sky.jpg";
var skydx = 2;
var skyx = 0;
var mountains = new Image();
mountains.src ="assets/img/mountains.png";
var mountainsdx = 10;
var mountainsx = 0;
var jeep = new Image();
jeep.src ="assets/img/jeep.png";
var jeepx = 100;
var jeepy = 210;
var jeepsx = 0;
var jeepsxWidth = 155;
var cntx = $("#parallax-canvas")[0].getContext("2d");
setInterval(draw, 10, cntx);
$(window).keydown(function(evt) {
switch (evt.keyCode) {
case 37: // Left arrow
if ((skyx + skydx) > skydx)
skyx -= skydx;
else
skyx = w;
if ((mountainsx + mountainsdx) > mountainsdx)
mountainsx -= mountainsdx;
else
mountainsx = 398;
if (jeepsx > 0)
jeepsx -= jeepsxWidth;
else
jeepsx = (jeepsxWidth*2);
break;
case 39: // Right arrow
if ((skyx + skydx) < (w - skydx))
skyx += skydx;
else
skyx = 0;
if ((mountainsx + mountainsdx) < (w - mountainsdx))
mountainsx += mountainsdx;
else
mountainsx = 0;
if (jeepsx < (jeepsxWidth*2))
jeepsx += jeepsxWidth;
else
jeepsx = 0;
break;
}
});
function draw(_cntx) {
drawRectangle(_cntx, 0, 0, w, h);
_cntx.drawImage(sky, skyx, 0, 300, 300, 0, 0, w, 300);
_cntx.beginPath();
_cntx.moveTo(0,300);
_cntx.lineTo(150,150);
_cntx.lineTo(300, 300);
_cntx.closePath();
_cntx.clip();
_cntx.drawImage(mountains, mountainsx, 0, 300, 300, 0, 0, w, 300);
_cntx.drawImage(jeep, jeepsx, 0, jeepsxWidth, 60, jeepx, jeepy, 155, 60);
}
function drawRectangle(_cntx, x, y, w, h) {
_cntx.beginPath();
_cntx.rect(x,y,w,h);
_cntx.closePath();
_cntx.fill();
_cntx.stroke();
}
});
The part where I added the clipping is this section:
function draw(_cntx) {
drawRectangle(_cntx, 0, 0, w, h);
_cntx.drawImage(sky, skyx, 0, 300, 300, 0, 0, w, 300);
_cntx.beginPath();
_cntx.moveTo(0,300);
_cntx.lineTo(150,150);
_cntx.lineTo(300, 300);
_cntx.closePath();
_cntx.clip();
_cntx.drawImage(mountains, mountainsx, 0, 300, 300, 0, 0, w, 300);
_cntx.drawImage(jeep, jeepsx, 0, jeepsxWidth, 60, jeepx, jeepy, 155, 60);
}
If you remove from and including the beginPath method to the clip method it will allow the three images to all move.
WHAT IS HAPPENING: The sky image does not move when pressing left and right arrow keys, the mountain and jeep are clipped but do move within the clipped region.
WHAT I WANT TO HAPPEN: The sky image to move as well as the mountain and jeep in the clipped region.
ALSO:
I would like to know how to move the clipped region with the arrow presses. What I mean is, at the moment there is a clipped region where by pressing left and right the images move but the clipped (cut out) region remains still. I would like to know (if possible) how to go about moving the clipped region with the arrow keys.
Demo being used: http://www.ibm.com/developerworks/web/library/wa-parallaxprocessing/
Unfortunately cannot setup a Fiddle as the images are not on the site so cannot be given a URL but the .zip is on the site and by copying and pasting the bottom part of code where their method is in the JavaScript file that is what I am getting.
Thanks for any help!
EDIT: Thanks Ken for your help with shortening code and efficiency. Nothing so far though has answered my initial questions of how to move the images and also the clipping positions.
You need to set a size for your canvas - don't use CSS to set the size of a canvas:
<canvas id="parallax-canvas" width=500 height=300>
Sorry, your browser does not support HTML5 Canvas.
</canvas>
Likewise you need to read the proper size from the canvas:
$(document).ready(function() {
var canvas = $("#parallax-canvas")[0];
var w = canvas.width;
var h = canvas.height;
...
The for each time you hit the cursor keys you need to redraw everything. The canvas doesn't know what is drawn into it - it's just a bunch of pixels so we need to provide the logic for updates ourselves.
Update seem as I missed you're doing the redraw from a setInterval loop so this doesn not apply so much, but I let the example stay as it will probably be a better approach as you only need to update when something is actually changing. A loop that redraws everything all the time will quickly drain batteries for example..
For example:
$(window).keydown(function(evt) {
switch (evt.keyCode) {
case 37: // Left arrow
if ((skyx + skydx) > skydx)
skyx -= skydx;
else
skyx = w;
if ((mountainsx + mountainsdx) > mountainsdx)
mountainsx -= mountainsdx;
else
mountainsx = 398;
if (jeepsx > 0)
jeepsx -= jeepsxWidth;
else
jeepsx = (jeepsxWidth*2);
draw(cntx );
break;
...
This you need to repeat for the other moves as well.
You will also run into problems with the way you are loading the images as loading is asynchronous. You need to handle loading by tapping into the onload handler:
var sky = new Image();
sky.onload = functionToHandleLoad;
sky.src = "assets/img/sky.jpg";
As you are loading many images you would need to count the images or use a bulk loader such as my YAIL loader.
For clipping it's important to use save/restore as currently browsers doesn't handle manual reset of clip regions that well:
function draw(_cntx) {
drawRectangle(_cntx, 0, 0, w, h);
_cntx.drawImage(sky, skyx, 0, 300, 300, 0, 0, w, 300);
_cntx.beginPath();
_cntx.moveTo(0,300);
_cntx.lineTo(150,150);
_cntx.lineTo(300, 300);
_cntx.closePath();
_cntx.save(); /// save current clip region
_cntx.clip();
_cntx.drawImage(mountains, mountainsx, 0, 300, 300, 0, 0, w, 300);
_cntx.drawImage(jeep, jeepsx, 0, jeepsxWidth, 60, jeepx, jeepy, 155, 60);
_cntx.restore(); /// reset clip
}
At the time of answering there where no fiddle available so I haven't checked the other parts of the code, but this should be a good start I think.
This function
function drawRectangle(_cntx, x, y, w, h) {
_cntx.beginPath();
_cntx.rect(x,y,w,h);
_cntx.closePath();
_cntx.fill();
_cntx.stroke();
}
can be simplified to:
function drawRectangle(_cntx, x, y, w, h) {
_cntx.fillRect(x, y, w, h);
_cntx.strokeRect(x, y, w, h);
}
No need to close path on a rect and beginPath is not needed with fillRect/strokeRect.

HTML5 Canvas Coordinates Chnged in chrome

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).

Drawing at cursor position on canvas with JavaScript

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.

How to draw HTML5 Canvas lines given user input of X,Y points?

I am trying to build a floorplan model in Canvas. Currently I have a grid image in my canvas and have the ability for users to draw lines by clicking and dragging their mouse. But, this doesn't guarantee straight lines.
Is there anyway I can provide input fields in the html page for users to input the beginning and ending x and y coordinates of the lines, and have it updated in my canvas code? I'm a beginner when it comes to JS/AJAX, so any remedial help is appreciated :)
Right now, this is the section that dictates how the lines get drawn:
$(document).ready(function() {
var canvas = document.getElementById('canvas');
var context = canvas.getContext("2d");
if(canvas.getContext) {
$('#canvas').mousedown(function (evt) {
var offset = $('#canvas').offset();
context.beginPath();
context.moveTo(20, 20);
});
$(document).mousemove(function(evt) {
var offset = $('#canvas').offset();
context.lineTo(evt.pageX - offset.left, evt.pageY - offset.top);
context.stroke();
}).mouseup(function() {
$(document).unbind('mousemove');
$(document).unbind('mouseup');
});
$('#clearcanvas').click(function () {
context.clearRect(0, 0, 600, 580);
});
}
});
I suspect it involves modifying following code:
context.lineTo(evt.pageX - offset.left, evt.pageY - offset.top);
Very simply you could use 4 input fields and take the value of each when a button is pressed
button.addEventListener('click', function() {
ctx.beginPath();
ctx.moveTo(x1.value, y1.value);
ctx.lineTo(x2.value, y2.value);
ctx.stroke();
}, false);
http://jsfiddle.net/TeGGx/

Categories