Rotating around an arbitrary point: HTML5 Canvas - javascript

Come see the amazing disappearing rectangle!
But seriously I have a really simple HTML5 canvas that just draws a rectangle(using lineTo instead of rect for a reason).
My Problem: I am attempting to rotate the rectangle 90 degrees. The rectangle should be rotated 90 degrees but instead it disappears.
In my webapp project I am getting weird x,y placement errors when I rotate my complex polygons in HTML5 canvas', so I have created this simple HTML to test rotation & ensure its rotating around its x,y point of 100,100. But even this has weird results when I attempt to rotate a shape.
Can anyone tell me how to get my rectangle visible & how I can rotate my polygons around a specific point without them completely changing x,y values.
Has anyone experienced this issue with HTML5 canvas' & know solutions to fix this?
<canvas id="testCanvas" width="900px" height="900px" style="background-color: blue;">
</canvas>
<script type="text/javascript">
var canvas = document.getElementById("testCanvas");
var dc = canvas.getContext("2d");
dc.clearRect(0, 0, canvas.width, canvas.height);
dc.save();
dc.fillStyle = "#FF0000";
dc.rotate( 90*Math.PI/180 ); // rotate 90 degrees
dc.beginPath();
dc.moveTo(100, 100);
dc.lineTo(200, 100);
dc.lineTo(200,300);
dc.lineTo(100,300);
dc.closePath();
dc.fill();
dc.restore();
-->
</script>

To rotate around a point you need to do 3 steps.
First translate the context to the center you wish to rotate around.
Then do the actual rotation.
Then translate the context back.
Like this:
var canvas = document.getElementById("testCanvas");
var dc = canvas.getContext("2d");
var angle = 0;
window.setInterval(function(){
angle = (angle + 1) % 360;
dc.clearRect(0, 0, canvas.width, canvas.height);
dc.save();
dc.fillStyle = "#FF0000";
dc.translate(150,200); // First translate the context to the center you wish to rotate around.
dc.rotate( angle*Math.PI/180 ); // Then do the actual rotation.
dc.translate(-150,-200); // Then translate the context back.
dc.beginPath();
dc.moveTo(100, 100);
dc.lineTo(200, 100);
dc.lineTo(200,300);
dc.lineTo(100,300);
dc.closePath();
dc.fill();
dc.restore();
}, 5);

When you rotate the canvas, it rotates from the origin (0, 0), so your rectangle ends up getting rotated off the screen.
See this example where it's only rotated 45 deg: http://jsfiddle.net/wjLSm/
One way to fix this is to translate the canvas by its width & height/2: http://jsfiddle.net/wjLSm/1/ (then 0,0 is at the middle -- be aware of this)

Did you mean like this?
var canvas = document.getElementById("testCanvas");
var dc = canvas.getContext("2d");
dc.clearRect(0, 0, canvas.width, canvas.height);
dc.save();
dc.fillStyle = "#FF0000";
dc.translate(200, -100);
dc.rotate(45 * Math.PI / 180);
dc.beginPath();
dc.moveTo(100, 100);
dc.lineTo(200, 100);
dc.lineTo(200, 300);
dc.lineTo(100, 300);
dc.closePath();
dc.fill();
// rotate 90 degrees
dc.restore();
<canvas id="testCanvas" width="900px" height="900px" style="background-color: blue;">
</canvas>

Related

Canvas, diagonal lines are thicker

