Canvas 寫的酷炫動畫代碼分析

霓虹燈線形成的自發(fā)六邊形,隨機性生成火花

在看這篇文章時,里面有個動畫的示例(如上圖),然后感覺有點很酷炫,就打算了解一下怎么寫的笛坦。( 先上代碼示例鏈接

前景提要

需要先確保你還記得三角函數(shù)的知識。
對 Canvas 的 API 有點了解苔巨,且稍微了解其中的 globalCompositeOperation API (不了解的話弯屈,可以看下這個文章,基本可以有點感覺)恋拷。

代碼分析

盡我所能,我盡量在代碼里關(guān)鍵地方都增加了注釋厅缺。(可能有些描述表達(dá)不夠好蔬顾,請見諒~)

   var w = (c.width = window.innerWidth),
      h = (c.height = window.innerHeight),
      ctx = c.getContext("2d"),
      //一些配置項
      opts = {
        len: 20, //線長
        count: 50, //線總數(shù)
        baseTime: 10, //線停留基礎(chǔ)時間
        addedTime: 10, //線額外停留時間
        dieChance: 0.05, //線重置的概率
        spawnChance: 1, //線生成的概率
        sparkChance: 0.1, //火花生成的概率
        sparkDist: 10, //火花距離線的距離
        sparkSize: 2, //火花大小

        color: "hsl(hue,100%,light%)", //hsl() 函數(shù)使用色相宴偿、飽和度、亮度來定義顏色诀豁。
        baseLight: 50, //基礎(chǔ)的顏色亮度
        addedLight: 10, // [50-10,50+10]
        shadowToTimePropMult: 6, //陰影的模糊級別
        baseLightInputMultiplier: 0.01, //基礎(chǔ)亮度
        addedLightInputMultiplier: 0.02, //額外亮度

        cx: w / 2,
        cy: h / 2,
        repaintAlpha: 0.04,
        hueChange: 0.1,
      },
      tick = 0, //控制顏色色相
      lines = [],
      dieX = w / 2 / opts.len,
      dieY = h / 2 / opts.len,
      baseRad = (Math.PI * 2) / 6;

    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, w, h);

    function loop() {
      //瀏覽器下次重繪前調(diào)用該方法
      window.requestAnimationFrame(loop);
      //循環(huán)過程中更改生成的霓虹燈顏色色相
      ++tick;

      /*  目標(biāo)圖像 = 已經(jīng)放置在畫布上的繪圖窄刘。
  源圖像 = 打算放置到畫布上的繪圖。 */
      ctx.globalCompositeOperation = "source-over"; //目標(biāo)圖像上顯示源圖像
      ctx.shadowBlur = 0;
      ctx.fillStyle = "rgba(0,0,0,alp)".replace("alp", opts.repaintAlpha);
      ctx.fillRect(0, 0, w, h);
      ctx.globalCompositeOperation = "lighter"; //顯示源圖像 + 目標(biāo)圖像(重疊圖形的顏色是通過顏色值相加來確定)

      //保持生成的霓虹燈線共有 count 個
      if (lines.length < opts.count && Math.random() < opts.spawnChance)
        lines.push(new Line());

      lines.map(function (line) {
        line.step();
      });
    }
    function Line() {
      //生成霓虹燈線時進(jìn)行初始化
      this.reset();
    }
    //初始化舷胜,重置
    Line.prototype.reset = function () {
      this.x = 0;
      this.y = 0;
      this.addedX = 0;
      this.addedY = 0;

      this.rad = 0;
      //亮度
      this.lightInputMultiplier =
        opts.baseLightInputMultiplier +
        opts.addedLightInputMultiplier * Math.random();

      this.color = opts.color.replace("hue", tick * opts.hueChange);
      this.cumulativeTime = 0; //累計的時間

      this.beginPhase();
    };
    //霓虹燈線每一步的開始前規(guī)劃階段
    Line.prototype.beginPhase = function () {
      this.x += this.addedX;
      this.y += this.addedY;

      this.time = 0;
      //霓虹燈線每一步的停留時間
      this.targetTime = (opts.baseTime + opts.addedTime * Math.random()) | 0;
      //隨機六邊形路線方向
      this.rad += baseRad * (Math.random() < 0.5 ? 1 : -1);
      this.addedX = Math.cos(this.rad);
      this.addedY = Math.sin(this.rad);
      //霓虹燈線消失重置的條件
      if (
        Math.random() < opts.dieChance ||
        this.x > dieX ||
        this.x < -dieX ||
        this.y > dieY ||
        this.y < -dieY
      )
        this.reset();
    };
    //行走一步
    Line.prototype.step = function () {
      ++this.time;
      ++this.cumulativeTime;
      //超過行走時間娩践,規(guī)劃下一步
      if (this.time >= this.targetTime) this.beginPhase();

      var prop = this.time / this.targetTime,
        wave = Math.sin((prop * Math.PI) / 2), //sin90°=1
        x = this.addedX * wave, //cos(R)=b/c
        y = this.addedY * wave; //sin(R)=a/c

      ctx.shadowBlur = prop * opts.shadowToTimePropMult; //陰影的模糊級別
      //模糊和填充的顏色
      ctx.fillStyle = ctx.shadowColor = this.color.replace(
        "light",
        opts.baseLight +
          opts.addedLight *
            Math.sin(this.cumulativeTime * this.lightInputMultiplier)
      );

      //繪制霓虹燈線
      ctx.fillRect(
        opts.cx + (this.x + x) * opts.len,
        opts.cy + (this.y + y) * opts.len,
        2,
        2
      );
      //隨機生成火花
      if (Math.random() < opts.sparkChance)
        ctx.fillRect(
          opts.cx +
            (this.x + x) * opts.len +
            Math.random() * opts.sparkDist * (Math.random() < 0.5 ? 1 : -1) -
            opts.sparkSize / 2,
          opts.cy +
            (this.y + y) * opts.len +
            Math.random() * opts.sparkDist * (Math.random() < 0.5 ? 1 : -1) -
            opts.sparkSize / 2,
          opts.sparkSize,
          opts.sparkSize
        );
    };
    loop();

    //監(jiān)聽瀏覽器窗口調(diào)整,重置
    window.addEventListener("resize", function () {
      w = c.width = window.innerWidth;
      h = c.height = window.innerHeight;
      ctx.fillStyle = "black";
      ctx.fillRect(0, 0, w, h);

      opts.cx = w / 2;
      opts.cy = h / 2;

      dieX = w / 2 / opts.len;
      dieY = h / 2 / opts.len;
    });

