JavaScript How can I rotate a segment of <canvas>, not the entire element?

I'm trying to learn some of the <canvas> API right now. I've tasked myself with creating a simple analog style clock with working clock hands (second, minute and hour).

The clock frame, face and hands are all drawn with the same canvas element. I've created a drawScene() function which runs every second and redraws the entire clock. If you want to look more in-depth at the code I will post it into a jsbin linked at the bottom of this post.

The goal is for the drawScene() method to call the drawClockFace() method, which passes the current second / minute / hour to individual functions that draw the hand based on the passed in time (ie drawSecondHand(currentSecond)).

question:

how do I rotate individual components of a canvas (ie the second hand on my clock) without rotating the entire canvas? I know I need to calculate where to draw the line from the center origin out, based on what the current second is. I'm just not sure the gemoetric calculation needed to determined "where" to draw the line.

Here's what I have so far -- note it's not super clean because I've been putzing with it. http://codepen.io/tconroy/pen/BcEbf

Answer:1

How do I rotate individual components of a canvas (ie the second hand on my clock) without rotating the entire canvas?

You can use the following approach to calculate the angles manually without rotating the canvas each time. The demos below produces a smooth running clock utilizing milliseconds (I'll show below how to drop this if not wanted).

Clock snapshot

Initially you would want to rotate the canvas -90 degrees to have 0 degrees point up and not to the right. This makes life easier in regards to the angles which will produce for example 0 degree for 12 o'clock. You can do this rotation after drawing the main face.

For each hand:

  • Get their angles based on time (milliseconds included in this example for smooth animation)
  • Render the lines based on angles

That's it.

Demo

At the core you can have a function which calculates current time into angles for the hour, minutes and seconds - this function will also get the smooth angles for "in-between" based on milliseconds (doesn't need to wait a whole second to change):

/// somewhere globally
var pi2 = Math.PI * 2;

function timeToAngles() {

    var os = 1 / 60,                  /// mini-step
        time = new Date(),            /// get current time
        h = time.getHours(),          /// get current hour
        m = time.getMinutes(),        /// get current minutes
        s = time.getSeconds(),        /// get current seconds
        ms = time.getMilliseconds(),  /// get current milliseconds
        sa, ma, ha;                   /// for calc. angles

    sa = pi2 * ((s / 60) + (os * ms * 0.001));         /// second's angle
    ma = pi2 * ((m / 60) + (os * s / 60));             /// minute's angle
    ha = pi2 * (((h % 12) / 12) + (( 1 / 12) * m / 60)); /// hour's angle

    return {
        h: ha,
        m: ma,
        s: sa
    }
}

Now it's simply a matter of feeding these angles to your render function:

(function loop() {
    renderClock()
    requestAnimationFrame(loop);
})();

If you want to update only per second replace rAF with (you could also remove the calculation from the timeToAngles() but it will be microscopic in this context):

(function loop() {
    setTimeout(loop, 1000);
    renderClock()
})();

Then render lines based on the angles you get from current time:

function renderClock() {

    var angles = timeToAngles(),     /// get angles
        cx = ctx.canvas.width * 0.5, /// center
        cy = ctx.canvas.width * 0.5,
        lh = cx * 0.5,               /// length of hour's hand
        lm = cx * 0.8,               /// length of minute's hand
        ls = cx * 0.9,               /// length of second' hand
        pos;                         /// end-point of hand

    /// clear and render face
    ctx.clearRect(0, 0, cx*2, cy*2);
    ctx.beginPath();
    ctx.arc(cx, cy, cx - ctx.lineWidth, 0, pi2);

    /// hours
    pos = lineToAngle(cx, cy, lh, angles.h);
    ctx.moveTo(cx, cy);
    ctx.lineTo(pos.x, pos.y);

    /// minutes
    pos = lineToAngle(cx, cy, lm, angles.m);
    ctx.moveTo(cx, cy);
    ctx.lineTo(pos.x, pos.y);

    /// render hours and minutes
    ctx.lineWidth = 5;
    ctx.stroke();
    ctx.beginPath();

    /// seconds
    pos = lineToAngle(cx, cy, ls, angles.s);
    ctx.moveTo(cx, cy);
    ctx.lineTo(pos.x, pos.y);

    ctx.lineWidth = 2;  /// create a variation for seconds hand
    ctx.stroke();
}

This helper function calculates the end-point based on angle and trigonometry:

function lineToAngle(x, y, length, angle) {
    return {
        x: x + length * Math.cos(angle),
        y: y + length * Math.sin(angle)
    }
}

Additional tip for rendering the clock face:

If you are making a clock then this tip can be very helpful - instead of clearing and rendering the face each update you can instead render the face once, convert the canvas to a data-uri and set that as a background image to canvas (itself).

This way you only need to redraw the hands. Before rotating the canvas -90 degrees (as shown above):

Demo

Clock with face as self-rendered background

var dots = 12,          /// generate dots for each hour
    dotPos = cx * 0.85, /// position of dots
    step = pi2 / dots,  /// calc step
    a = 0;              /// angle for loop

ctx.beginPath();

/// create body border
ctx.beginPath();
ctx.arc(cx, cy, cx - ctx.lineWidth - 2, 0, pi2);
ctx.fillStyle = '#000';
ctx.lineWidth = 5;
ctx.stroke();

/// color of hour dots
ctx.fillStyle = '#999';

/// draw the dots    
for(; a < pi2; a += step) {
    var pos = lineToAngle(cx, cy, dotPos, a);
    ctx.beginPath();
    ctx.arc(pos.x, pos.y, 3, 0, pi2);
    ctx.fill();
}

/// create highlighted dots for every 3 hours
a = 0;
step = pi2 / 4;

ctx.fillStyle = '#777';    
for(; a < pi2; a += step) {
    var pos = lineToAngle(cx, cy, dotPos, a);
    ctx.beginPath();
    ctx.arc(pos.x, pos.y, 5, 0, pi2);
    ctx.fill();
}

/// set as background
clock.style.backgroundImage = 'url(' + clock.toDataURL() + ')';

Then start the loop here.

Answer:2

For complex functions declared within a loop, I can see why I wouldn't want to do this, but why would it be be considered bad javascript? We can name the function and place it outside the loop of ...

For complex functions declared within a loop, I can see why I wouldn't want to do this, but why would it be be considered bad javascript? We can name the function and place it outside the loop of ...

  1. functions declared within loops referencing an outer
  2. functions declared within loops referencing
  3. functions declared within loops referencing an outer scoped
  4. functions declared within loops
  5. creating functions within loops
  6. functions within a loop
  7. jshint functions declared within loops
  8. jshint functions declared within loops referencing
  9. javascript functions declared within loops

When I hover the element with the class .notice, tooltip shows me blue. When I hover it again, then it shows me whatever Ajax is returning. <script> $(".notice").tooltip(); ...

When I hover the element with the class .notice, tooltip shows me blue. When I hover it again, then it shows me whatever Ajax is returning. <script> $(".notice").tooltip(); ...

  1. value attribute html
  2. value attribute html tags
  3. value attribute mdn
  4. value attribute javascript
  5. value attribute table arcgis
  6. value attribute radio button html
  7. value attribute jquery
  8. value attribute button
  9. value attribute on div
  10. value attribute checkbox
  11. value attribute in select tag
  12. value attribute in option tag
  13. value attribute in textarea
  14. value attribute in form tag
  15. value attribute in anchor tag
  16. value attribute in a tag
  17. value attribute of input tag in html
  18. value attribute in angular
  19. value attribute css
  20. value attribute in ordered list

Is there a way in a chrome extension to know if a tab is currently playing an audio content (from Flash, <audio>, <video>, Web Audio API or any other way (Aka, when the "speaker" is in the ...

Is there a way in a chrome extension to know if a tab is currently playing an audio content (from Flash, <audio>, <video>, Web Audio API or any other way (Aka, when the "speaker" is in the ...

I am trying to work with owl slider. I have gone through the documentation but I am unable to figure out how to loop through owl slider single slides. I mean they work fine with pagination but with ...

I am trying to work with owl slider. I have gone through the documentation but I am unable to figure out how to loop through owl slider single slides. I mean they work fine with pagination but with ...