i have following problem. Im trying to draw a border with notches on my buttons and slider. thats working fine. but i noticed that my diagonal lines are twice as thick as the normal ones.
I saw that i have to work on the width/height settings in the canvas/tag itself. but i cant get it working!
Maybe i dont understand realy to make it work. pls help :)
button_canvas_border
slider_canvas_border
thats my html
<canvas width="1290" height="738" class="slider_canvas" id="slider_canvas" ></canvas>
or
<canvas class="slider_canvas" id="slider_canvas" ></canvas>
both dont work
and my js
jQuery(document).ready(function($) {
$('.slider_canvas').each(function(index, canvas) {
var wrapper_height = $('.nsaw_slider_front_wrapper').height() + 30;
var wrapper_width = $('.nsaw_slider_front_wrapper').width() + 30;
/* doesnt matter what i do on the below, its just not working */
canvas.width = wrapper_width;
canvas.height = wrapper_height;
canvas.style.width = wrapper_width;
canvas.style.height = wrapper_height;
canvas.setAttribute('width', wrapper_width);
canvas.setAttribute('height', wrapper_height);
/* doesnt matter what i do on the above, its just not working */
var point_01_cord = wrapper_width * 0.85;
var point_02_cord = wrapper_height * 0.25423;
var point_03_cord = wrapper_width * 0.15;
var point_04_cord = wrapper_height * 0.74577;
var ctx = canvas.getContext('2d');
var gradient = ctx.createLinearGradient(0, 0, 170, 0);
gradient.addColorStop("0", "#0033ff");
gradient.addColorStop("1" ,"#ffff00");
ctx.strokeStyle = gradient;
ctx.lineWidth = 5;
ctx.beginPath();
ctx.translate(0.5,0.5);
ctx.moveTo(0,0);
ctx.lineTo(point_01_cord,0);
ctx.lineTo(wrapper_width,point_02_cord);
ctx.lineTo(wrapper_width,wrapper_height);
ctx.lineTo(point_03_cord,wrapper_height);
ctx.lineTo(0,point_04_cord);
ctx.lineTo(0,0);
ctx.stroke();
});
});
maybe someone can help :)
Strokes do overlap from both sides of the coordinates, that's by the way why you saw some say you should translate by 0.5, so that lineWidth = 1 covers a full pixel instead of two halves. (More on that here).
Here you are drawing a 5px wide line so you really don't want that offset (5 pixels can be rendered perfectly fine), even though it's not your actual problem here.
Your drawing is at the edges of the canvas boundaries. This means that only half of your line is visible.
To workaround that, offset all your lines coordinates to take into account its lineWidth. For instance the top line instead of having its y values set to 0 should have it set to 2.5 without the initial translate or to 2 with if you really want to keep it.

How to rotate one object at another (slowly)

I have been looking around for this function and thus far I just can't find any I can make any sense of. I already have a rotating function to make it equal to the position but slowly is proving to be a bit harder with 0-360 and all.
I am using a html canvas 2d context to render the objects on a Cartesian coordinate system .
I would like object1 to face at positionX and positionY at a turn rate (R) , fairly straightforward.
there is no need for me to supply any code since your likely going to make your own anyways. But I will anyways here you go:
let faceAt = function (thisObject,positionX,positionY) {
let desiredLocationX = positionX - thisObject.transform.x;
let desiredLocationY = positionY -thisObject.transform.y;
thisObject.transform.rotation = Math.degrees(Math.atan2(desiredLocationY, desiredLocationX));
};
The (Math.degrees) function converts radians to degrees.
This thread says it all : https://www.google.ca/amp/s/jibransyed.wordpress.com/2013/09/05/game-maker-gradually-rotating-an-object-towards-a-target/amp/
This question is quite unclear. But, I'm assuming you essentially just want to rotate an element around an arbitrary point on a HTML5 canvas.
On a canvas, you can only draw one element at a time. You can't really manipulate singular elements - for example, you can't rotate an element by itself. Instead, you'd need to rotate the entire canvas. This will always rotate around the centre of the canvas, but if you move the canvas origin, then you will draw on a different part of the canvas; thus allowing you to rotate around a point.
Check out the following example. You can click anywhere on the canvas to make the square rotate around that point. Hopefully this is what you are after:
let cv = document.getElementById("cv");
let ctx = cv.getContext("2d");
let angle = 0;
//Variables you can change:
let speed = 1; //Degrees to rotate per frame
let pointX = 250; //The x-coord to rotate around
let pointY = 250; //The y-coord to rotate around
ctx.fillStyle = "#000";
setInterval(()=>{ //This code runs every 40ms; so that the animation looks smooth
angle = (angle + speed) % 360; //Increment the angle. Bigger changes here mean that the element will rotate faster. If we go over 360deg, reset back to 0.
ctx.clearRect(0, 0, 400, 400); //Clear away the previous frame.
//Draw the point we are rotating around
ctx.beginPath();
ctx.arc(pointX,pointY,5,0,2*Math.PI);
ctx.fill();
ctx.closePath();
ctx.save(); //Save the state before we transform and rotate the canvas; so we can go back to the unrotated canvas for the next frame
ctx.translate(pointX, pointY); //Move the origin (0, 0) point of the canvas to the point to rotate around. The canvas always rotates around the origin; so this will allow us to rotate around that point
ctx.rotate(angle*Math.PI/180); //Rotate the canvas by the current angle. You can use your Math.degrees function to convert between rads / degs here.
ctx.fillStyle = "#f00"; //Draw in red. This is also restored when ctx.restore() is called; hence the point will always be black; and the square will always be red.
ctx.fillRect(0, 0, 50, 50); //Draw the item we want rotated. You can draw anything here; I just draw a square.
ctx.restore(); //Restore the canvas state
}, 40);
//Boring event handler stuff
//Move the point to where the user clicked
//Not too robust; relys on the body padding not changing
//Really just for the demo
cv.addEventListener("click", (event)=>{
pointX = event.clientX - 10;
pointY = event.clientY - 10;
});
#cv {
border:solid 1px #000; /*Just so we can see the bounds of the canvas*/
padding:0;
margin:0;
}
body {
padding:10px;
margin:0;
}
<canvas id="cv" width="400" height="400"></canvas><br>
Click on the canvas above to make the rectangle rotate around the point that was clicked.