簡單來描述下,上面的主要代碼:

  • 每一次瀏覽器重繪前都調(diào)用 loop() 函數(shù)烹骨。
  • 在 loop() 函數(shù)里翻伺,保持共有 count 個實例化的 Line 。
  • 在實例化時沮焕,調(diào)用 reset() 函數(shù)進(jìn)行一些屬性的初始化吨岭。
  • 在初始化完成后,調(diào)用 beginPhase() 函數(shù)進(jìn)行下一步繪制的路線規(guī)劃峦树。(其中霓虹燈線觸發(fā)重置條件時辣辫,調(diào)用 reset() 函數(shù),進(jìn)行屬性的數(shù)值化)
  • 回到 loop() 函數(shù)魁巩,遍歷每一個示例 Line 急灭,調(diào)用 step() 函數(shù),進(jìn)行繪制霓虹燈線和線周圍的火花谷遂。(其中超過每一步規(guī)定的停留時間后葬馋,調(diào)用 beginPhase() 函數(shù),規(guī)劃下一步埋凯。)
問題

這個動畫点楼,讓我一開始感覺到厲害的地方是,霓虹燈線行走的尾部白对,有個漸漸的變暗淡的過程掠廓。所以,讓人感覺這個動畫甩恼,就很酷炫蟀瞧。
而這個是怎么做的呢?我上面描述刻意沒有講到条摸≡梦郏可以看下代碼,思考下钉蒲,思路感覺挺微妙的切端。(我是重新看了下代碼才明白的)

