Search code examples
javascripthtmlcanvas

Rotate 'note' on the canvas to always touch the upper left corner


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

Postit from HTML5 Canvas


MORE INFO

Phrog, I think you know what I'm trying to do. This image shows what I want to do: enter image description here 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: enter image description here 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: enter image description here

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?


Solution

  • 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

    Diagram showing a rectangle rotated counter-clockwise, with corner A on the Y axis at 0,12 and corner B on the X axis at 20,0; construction D is at 20,12, and the angle DAB is labeled with a lowercase 'a'

    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:

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

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

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