Анализа на кодот на Skycons

jsВо овој блог пост следи анализа на кодот на Skycons. Тоа е мала JavaScript библиотека која црта анимирани икони за временските состојби, користејќи го HTML5 <canvas> тагот. Се користи во одличниот сервис Forecast.

Кога ќе завршите со читање ќе знаете како се пишува една JS библиотека (притоа не jQuery или node/AMD/CommonJS библиотека, туку нај нај `нормална` библиотека), ќе знаете која и е структурата и може ќе научите и некоја финтица попат :)

Како се користи

Првин да видиме пример како се користи библиотеката:

Дефинираме канвас:

<canvas id="icon1" width="128" height="128"></canvas>

Ја вклучуваме библиотеката во HTML-от. И потоа во JavaScript кодот создаваме инстанца од Skycons

var icons = new Skycons();

и дефинираме која временска состојба да се исцрта за кој канвас и повикуваме нејзино анимирање.

icons.set("icon1", Skycons.RAIN);
icons.play();
Пример: различните икони кои ги нуди, притоа и во различни големини
Пример: различните икони кои ги нуди, притоа и во различни големини

Анализа

Пред да почнеме да го анализираме кодот, може да приметиме дека нуди методи кои се пристапуваат `дирекно` од библиотеката (пр. .RAIN) и методи кои се пристапуваат од инстанцата (пр. .set и .play). Друго за напомена, верзијата на библиотеката која е дел од овој блог пост е со id cec079f77b (нормално е да има и понови/подобрени верзии од библиотеката во иднина). Да почнеме:

Кодот започнува со декларирање на варијабла наречена Skycons

var Skycons;

и притоа не се прави ништо повеќе со неа во овој момент, нема иницијализација. Повеќе за варијабли тука.

Следно, целиот преостанат код се наоѓа во овој блок

(function(global) {

}(this));

и тука ја користиме и варијаблата Skycons. Овој блок се нарекува анонимна самоповикувачка функција, која во нашиот слчај е со аргумент – референца до глобална променлива. Доколку експлицитно не дефинираме – сите функции и варијабли во неа се приватни и нема да може да се пристапат од надвор. Повеќе за анонимни самоповикувачки функции тука, тука и тука.

Во оваа функција прво нешто е изразот “use strict”, што значи дека целата функција се извршува под ‘strict mode’.

"use strict";

Оваа декларација мора да биде на самиот почеток на функцијата. Повеќе за овој мод на работа тука.

Следно се декларираат две варијабли

var requestInterval, cancelInterval;

и после нив има уште една анонимна самоповикувачка функција, во која двете варијабли се употребуваат

(function() {
  var raf = global.requestAnimationFrame       ||
            global.webkitRequestAnimationFrame ||
            global.mozRequestAnimationFrame    ||
            global.oRequestAnimationFrame      ||
            global.msRequestAnimationFrame     ,
      caf = global.cancelAnimationFrame        ||
            global.webkitCancelAnimationFrame  ||
            global.mozCancelAnimationFrame     ||
            global.oCancelAnimationFrame       ||
            global.msCancelAnimationFrame      ;

  if(raf && caf) {
    requestInterval = function(fn, delay) {
      var handle = {value: null};

      function loop() {
        handle.value = raf(loop);
        fn();
      }

      loop();
      return handle;
    };

    cancelInterval = function(handle) {
      caf(handle.value);
    };
  }

  else {
    requestInterval = setInterval;
    cancelInterval = clearInterval;
  }
}());

Во функцијава се дефинираат рамките за анимација и за сопирање на анимацијата и се дефинирани на тој начин што би работеле на сите интернет пребарувачи кои ја подржуваат оваа функционалност. Ова се нарекува RequestAnimationFrame shim, повеќе информации има тука и тука.

Следно има код во коментар (скокаме коментари!) и после него се дефинираат неколку константи (кои како и претходните две варијабли, не се достапни надвор од библиотеката пример кога е инстанцирана – бидејќи се во анонимна самоповикувачка функција).

var KEYFRAME = 500,
    STROKE = 0.08,
    TWO_PI = 2.0 * Math.PI,
    TWO_OVER_SQRT_2 = 2.0 / Math.sqrt(2);