答案

關(guān)鍵點就在于 loop() 函數(shù)里的這兩行代碼。

   ctx.fillStyle = "rgba(0,0,0,alp)".replace("alp", opts.repaintAlpha);
   ctx.fillRect(0, 0, w, h);

通過每一次的層層疊加上有一定透明度的黑色顷啼,從而達(dá)到了后面尾巴逐漸消滅的效果踏枣。(如果你開始一眼就發(fā)現(xiàn)了昌屉,打擾了,獻(xiàn)丑了)

最后

酷炫的 Canvas 從來沒有寫過茵瀑,也沒接觸過间驮。這次試著分析這個酷炫動畫代碼,算是對如何用 Canvas 畫動畫有了點感覺了吧马昨。
另外竞帽,雖然看懂了代碼,但似乎不是知道 Canvas 怎么畫動畫就能寫出這個效果的鸿捧,總感覺里面似乎蘊涵了一些數(shù)學(xué)功底~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末屹篓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子笛谦,更是在濱河造成了極大的恐慌抱虐,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饥脑,死亡現(xiàn)場離奇詭異恳邀,居然都是意外死亡,警方通過查閱死者的電腦和手機灶轰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門谣沸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人笋颤,你說我怎么就攤上這事乳附。” “怎么了伴澄?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵赋除,是天一觀的道長。 經(jīng)常有香客問我非凌,道長举农,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任敞嗡,我火速辦了婚禮颁糟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘喉悴。我一直安慰自己棱貌,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布箕肃。 她就那樣靜靜地躺著婚脱,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上起惕,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天涡贱,我揣著相機與錄音,去河邊找鬼惹想。 笑死,一個胖子當(dāng)著我的面吹牛督函,可吹牛的內(nèi)容都是我干的嘀粱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼辰狡,長吁一口氣:“原來是場噩夢啊……” “哼锋叨!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起宛篇,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤娃磺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后叫倍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體偷卧,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年吆倦,在試婚紗的時候發(fā)現(xiàn)自己被綠了听诸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蚕泽,死狀恐怖晌梨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情须妻,我是刑警寧澤仔蝌,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站荒吏,受9級特大地震影響敛惊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜司倚,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一豆混、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧动知,春花似錦皿伺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春妒穴,著一層夾襖步出監(jiān)牢的瞬間宋税,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工讼油, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留杰赛,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓矮台,卻偏偏與公主長得像乏屯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瘦赫,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 代碼下載:Github視頻地址:慕課·canvas小球倒計時 二辰晕、倒計時數(shù)字展示 2.1、靜態(tài)數(shù)字展示 如果想用一...
    可愛多小姐閱讀 271評論 0 1
  • 一,介紹與需求 1.1,介紹 canvas是HTML5中新增一個HTML5標(biāo)簽與操作canvas的javascri...
    長布閱讀 442評論 0 0
  • 1 Canvas接口元素定義 1.1 getContext()方法 為了在canvas上繪制确虱,你必須先得到一個畫布...
    Kevin_Junbaozi閱讀 1,304評論 1 2
  • 一含友、簡介 是一個可以使用腳本(通常為JavaScript)來繪制圖形的 HTML 元素.例如,它可以用于繪制圖表、...
    Adoins閱讀 2,192評論 0 2
  • canvas元素的基礎(chǔ)知識 在頁面上放置一個canvas元素校辩,就相當(dāng)于在頁面上放置了一塊畫布窘问,可以在其中進(jìn)行圖形的...
    oWSQo閱讀 10,290評論 0 19