简体   繁体   English

如何旋转 <canvas> ,不是整个元素?

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

I'm trying to learn some of the <canvas> API right now. 我正在尝试学习一些<canvas> API。 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. 我创建了一个drawScene()函数,该函数每秒运行一次并重绘整个时钟。 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. 如果您想更深入地查看代码,我会将其发布到本文底部链接的jsbin中。

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) ). 我们的目标是使drawScene()方法调用drawClockFace()方法,该方法将当前的秒/分钟/小时传递给各个函数,这些函数根据传递的时间来绘制指针(即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 http://codepen.io/tconroy/pen/BcEbf

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). 下面的演示使用毫秒产生一个平稳运行的时钟(如果不需要,我将在下面显示如何删除它)。

时钟快照

Initially you would want to rotate the canvas -90 degrees to have 0 degrees point up and not to the right. 最初,您希望将画布旋转-90度,以使0度指向上方而不是向右。 This makes life easier in regards to the angles which will produce for example 0 degree for 12 o'clock. 就角度而言,这会使生活变得更轻松,例如,在12点钟时将产生0度。 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): 如果您只想每秒更新一次,则将rAF替换为(也可以从timeToAngles()删除计算,但在这种情况下它将是微观的):

(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). 如果您要进行时钟设定,那么此提示可能会非常有用-无需清除和渲染人脸,每次更新时,您可以将人脸渲染一次,将画布转换为data-uri并将其设置为画布的背景图片(本身)。

This way you only need to redraw the hands. 这样,您只需要重画手即可。 Before rotating the canvas -90 degrees (as shown above): 在将画布旋转-90度之前(如上所示):

Demo 演示

时钟与脸作为自我渲染的背景

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. 然后在这里开始循环。

You can use context.save & context.restore to temporarily rotate part of your clock (like the second hand) 您可以使用context.save和context.restore来临时旋转时钟的一部分(例如秒针)

The idea is: 这个想法是:

  • save the unrotated context: context.save(); 保存未旋转的上下文:context.save();
  • rotate to the desired angle: context.rotate(angle); 旋转到所需的角度:context.rotate(angle);
  • restore the context to its unrotated state for further drawing: context.restore(); 将上下文恢复为其未旋转状态以进行进一步绘制:context.restore();

A Demo: http://jsfiddle.net/m1erickson/PjZz3/ 演示: http : //jsfiddle.net/m1erickson/PjZz3/

在此处输入图片说明

Example code: 示例代码:

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" />
<script src="http://code.jquery.com/jquery.min.js"></script>

<style>
    body{ background-color: white; }
    canvas{border:1px solid red;}
</style>

<script>
    $(function(){

        var canvas=document.getElementById("canvas");
        var ctx=canvas.getContext("2d");
        ctx.strokeStyle="black"
        ctx.fillStyle="ivory";

        function animate() {
                requestAnimFrame(animate);

                // clear the canvas
                ctx.clearRect(0,0,canvas.width,canvas.height);

                // Draw the clock face
                ctx.beginPath();
                ctx.arc(150,150,50,0,Math.PI*2);
                ctx.closePath();
                ctx.lineWidth=3;
                ctx.stroke();
                ctx.fillStyle="ivory";
                ctx.fill();
                ctx.fillStyle="black";
                ctx.fillText("12",145,115);

                // Separately rotate the second hand 
                // using ctx.save and ctx.restore
                // plus ctx.rotate

                // calc the rotation angle
                var d=new Date();
                var radianAngle=(d.getSeconds()/60)*Math.PI*2;
                ctx.save();
                ctx.translate(150,150);
                ctx.rotate(radianAngle);
                ctx.moveTo(0,0);
                ctx.lineTo(45,0);
                ctx.stroke();
                ctx.restore();
        }

animate();        

    }); // end $(function(){});
</script>

</head>

<body>
    <canvas id="canvas" width=350 height=350></canvas>
</body>
</html>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM