Related
I'm trying to join two separate bezier curves into one continuous curve. Currently, what I have looks like this:
The problem is that they aren't joined, so the points at which they meet look pointy/sharp instead of curvy and smooth. I've looked into documentation for joining bezier curves in P5.js, but am unsure of how to translate this into HTML5 Canvas. How do I join these two bezier curves so that they look like one smooth and continuous curve?
This is my code:
const canvas = document.getElementById('canvas');
const c = canvas.getContext("2d");
width = 800;
height = 500;
canvas.width = width;
canvas.height = height;
let face;
let centerX = width / 2;
let centerY = height / 3;
setup();
function setup() {
c.clearRect(0, 0, canvas.width, canvas.height);
face = new Face();
draw();
};
function draw() {
setBackground(`rgba(250, 250, 250, 1)`);
c.beginPath();
c.moveTo(centerX - face.hsx, centerY + face.hsy);
c.bezierCurveTo(centerX - face.hcp1x / 10, centerY - face.hsy2,
centerX + face.hcp1x / 10, centerY - face.hsy2,
centerX + face.hsx, centerY + face.hsy);
c.moveTo(centerX - face.hsx, centerY + face.hsy);
c.bezierCurveTo(centerX - face.hcp1x, centerY + face.hcp1y,
centerX + face.hcp1x, centerY + face.hcp1y,
centerX + face.hsx, centerY + face.hsy);
c.stroke();
c.fillStyle = (`rgba(25, 250, 211, 0)`);
c.fill();
}
function setBackground(color) {
c.fillStyle = color;
c.fillRect(0, 0, width, height);
}
function Face() {
this.hsx = 150;
this.hsy = 0;
this.hsy2 = 120;
this.hcp1x = 120;
this.hcp1y = 250;
}
Common tangent
To join two beziers smoothly you need to make the lines from the common point parallel thus defining the tangent at the end and start of the two beziers to be the same.
The following image illustrates this
The line that is defined by the two control points (C2, C1) and the common point (P) is the tangent of the curve at P. The length of the line segments have no constraints.
How?
There are dozens of ways to do this and how you do it is dependent on the requirements of the curve, the type of curve, and much more.
Example
I am not going to give a full example as it requires an understanding of vector maths and a cover all solution on the assumption you are not familiar with vector maths would be huge.
Thus the most basic pseudo code example uses the previous control and end points to calculate the next control point. ? represents unknowns which are not bound by constraints required to keep the lines parallel
// From illustration in answer
corner = ? // Distance to next control point as fraction of distance
// from previous control point
C2 = {x:?, y:?} // Last control point of previous bezier
P = {x:?, y:?} // Start of next bezier
C1 = { // Next control point along line from previous and scaled
x: P.x + (P.x - C2.x) * corner,
y: P.y + (P.y - C2.y) * corner,
}
// two beziers with common point P
ctx.bezierCurveTo(?,?, C2.x, C2.y, P.x, P.y)
ctx.bezierCurveTo(C1.x, C1.y, ?, ?, ?, ?)
In the below page:
https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_canvas_beziercurveto
You change the width and height of the canvas to 1000.
Then you replace the two lines between beginpath and stroke with the below code.
points=[
{x:0, y:300},//0
{x:100,y:500},//1
{x:200,y:300},//2
{x:300,y:100},//3
{x:400,y:300},//4
{x:100,y:500},//5
{x:100,y:300},//6
];
ctx.rect(points[0].x-5, points[0].y-5, 10,10);
var smoother={};
smoother.x=((points[1].x-points[0].x)/10)+points[0].x;
smoother.y=((points[1].y-points[0].y)/10)+points[0].y;
ctx.rect(smoother.x-5, smoother.y-5, 10,10);
ctx.rect(points[1].x-5, points[1].y-5, 10,10);
ctx.rect(points[2].x-5, points[2].y-5, 10,10);
ctx.moveTo(points[0].x,points[0].y);
ctx.bezierCurveTo(
smoother.x, smoother.y,
points[1].x, points[1].y,
points[2].x, points[2].y
);
var smoother={};
var dx=(points[2].x-points[1].x);
var dy=(points[2].y-points[1].y);
var yperx=(dy/dx);
travel_x=dx;
travel_y=(dx*yperx);
smoother.x=points[2].x+travel_x/3;
smoother.y=points[2].y+travel_y/3;
ctx.rect(smoother.x-5, smoother.y-5, 10,10);
ctx.rect(points[3].x-5, points[3].y-5, 10,10);
ctx.rect(points[4].x-5, points[4].y-5, 10,10);
ctx.moveTo(points[2].x,points[2].y);
ctx.bezierCurveTo(
smoother.x, smoother.y,
points[3].x, points[3].y,
points[4].x, points[4].y
);
var smoother={};
var dx=(points[4].x-points[3].x);
var dy=(points[4].y-points[3].y);
var yperx=(dy/dx);
travel_x=dx;
travel_y=(dx*yperx);
smoother.x=points[4].x+travel_x/3;
smoother.y=points[4].y+travel_y/3;
ctx.rect(smoother.x-5, smoother.y-5, 10,10);
ctx.rect(points[5].x-5, points[5].y-5, 10,10);
ctx.rect(points[6].x-5, points[6].y-5, 10,10);
ctx.moveTo(points[4].x,points[4].y);
ctx.bezierCurveTo(
smoother.x, smoother.y,
points[5].x, points[5].y,
points[6].x, points[6].y
);
You can also run it here by pressing the run button:
https://www.w3schools.com/code/tryit.asp?filename=GSP1RKBFHGGK
At that you can manipulate the pixels in points[], and notice that the bezier curves always connect kinda smoothly.
That's because in each new bezier curve, the system automatically makes the first bezier point, which only serves the role of smoothing the line. Which is basically just a point that continues in whatever direction the previous bezier was heading, for a little bit. The next pixel in the bezier is then an actual destination, which the given bezier curve then takes care of smoothing.
There is the number 3 in there, it represents how quickly you want to start going in the actual direction. If it's too large we start to too quickly head in the needed direction and the smoothness suffers.
If it's too small we are ignoring too much where the line needs to be going, in favor of smoothness.
I was trying to get the green triangle to rotate about its center and orient itself towards the mouse position. I was able to accomplish this, and you can view the full code and result here:
https://codepen.io/Carpetfizz/project/editor/DQbEVe
Consider the following lines of code:
r = Math.atan2(mouseY - centerY, mouseX - centerX)
ctx.rotate(r + Math.PI/2)
I arbitrarily added Math.PI/2 to my angle calculation because without it, the rotations seemed to be 90 degrees off (by inspection). I want a better understanding of the coordinate system which atan2 is being calculated with respect to so I can justify the reason for offsetting the angle by 90 degrees (and hopefully simplify the code).
EDIT:
To my understanding, Math.atan2 is measuring the angle illustrated in blue. Shouldn't rotating both triangles that blue angle orient it towards the mouse mouse pointer (orange dot) ? Well - obviously not since it's the same angle and they are two different orientations, but I cannot seem to prove this to myself.
This is because of how the Math.atan2 works.
From MDN:
This is the counterclockwise angle, measured in radians, between the positive X axis, and the point (x, y).
In above figure, the positive X axis is the horizontal segment going from the junction to the right-most position.
To make it clearer, here is an interactive version of this diagram, where x, y values are converted to [-1 ~ 1] values.
const ctx = canvas.getContext('2d'),
w = canvas.width,
h = canvas.height,
radius = 0.3;
ctx.textAlign = 'center';
canvas.onmousemove = canvas.onclick = e => {
// offset mouse values so they are relative to the center of our canvas
draw(as(e.offsetX), as(e.offsetY));
}
draw(0, 0);
function draw(x, y) {
clear();
drawCross();
drawLineToPoint(x, y);
drawPoint(x, y);
const angle = Math.atan2(y, x);
drawAngle(angle);
writeAngle(angle);
}
function clear() {
ctx.clearRect(0, 0, w, h);
}
function drawCross() {
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(s(0), s(-1));
ctx.lineTo(s(0), s(1));
ctx.moveTo(s(-1), s(0));
ctx.lineTo(s(0), s(0));
ctx.strokeStyle = ctx.fillStyle = '#2e404f';
ctx.stroke();
// positive X axis
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(s(0), s(0));
ctx.lineTo(s(1), s(0));
ctx.stroke();
ctx.lineWidth = 1;
ctx.font = '20px/1 sans-serif';
ctx.fillText('+X', s(1) - 20, s(0) - 10);
}
function drawPoint(x, y) {
ctx.beginPath();
ctx.arc(s(x), s(y), 10, 0, Math.PI * 2);
ctx.fillStyle = 'red';
ctx.fill();
ctx.font = '12px/1 sans-serif';
ctx.fillText(`x: ${x.toFixed(2)} y: ${y.toFixed(2)}`, s(x), s(y) - 15);
}
function drawLineToPoint(x, y) {
ctx.beginPath();
ctx.moveTo(s(0), s(0));
ctx.lineTo(s(x), s(y));
ctx.strokeStyle = 'red';
ctx.setLineDash([5, 5]);
ctx.stroke();
ctx.setLineDash([0]);
}
function drawAngle(angle) {
ctx.beginPath();
ctx.moveTo(s(radius), s(0));
ctx.arc(s(0), s(0), radius * w / 2,
0, // 'arc' method also starts from positive X axis (3 o'clock)
angle,
true // Math.atan2 returns the anti-clockwise angle
);
ctx.strokeStyle = ctx.fillStyle = 'blue';
ctx.stroke();
ctx.font = '20px/1 sans-serif';
ctx.fillText('∂: ' + angle.toFixed(2), s(0), s(0));
}
// below methods will add the w / 2 offset
// because canvas coords set 0, 0 at top-left corner
// converts from [-1 ~ 1] to px
function s(value) {
return value * w / 2 + (w / 2);
}
// converts from px to [-1 ~ 1]
function as(value) {
return (value - w / 2) / (w / 2);
}
<canvas id="canvas" width="500" height="500"></canvas>
So now, if we go back to your image, it currently points to the top (positive Y axis), while the angle you just measured is realtive to the x axis, so it doesn't point where you intended.
Now we know the problem, the solution is quite easy:
either apply the + Math.PI / 2 offset to your angle like you did,
either modify your original image so that it points to the positive X axis directly.
The coordinate system on canvas works with 0° pointing right. This means anything you want to point "up" must be initially drawn right.
All you need to do in this case is to change this drawing:
to
pointing "up" 0°
and you can strip the math back to what you'd expect it to be.
var ctx = c.getContext("2d"), img = new Image;
img.onload = go; img.src = "https://i.stack.imgur.com/Yj9DU.jpg";
function draw(pos) {
var cx = c.width>>1,
cy = c.height>>1,
angle = Math.atan2(pos.y - cy, pos.x - cx);
ctx.setTransform(1,0,0,1,cx, cy);
ctx.rotate(angle);
ctx.drawImage(img, -img.width>>1, -img.height>>1);
}
function go() {
ctx.globalCompositeOperation = "copy";
window.onmousemove = function(e) {draw({x: e.clientX, y: e.clientY})}
}
html, body {margin:0;background:#ccc}
#c {background:#fff}
<canvas id=c width=600 height=600></canvas>
When you do arctangents in math class, you're generally dealing with an y-axis that increases going upwards. In most computer graphics systems, however, including canvas graphics, y increases going downward. [erroneous statement deleted]
Edit: I have to admit what I wrote before was wrong for two reasons:
A change in the direction of the axis would be compensated for by adding π, not π/2.
The canvas context rotate function rotates clockwise for positive angles, and that alone should compensate for the flip of the y-axis.
I played around with a copy of your code in Plunker, and now I realize the 90° rotation simply compensates for the starting orientation of the graphic image you're drawing. If the arrowhead pointed right to start with, instead of straight up, you wouldn't need to add π/2.
I encountered the same problem and was able to achieve the desired result with a following axis 'trick':
// Default usage (works fine if your image / shape points to the RIGHT)
let angle = Math.atan2(delta_y, delta_x);
// 'Tricky' usage (works fine if your image / shape points to the LEFT)
let angle = Math.atan2(delta_y, -delta_x);
// 'Tricky' usage (works fine if your image / shape points to the BOTTOM)
let angle = Math.atan2(delta_x, delta_y);
// 'Tricky' usage (works fine if your image / shape points to the TOP)
let angle = Math.atan2(delta_x, -delta_y);
I'm finishing a project, but I have one more step to finish.
I want to visualize microphone input by a canvas.
Getting the data from the microphone isn't a problem.
But I want to visualize it in a special way. (see image)
I want to animate each element from the wave.
My problem isn't the animation.
My problem is to create those shapes in the CANVAS.
This is an example of one shape:
I can create a rounded corner shape with the canvas
const draw = () => {
fillRoundedRect(20, 20, 100, 100, 20);
ctx.fillStyle = "red";
ctx.fill();
};
const fillRoundedRect = (x, y, w, h, r) => {
ctx.beginPath();
ctx.moveTo(x+r, y);
ctx.lineTo(x+w-r, y);
ctx.quadraticCurveTo(x+w, y, x+w, y+r);
ctx.lineTo(x+w, y+h-r);
ctx.quadraticCurveTo(x+w, y+h, x+w-r, y+h);
ctx.lineTo(x+r, y+h);
ctx.quadraticCurveTo(x, y+h, x, y+h-r);
ctx.lineTo(x, y+r);
ctx.quadraticCurveTo(x, y, x+r, y);
ctx.fill();
};
Can someone help me with creating a shape like in the second image?
Thanks in advance guys!
Instead of trying to make a single shape with dependency on surrounding shapes and a high risk of headache math-wise, use instead two shapes which you merge using composition. My suggestion anyways.
Draw all the bars in full height using composition mode source-over (default)
Define a single shape on top using some sort of spline (I would suggest a cardinal spline).
Set composition mode to destination-out and render an enclosed shape using the spline as top "line".
Example
This should work in a loop (remember to clear canvas for each frame) but shows only the building stones needed here -
var ctx = c.getContext("2d");
var points = [];
var skippy = 0;
// render all bars
ctx.globalCompositeOperation = "source-over"; // not needed here, but in a loop yes!
// produce bars
ctx.beginPath(); // not needed here, but in a loop yes!
for(var x = 0; x < c.width; x += 30) {
ctx.rect(x, 0, 16, c.height)
// OKIDOKI, lets produce the spline using random points (y) as well
// but not for all, only every second for prettyness... modify to taste
if (skippy++ % 2 === 0) points.push(x, c.height * Math.random());
}
points.push(c.width, c.height * Math.random()); // one last
ctx.fillStyle = "rgb(198, 198, 198)";
ctx.fill();
// render spline
ctx.beginPath();
ctx.moveTo(0, c.height); // bottom left corner
curve(ctx, points); // spline
ctx.lineTo(c.width, c.height); // bottom right corner
ctx.closePath();
ctx.globalCompositeOperation = "destination-out";
ctx.fill();
I want to achive the following:
Draw a bg-image to the canvas (once or if needed repeatedly)
The image should not be visible at the beginning
While i "paint" shapes to the canvas the bg-image should get visible where the shapes were drawn
The parts of the image that will be revealed shall be "painted" (like with a brush) so i want to use strokes.
What i tried:
- Do not clear the canvas
- Paint rects to the canvas with globalCompositeOperation = 'destination-in'
This works, the rectangles reveal the image but i need strokes
If i use strokes they are ignored with 'destination-in' while i see them with normal globalCompositeOperation.
Is this intended that the strokes are ignored? Is there a workaround like somehow converting the stroke/shape to a bitmap? Or do i have have to use two canvas elements?
In OpenGL i would first draw the image with its rgb values and with a = 0 and then only "paint" the alpha in.
You can solve it by these steps:
Set the image as a pattern
Set the pattern as fillStyle or strokeStyle
When you now fill/stroke your shapes the image will be revealed. Just make sure the initial image fits the area you want to reveal.
Example showing the principle, you should be able to adopt this to your needs:
var ctx = canvas.getContext("2d"),
img = new Image,
radius = 40;
img.onload = setup;
img.src = "http://i.imgur.com/bnAEEXq.jpg";
function setup() {
// set image as pattern for fillStyle
ctx.fillStyle = ctx.createPattern(this, "no-repeat");
// for demo only, reveals image while mousing over canvas
canvas.onmousemove = function(e) {
var r = this.getBoundingClientRect(),
x = e.clientX - r.left,
y = e.clientY - r.top;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.arc(x, y, radius, 0, 2*Math.PI);
ctx.fill();
};
}
<canvas id=canvas width=900 height=600></canvas>
Hope this helps!
Alternative solution:
Put the image as a normal image on your website
add a canvas and use CSS positioning to place it right above the image
Fill the canvas with the color you use as the page background
have your paint tools erase the canvas when you draw. By the way, you can set context.globalCompositionOperation = 'destination-out' to turn all drawing operations into an eraser.
Here is an example. As you can see, the alpha properties of your tools are respected.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//prepare canvas
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, 120, 120);
//prepare a 30% opacity eraser
ctx.globalCompositeOperation = 'destination-out';
ctx.lineWidth = 5;
ctx.strokeStyle = 'rgba(0, 0, 0, 0.3)';
// make random strokes around cursor while mouse moves
canvas.onmousemove = function(e) {
var rect = this.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
ctx.beginPath();
ctx.moveTo(x + Math.random() * 33 - 16, y + Math.random() * 33 - 16);
ctx.lineTo(x + Math.random() * 33 - 16, y + Math.random() * 33 - 16);
ctx.stroke();
}
<span>Move your mouse:</span>
<div>
<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/HTML5_logo_and_wordmark.svg/120px-HTML5_logo_and_wordmark.svg.png' style='position:absolute'>
<canvas id='canvas' width=120 height=120 style='position:absolute'></canvas>
</div>
The final code that worked for me was:
<canvas id="bg-admin-canvas" width="500" height="500" style="margin:15px; background:#09F;"></canvas>
<script>
var postit = function(width,height,angle){
var canvas = document.getElementById("bg-admin-canvas");
var ctx = canvas.getContext("2d");
var radians = angle * Math.PI / 180;
var move = width*Math.sin(radians);
if(angle < 0 ){ ctx.translate(0,-move); }else{ ctx.translate(move,0); }
ctx.rotate(radians);
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0.05,"rgba(0,0,0,0)");
gradient.addColorStop(0.5,"rgba(0,0,0,0.3)");
ctx.fillStyle = gradient;
ctx.fillRect(0,0,width,height);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(width, 0);
ctx.lineTo(width,height);
ctx.lineTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.closePath();
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0,'#f7f8b9');
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
ctx.beginPath();
ctx.moveTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.quadraticCurveTo(width*.05,height-height*.05,width*.1,height-height*.1);
ctx.quadraticCurveTo(width*.1,height-height*.07,width-width*.8,height-height*.02);
ctx.closePath();
ctx.fillStyle = '#ffffff';
ctx.fill();
var gradient = ctx.createLinearGradient(0,height,width*.1,height-height*.1);
gradient.addColorStop(0,"rgba(222,222,163,0.8)");
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
}
postit(300, 300, 10);
</script>
Hi,
I made a quick and dirty "post-it" note with html5's canvas and some js.
I want to be able to rotate them anyway I want so I tried to use the translate. The example below I have a translate of 0,250 just so you could see the whole thing.
Ideally, I know if my canvas was 300,300 then I would
ctx.translate(150,150);
ctx.rotate(-30);
ctx.translate(-150,-150);
Of course since I'm rotating a square it gets cut off.
How would I rotate the square and move it on the canvas so the whole thing is showing but at the very top left edge of the canvas?
I added an image with my thinking of just getting the height of a triangle and moving it that much, but when translated, it doesn't seem to work just right.
I'll paste my whole function so you can look at it, but if you have any ideas, I would appreciate it. This isn't important, just messing around today.
var postit = function(width,height,angle){
var canvas = jQuery("#bg-admin-canvas").get(0);
var ctx = canvas.getContext("2d");
/*var area = (width*width*Math.sin(angle))/2;
var h = (area*2) / width + 30;
ctx.translate(0,h);
*/
//ctx.translate(150,150);
ctx.translate(0,250);
ctx.rotate(angle*Math.PI / 180);
//ctx.translate(-150,-150);
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0.05,"rgba(0,0,0,0)");
gradient.addColorStop(0.5,"rgba(0,0,0,0.3)");
ctx.fillStyle = gradient;
ctx.fillRect(0,0,width,height);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(width, 0);
ctx.lineTo(width,height);
ctx.lineTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.closePath();
var gradient = ctx.createLinearGradient(0,height,width/2,height/2);
gradient.addColorStop(0,'#f7f8b9');
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
ctx.beginPath();
ctx.moveTo(width-width*.8,height-height*.02);
ctx.quadraticCurveTo(0+width*.02,height-height*.02,0+width*.02,(height - height*.2));
ctx.quadraticCurveTo(width*.05,height-height*.05,width*.1,height-height*.1);
ctx.quadraticCurveTo(width*.1,height-height*.07,width-width*.8,height-height*.02);
ctx.closePath();
ctx.fillStyle = '#ffffff';
ctx.fill();
var gradient = ctx.createLinearGradient(0,height,width*.1,height-height*.1);
gradient.addColorStop(0,"rgba(222,222,163,0.8)");
gradient.addColorStop(1,'#feffcf');
ctx.fillStyle = gradient;
ctx.fill();
}
postit(300, 300, -35);
MORE INFO
Phrog, I think you know what I'm trying to do. This image shows what I want to do:
Now, the only thing is, I want to be able to pass in any width and height and angle and make the adjustment on the fly.
As an example with the following code:
var canvas = document.getElementById("bg-admin-canvas");
var ctx = canvas.getContext("2d");
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.translate(50, 50);
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.translate(-25, -25);
ctx.arc(0,0,3,0,360,true); ctx.fill();
I get the following image:
Now, if I add a rotate in there like this:
var canvas = document.getElementById("bg-admin-canvas");
var ctx = canvas.getContext("2d");
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.translate(50, 50);
ctx.arc(0,0,3,0,360,true); ctx.fill();
ctx.rotate(30*Math.PI/180);
ctx.translate(-25, -25);
ctx.arc(0,0,3,0,360,true); ctx.fill();
I now have a sloped coordinates as the result is:
As I found, this is because the coordinates are no longer horizontal and vertical.
So, with this rotated coordinate structure, I can't figure out how to move my square (which could be any size and rotated at any angle) back to the left and top (so it fits in as little space as possible)
Does that make sense?
In short:
Translate the context in the Y direction only to put the corner where it should be.
Rotate the context around this offset point.
Draw your object at 0,0.
Here is an interactive, working example, which you can see online here:
http://phrogz.net/tmp/canvas_rotate_square_in_corner.html
<!DOCTYPE HTML>
<html lang="en"><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>HTML5 Canvas Rotate Square in Corner</title>
<style type="text/css" media="screen">
body { background:#eee; margin:2em; text-align:center }
canvas { display:block; margin:auto; background:#fff; border:1px solid #ccc }
</style>
</head><body>
<canvas width="250" height="200"></canvas>
<script type="text/javascript" charset="utf-8">
var can = document.getElementsByTagName('canvas')[0];
var ctx = can.getContext('2d');
ctx.strokeStyle = '#600'; ctx.lineWidth = 2; ctx.lineJoin = 'round';
ctx.fillStyle = '#ff0'
document.body.onmousemove = function(evt){
var w=140, h=120;
var angle = evt ? (evt.pageX - can.offsetLeft)/100 : 0;
angle = Math.max(Math.min(Math.PI/2,angle),0);
ctx.clearRect(0,0,can.width,can.height); ctx.beginPath();
ctx.save();
ctx.translate(1,w*Math.sin(angle)+1);
ctx.rotate(-angle);
ctx.fillRect(0,0,w,h);
ctx.strokeRect(0,0,w,h);
ctx.restore();
};
document.body.onmousemove();
</script>
</body></html>
Analysis
In the above diagram, point A is the upper-left corner of our post-it note and point B is the upper-right corner. We have rotated the post-it note -a radians from the normal angle (clockwise rotations are positive, counter-clockwise are negative).
We can see that the point A stays on the y axis as the post-it rotates, so we only need to calculate how far down the y axis to move it. This distance is expressed in the diagram as BD. From trigonometry we know that
sin(a) = BD / AB
Rearranging this formula gives us
BD = AB * sin(a)
We know that AB is the width of our post-it note. A few details:
Because our angle will be expressed as a negative number, and the sin of a negative number yields a negative result, but because we want a positive result, we must either negate the result
BD = -AB * sin(-a)
or just 'cheat' and use a positive angle:
BD = AB * sin(a)
We need to remember to translate our context before we rotate it, so that we first move directly down the axis to establish our origin at the right spot.
Remember that rotations in HTML5 Canvas use radians (not degrees). If you want to rotate by 20 degrees, you need to convert that to radians by multiplying by Math.PI/180:
ctx.rotate( 20*Math.PI/180 );
This also applies to the arc command; you should be doing ctx.arc(x,y,r,0,Math.PI*2,false); for a full circle.
You should create you canvas element and then rotate it using CSS. It would keep your canvas intact and only rotate the element itself.
Here is some example css rules:
-webkit-transform: rotate(-30deg);
-moz-transform: rotate(-30deg);
Refer to http://snook.ca/archives/html_and_css/css-text-rotation