Use setInterval to infinitely draw rectangles

I'm trying to learn how to learn basic animation using ONLY canvas with the setInterval function.
I'm trying to draw a simple rectangle to the screen and move it to the right 1 pixel every 100 milliseconds. However when I do it paints over the previous rectangle. I called clearRect() but that doesn't seem to do anything.
How can I make this rectangle smoothly travel across the screen without leaving a trail?
Also if there is a better way to do this rather than using clearRect() and translate() please do share.
var ctx = document.getElementById('mycanvas').getContext('2d');
var a = setInterval(draw,100);
var x = 50;
function draw()
{
ctx.clearRect(0,0,300,300);
ctx.translate(1,0);
ctx.rect(x,50,50,50);
ctx.stroke();
}
You can do it two different ways:
You can continue to use rect() and stroke(), but you need to call beginPath() beforehand. When you call methods like rect(), a list, called the "path," is kept of all of the shapes, or "subpaths," that you've created. Then, when you call stroke(), the entire path is drawn. Thus, even if you clear the screen, all of the past rectangles are still remembered in the path, and drawn again. beginPath() clears that list.
var x = 50;
function draw() {
ctx.clearRect(0, 0, 300, 300);
ctx.beginPath();
ctx.rect(x, 50, 50, 50);
ctx.stroke();
x++;
}
Or, you can combine the rect() and stroke() into one line, and not need to call beginPath(). That's because the rectangle is both created and drawn at the same time, and isn't put in the list.
var x = 50;
function draw() {
ctx.clearRect(0, 0, 300, 300);
ctx.strokeRect(x, 50, 50, 50);
x++;
}
Either way, I advise incrementing x instead of using translate(), because translate() basically moves the imaginary "pen" that is drawing on the canvas. So if you translate(50, 50), and then you try to draw a rectangle at (0, 0) on the canvas, it will actually be at (50, 50).
As Microsoft puts it on MSDN, "The translate method effectively remaps the (0,0) origin on a canvas."
If you repeatedly do that, it will become difficult to keep track of where you're actually drawing.
Your x variable never changes, so your shape will not move. You need to increment x to get movement:
var x = 50;
function draw(){
ctx.clearRect(0,0,300,300);
ctx.translate(1,0);
ctx.rect(x,50,50,50);
ctx.stroke();
x++;
}
In order to get a smooth animation with shapes and other sprites moving across the screen (or even staying still) it would be better to make a clearScreen method that will basically draw over the entire canvas in whatever background color the canvas is. It is basically just a function that will draw a white (or whatever background color you are using) rectangle over the entire canvas. Then, you call the draw function that will make all the necessary drawings. That way, there won't be any trail or anything of the past movements and you won't have to call clearRect() on every single rectangle you make.
Basically, the function will erase the canvas and you can redraw whatever you need to in order to make the animation of the box moving across the screen.
Does that make sense?
EDIT:
Also, to be clear, you would make your own clearScreen method based on what size your canvas is and what color your background is. Its not hard, all it does is draw a rectangle over the screen.
Simply increment x on every call:
var canvas = document.getElementById('mycanvas')
var ctx = canvas.getContext('2d');
var a = setInterval(draw,100);
var x = 50;
function draw(){
canvas.width = canvas.width; // clears the canvas
ctx.rect(x++,50,50,50);
ctx.stroke();
if (x > 250) // resets the position
x = 50;
}
<canvas id="mycanvas"></canvas>
I also removed that translation since there's no need to do it just for the square animation.
Try beginPath() and closePath():
var maxX, x = 0,
s = 50,
maxY;
var repaint = function(ctx) {
if (x + s >= maxX) { //reached end of the canvas
return;
}
ctx.clearRect(0, 0, maxX, maxY); //clear previous
ctx.beginPath(); //start drawing
ctx.rect(x, s, s, s);
ctx.stroke();
ctx.closePath(); //stop drawing
x++;
setTimeout(function() {
repaint(ctx); //continue here
}, 100);
};
var cnvs = document.getElementById('canvas');
maxX = cnvs.width;
maxY = cnvs.height;
repaint(cnvs.getContext('2d'));
canvas {
border: 1px solid grey;
}
<canvas width="360" height="180" id='canvas'>HTML5 canvas not supported</canvas>

Drawing wtih mouse move make line way of mouse point

