如何旋转<canvas>的一部分,而不是整个元素?</canvas>

时间:2014-02-02 17:19:53

标签: javascript css html5 canvas rotation

我正在尝试学习一些<canvas> API。我的任务是创建一个简单的模拟式时钟,工作时钟指针(秒,分钟和小时)。

时钟框架,面部和指针都使用相同的画布元素绘制。我创建了一个drawScene()函数,它每秒运行一次并重绘整个时钟。如果你想更深入地看一下代码,我会将它发布到这篇文章底部链接的jsbin中。

目标是drawScene()方法调用drawClockFace()方法,该方法将当前秒/分钟/小时传递给根据传入时间绘制手的各个函数(即{{{ 1}})。

问题

如何在不旋转整个画布的情况下旋转画布的各个组件(即我的时钟上的秒针)?我知道我需要根据当前秒数来计算从中心原点绘制线的位置。我只是不确定需要确定“在哪里”画线的宝石计算。

这是我到目前为止所做的 - 请注意它并不是超级干净,因为我一直在使用它。 http://codepen.io/tconroy/pen/BcEbf

2 个答案:

答案 0 :(得分:5)

  

如何旋转画布的各个组件(即秒针   在我的时钟上)没有旋转整个画布?

您可以使用以下方法手动计算角度,而无需每次都旋转画布。下面的演示使用毫秒产生一个平滑的运行时钟(如果不需要,我将在下面显示如何删除它。)

Clock snapshot

最初你想要将画布旋转-90度,使其向上0度,而不是向右。这使得关于将产生例如12度的0度的角度的生活更容易。绘制主面后,可以执行此旋转。

每只手:

  • 根据时间获取角度(此示例中包含的平滑动画的毫秒数)
  • 根据角度渲染线条

就是这样。

Demo

在核心你可以有一个函数来计算小时,分钟和秒的角度的当前时间 - 这个函数也将获得基于毫秒的“中间”的平滑角度(不需要等待一个整秒改变):

/// 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
    }
}

现在只需将这些角度提供给渲染功能:

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

如果你想每秒更新替换rAF(你也可以从timeToAngles()删除计算,但在这种情况下它将是微观的):

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

然后根据您从当前时间获得的角度渲染线条:

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

此辅助函数根据角度和三角函数计算终点:

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

渲染钟面的其他提示:

如果你正在制作一个时钟,那么这个提示可能非常有用 - 而不是每次更新清除和渲染面部,而是可以渲染面部一次,将画布转换为数据uri并将其设置为背景图像画布(本身)。

这样你只需要重绘双手。在旋转画布-90度之前(如上图所示):

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() + ')';

然后在这里开始循环。

答案 1 :(得分:1)

你可以使用context.save&amp; context.restore暂时旋转部分时钟(如秒针)

这个想法是:

  • 保存未旋转的上下文:context.save();
  • 旋转到所需的角度:context.rotate(angle);
  • 将上下文恢复到其未旋转状态以进一步绘制:context.restore();

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

enter image description here

示例代码:

<!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>