簡介
既然都做解題程式了,乾脆連出題程式也作一下吧
命名
Eimorhc是Chromie的倒字。(本人極愛倒字)
Chromie是解題者,出題者當然是要倒過來囉。
實作細節
Canvas圖形變換
- 第一個碰到的問題是要旋轉指針…而且要旋轉兩根。網路上有不少使用canvas實現時鐘的作品,不過我大致看了一下決定自己用canvas刻。
參考網頁
一樣是MDN的Canvas教學
Canvas的save/restore
在這邊卡了一下,
以我的理解restore跟save要成對使用,是用來區隔不同座標變換組合的起點跟終點。
以下用畫這個時鐘的函式當範例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| renderClock: function(){
var canvas = this.element.find('#canvas canvas')[0], self = this;
var ctx = canvas.getContext("2d");
var options = self.constructor.defaults.canvas;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.arc(options.x,options.y,options.C,0,Math.PI*2,true); // 外圈
ctx.fillStyle = 'black';
ctx.font= 'bold 18px sans-serif';
ctx.fill();
$.each(self.puzzle, function(index, value){
var slice = index/self.puzzle.length;
if(self.available[index] && !self.steped[index])
{
ctx.beginPath();
ctx.fillStyle = "#0186d1";
ctx.strokeStyle = "#0186d1";
ctx.arc(options.x+options.r*Math.sin(2*Math.PI*slice), options.y-options.r*Math.cos(2*Math.PI*slice), 10, 0, Math.PI*2, true);
ctx.fill();
ctx.closePath();
}
if(!self.steped[index])
{
ctx.fillStyle = self.constructor.defaults.color[value];
ctx.fillText(value, options.x-6+options.r*Math.sin(2*Math.PI*slice), options.y+6-options.r*Math.cos(2*Math.PI*slice));
}
});
ctx.save();
|
到這裡為止是畫時鐘大圓跟刻度,不需要用到座標變換。
這裡將畫布作第一次乾淨的存檔。
也就是說,平移跟旋轉量都是0。
1
2
3
4
| ctx.translate(options.x,options.y);
ctx.rotate(2*Math.PI*((this.vars.plus+this.vars.angle)/this.puzzle.length));
ctx.drawImage(this.constructor.img,-4,-options.y/2+5,7,37);
ctx.restore();
|
開始畫第一根指針,需要旋轉angle+plus
角度。
畫完之後就呼叫restore()
回到旋轉量是0的時候。
這裡還要再存一次乾淨的狀態給後面的畫布用。
因為剛剛save過得狀態已經被restore用掉了!
1
2
3
4
5
6
| ctx.translate(options.x,options.y);
ctx.rotate(2*Math.PI*((this.vars.angle+this.vars.minus)/this.puzzle.length));
ctx.drawImage(this.constructor.img,-4,-options.y/2+5,7,37);
ctx.restore();
ctx.save();
}
|
全部做完。
利用$.animate的動畫來控制Canvas
這邊參考了http://acko.net/blog/abusing-jquery-animate-for-fun-and-profit-and-bacon/, 主要的技巧是
1. 對$.fx進行改寫,讓$.animate可以接受自訂的屬性
1
2
3
4
5
6
| var $_fx_step_default = $.fx.step._default;
$.fx.step._default = function (fx) {
if (!fx.elem.customAnimate) return $_fx_step_default(fx);
fx.elem[fx.prop] = fx.now;
fx.elem.updated = true;
};
|
2. 利用一個中介div DOM,讓$.animate去動態改變自訂屬性。
1
2
3
4
5
6
7
8
| this.vars = $.extend($('<div>')[0], {
angle: 0,
plus: 0,
minus: 0,
end: false,
customAnimate: true,
updated: true
});
|
3. Canvas的繪圖函式中,直接拿中介DOM的屬性來繪圖。
1
| ctx.rotate(2*Math.PI*((this.vars.plus+this.vars.angle)/this.puzzle.length));
|
4. 任何時候想要讓Canvas有動畫只要直接對中介DIV作animate
1
2
3
4
5
| $(self.vars).animate({ angle: index, plus: 0, minus: 0 }, { duration: 2000}).queue(function(){
$(this).dequeue();
}).animate({ angle: index, plus: value, minus: -value }, { duration: 2000}).queue(function(){
$(this).dequeue();
});
|
5.建立一個定時函數檢查update的值
1
2
3
4
5
| setInterval(function(){
if (!self.vars.updated) return;
self.vars.updated = false;
self.renderClock();
}, 30);
|
Screenshot & Github demo pages
http://alivedise.github.com/Eimorhc/