I am making a project where i want to draw on a canvas.
I get the draw method on mouseover and intend to draw a line while following the mouse.
It draws the line perfectly only not on the correct position.
It is made possibly bij JQuery.
var canvas = document.getElementById('myCanvas');
ctx = canvas.getContext('2d');
ctx.beginPath();
$("#myCanvas").mousemove(function(arg)
{ ctx.lineTo(arg.pageX,arg.pageY-80);
ctx.stroke();
});
My html Canvas code:
<canvas id="myCanvas" width="500" height="500">
</canvas>
I hope it is understandable and that somebody can help me. (information the -80 at pageY is because i works better on my screen than)
Here is an optional way of reading mouse position relative to canvas:
$("#myCanvas").mousemove(function(arg) {
var pos = getMousePos(canvas, arg);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
});
function getMousePos(canvas, e) {
var rect = canvas.getBoundingClientRect(); // absolute position of canvas
return {
x: e.clientX - rect.left, // make x/y relative to canvas
y: e.clientY - rect.top
};
}
Just a side note: We will get issues with that lineTo/stroke combo BTW as the lineTo will add to path and when you stroke, the new line as well as all the other lines added will be stroked. As it is it will become slower the more lines that are drawn and anti-aliased pixels will start to appear. You can use beginPath and moveTo to solve but that is out-of-scope for this question.

Scaling background pattern fill in html5 canvas tag

I have a canvas tag displaying a set of paths and rectangles.
I zoom into these paths and rectangles by using the ctx.scale(2,2) function and then redraw them with the new scale so they are sharp.
I would like to have a simple textured background for some of the rectangles, i.e. one pixel black one white, however when I apply the texture as a fill to the scaled rectangle, canvas scales the background pattern as well. I would like the background pattern to remain at the original scale of one pixel black, one pixel white. But cannot seem to figure out how to do this with it looking blurry. I have a example here : http://jsfiddle.net/4UxWg/
var patternCanvas = document.createElement('canvas');
var pattern_ctx = patternCanvas.getContext("2d");
patternCanvas.width = 1;
patternCanvas.height = 2;
pattern_ctx.fillRect(0,0,1,1);
var mainCanvas = document.createElement('canvas');
var ctx = mainCanvas.getContext("2d");
mainCanvas.height = 500;
mainCanvas.width = 400;
document.getElementsByTagName("body")[0].appendChild(mainCanvas);
//scale function - remove this line to see how the pattern should look like
ctx.scale(5,5);
var pattern = ctx.createPattern(patternCanvas, "repeat");
ctx.fillStyle= pattern;
ctx.fillRect(2,2,40,40); //fillRect looks wierd and pattern is no longer 1px by 1px
thanks for any help!
Update I read background as the global background behind all the shapes. But it seem to be the fill of the shapes if I now understand it correctly - in that case you need to do things slightly different:
To keep filled pattern unaffected by scaled dimensions of the shapes you will need to manually scale the shapes -
Either:
var scale = 5;
ctx.fillRect(x * scale, y * scale, w * scale, h * scale);
or use a wrapper function:
function fillRectS(x, y, w, h) {
ctx.fillRect(x * scale, y * scale, w * scale, h * scale);
}
fillRectS(x, y, w, h);
For stroke you just do the same:
function lineWidthS(w) {
ctx.lineWidth = w * scale;
}
and so forth (lines, moveTo, arc). This will allow you to scale all shapes but keep pattern fill at 1:1 ratio. The overhead will be minimal.
Old answer -
There are at least two ways around this:
1) You can reset the translation when you fill the pattern and then re-apply transformations right after.
/// reset
ctx.transform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = pattern;
ctx.fillRect(x, y, w, h);
ctx.scale(sx, sy);
2) Layer the canvases so that the background is drawn separately on one canvas and use the top canvas to draw in anything else that needs to be drawn in scale.
Different scales and transformations used simultaneously is one good case scenario to use layered canvas.
HTML:
<div id="container">
<canvas id="bg" ... ></canvas>
<canvas id="main" ... ></canvas>
</div>
CSS:
#container {
position:relative;
}
#container > canvas {
position:absolute;
left:0;
top:0;
}
Then get context for each and draw in as needed (pseudo-ish):
var bgCtx = bg.getContext('2d');
var ctx = main.getContext('2d');
... other setup code here ...
/// will only affect foreground canvas
ctx.scale(5, 5);
/// will only fill background canvas
bgCtx.fillStyle = pattern;
bgCtx.fillRect(x, y, w, h);
Now these won't affect each other.
The returned CanvasPattern has a setTransform method. In this way you can avoid having to use a separate canvas as a pattern. Just scale the pattern in the opposite direction.
For posterity, I wrote a Context2D wrapper that automatically does this and adds some tracking/debugging features to help you draw. It doesn't actually sacrifice any performance!
You can find it here: https://github.com/LemonPi/Context2DTracked
Or if you're using npm (with webpack or something) npm install context-2d-tracked

Categories