После константите, следуваат функциите за цртање. Функциите се дефинирани на тој начин што може да се ре-искористат за повеќе икони наеднаш. Пример во сите случаи кога иконата за времето има облаче, се повикува истата функција за цртање на тој облак. Да ги разгледаме…

Првата е за цртање на круг.

function circle(ctx, x, y, r) {
  ctx.beginPath();
  ctx.arc(x, y, r, 0, TWO_PI, false);
  ctx.fill();
}

Како параметри се праќаат контекстот, точките x, y и радиусот r. ctx е контекстот земен од канвас елементот и тој нуди методи за цртање најразлични геометриски форми и преку него се цртаат иконите. Но, ако цел код е повици кон ctx ќе има многу повторување и нема да биде читлив, па затоа се овие помошни функции. Повеќе за цртање форми во канвас тука и тука.

Следната функција е за цртање на линија.

function line(ctx, ax, ay, bx, by) {
  ctx.beginPath();
  ctx.moveTo(ax, ay);
  ctx.lineTo(bx, by);
  ctx.stroke();
}

Јасно е дека преку канвасот се дефинира линија од една до друга точка и се исцртува.

Нема да ги опишувам сите овие помошни методи, само ќе ги излистам, можете во библиотеката да видите кои повици кон кои методи од контекстот се искористени.

function puff(ctx, t, cx, cy, rx, ry, rmin, rmax) {...}
function puffs(ctx, t, cx, cy, rx, ry, rmin, rmax) {...}
function cloud(ctx, t, cx, cy, cw, s, color) {...}
function sun(ctx, t, cx, cy, cw, s, color) {...}
function moon(ctx, t, cx, cy, cw, s, color) {...}
function rain(ctx, t, cx, cy, cw, s, color) {...}
function sleet(ctx, t, cx, cy, cw, s, color) {...}
function snow(ctx, t, cx, cy, cw, s, color) {...}
function fogbank(ctx, t, cx, cy, cw, s, color) {...}

Потоа има помошни матрици со броеви за помош на цртањето на анимацијата на ветрот

var WIND_PATHS = [ ...
    ],
    WIND_OFFSETS = [ ...
    ];

И уште две помошни функции

function leaf(ctx, t, x, y, cw, s, color) {...}
function swoosh(ctx, t, cx, cy, cw, s, index, total, color) {...}

Сега почнува интересното.

Skycons = function(opts) {
  this.list     = [];
  this.interval = null;
  this.color    = opts && opts.color ? opts.color : "black";
};

Оваа функција се повикува кога во горниот пример беше извршено new Skyicons(). Опционално може и бојата да се дефинира, пример new Skyicons({"color" : "blue"}). Полето list е листата во која се додаваат канвас елементите при повикување на .add и .set и полето interval се користи во .play и .pause. Повеќе за овие функции кога ќе им дојде редот.

Следни се функциите за временските состојби.

