[FF13-2] Eimorhc - Timeline Puzzle Creator 以及一些心得

- - posted in javascriptmvc, jquery | Comments

簡介

既然都做解題程式了,乾脆連出題程式也作一下吧

命名

Eimorhc是Chromie的倒字。(本人極愛倒字) Chromie是解題者,出題者當然是要倒過來囉。

實作細節

Canvas圖形變換

  1. 第一個碰到的問題是要旋轉指針…而且要旋轉兩根。網路上有不少使用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的時候。

1
   ctx.save();

這裡還要再存一次乾淨的狀態給後面的畫布用。 因為剛剛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/

Comments