Skycons.CLEAR_DAY = function(ctx, t, color) {
  var w = ctx.canvas.width,
      h = ctx.canvas.height,
      s = Math.min(w, h);

  sun(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
};

Во оваа функција се пресметуваат неколку варијабли во зависност од големината на канвасот, и се повикува помошната функција sun.

Следна функција е

Skycons.CLEAR_NIGHT = function(ctx, t, color) {
  var w = ctx.canvas.width,
      h = ctx.canvas.height,
      s = Math.min(w, h);

  moon(ctx, t, w * 0.5, h * 0.5, s, s * STROKE, color);
};

која е слична на претходната, но на крај ја повикува помошната функција moon.

Во овој стил, следат функциите

Skycons.CLOUDY = function(ctx, t, color) { ... };
Skycons.RAIN = function(ctx, t, color) { ... };
Skycons.SLEET = function(ctx, t, color) { ... };
Skycons.SNOW = function(ctx, t, color) { ... };
Skycons.WIND = function(ctx, t, color) { ... };
Skycons.FOG = function(ctx, t, color) { ... };

Функциите кои се достапни преку инстанцата на Skyicons (add, set, draw, …) се на ред. Следно во кодот е дефиниран блокот

  Skycons.prototype = {
  };

И во него прва е функцијата add

add: function(el, draw) {
  var obj;

  if(typeof el === "string")
    el = document.getElementById(el);

  obj = {
    element: el,
    context: el.getContext("2d"),
    drawing: draw
  };

  this.list.push(obj);
  this.draw(obj, KEYFRAME);
},

Пред да објаснам што прави функцијата, да размислиме зошто оваа е достапна преку инстанцата на Skycons. Тоа е бидејќи е дефинирана преку prototype. Повеќе за prototypes тука и тука.

Во функцијата се креира празна варијабла obj, со getElementById се зема канвас елементот по името (пр. icon1, повеќе инфо тука), се создава објектот, се додава во листата и се повикува за него draw.

Многу важно тука е назначувањето на draw за полето drawing во создавањето на објектот. Да го земеме примерот icons.add(“icon1″, Skycons.CLEAR_DAY); – во obj за полето drawing ја назначуваме функцијата CLEAR_DAY. Во JavaScript можеме функции да праќаме како аргументи на методи и да ги назначуваме на полиња. Повеќе за тоа тука.

Следна е функцијата set

set: function(el, draw) {
  var i;

  if(typeof el === "string")
    el = document.getElementById(el);

  for(i = this.list.length; i--; )
    if(this.list[i].element === el) {
      this.list[i].drawing = draw;
      this.draw(this.list[i], KEYFRAME);
      return;
    }

  this.add(el, draw);
},

Која го прави истото како add, само ако веќе постои објект со такво име на канвасот – го заменува во листата.

Следна функција е remove

remove: function(el) {
  var i;

  if(typeof el === "string")
    el = document.getElementById(el);

  for(i = this.list.length; i--; )
    if(this.list[i].element === el) {
      this.list.splice(i, 1);
      return;
    }
},

Која отстранува објект од листата.

Потоа е функцијата draw

draw: function(obj, time) {
  var canvas = obj.context.canvas;

  obj.context.globalCompositeOperation = 'destination-out';
  obj.context.fillRect(0, 0, canvas.width, canvas.height);
  obj.context.globalCompositeOperation = 'source-over';

  obj.drawing(obj.context, time, this.color);
},

Која се повикува во play функцијата, и како параметри прима објект од листата и време. Првин го зема канвасот, го чисти, и за објектот го повикува методот drawing. А drawing е впрочем еден од методите за времињата, како RAIN, CLOUDY, итн… такада еден од тие методи се извршува соодветно.

Следна е функцијата play

play: function() {
  var self = this;

  this.pause();
  this.interval = requestInterval(function() {
    var now = Date.now(),
        i;

    for(i = self.list.length; i--; )
      self.draw(self.list[i], now);
  }, 1000 / 60);
},

Во неа првин се забележува интересното var self = this; – оваа варијабла self ни помага да чуваме референца до this во случаи кога може контекстот на извршување на this да се менува. Повеќе за тоа тука. Потоа повикуваме pause – функција која ќе ја објасниме следна, и потоа повикуваме requestInterval и во секоја итерација ги итерираме сите објекти во листата и повикуваме исцртување – така ја добиваме анимираноста.

И последната функција, pause

pause: function() {
  var i;

  if(this.interval) {
    cancelInterval(this.interval);
    this.interval = null;
  }
}

Во неа, доколку interval е некогаш дефиниран (што значи доколку е повикано претходно play) се повикува cancelInterval и interval се поставува на null.

Крај на кодот. Структурата на библиотеката графички преставено изгледа вака:

Преглед на структурата на Skycons
Преглед на структурата на Skycons

Сега уште еден коментар. Зошто некои методи се достапни преку Skycons дирекно, а некои преку инстанцата? Затоа што можеме да имаме повеќе икони на една иста веб страна, и не сакаме сите да бидат со исти бои, или некои сакаме да се анимирани а некои статички. На овој начин тоа е возможно.

И тоа е тоа. Доколку имате сугестии, поправки, идеи, надополнувања или било каков коментар – бујрум!

Напишете коментар

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Промени )

Twitter слика

You are commenting using your Twitter account. Log Out / Промени )

Facebook photo

You are commenting using your Facebook account. Log Out / Промени )

Google+ photo

You are commenting using your Google+ account. Log Out / Промени )

Connecting to %s