canvas入門——用canvas製做倒計時項目總結

項目演示git

此項目是慕課網上的視頻,這篇文章本身的學習總結。github

點陣

canvas中繪製數字,可使用點陣的方式。canvas

7.png

下面是5的點陣佈局,1表明要繪製,0表明不要繪製。這裏數字採用的是10 * 7的網格系統,冒號是10 * 4的網格系統。數組

[
  [1, 1, 1, 1, 1, 1, 1],
  [1, 1, 0, 0, 0, 0, 0],
  [1, 1, 0, 0, 0, 0, 0],
  [1, 1, 1, 1, 1, 1, 0],
  [0, 0, 0, 0, 0, 1, 1],
  [0, 0, 0, 0, 0, 1, 1],
  [0, 0, 0, 0, 0, 1, 1],
  [0, 0, 0, 0, 0, 1, 1],
  [1, 1, 0, 0, 0, 1, 1],
  [0, 1, 1, 1, 1, 1, 0]
]

有了這個這個網格的系統,咱們就能將數字繪製出來了。dom

數字繪製

canvas中如何用圓將數字繪製出來呢?,圓心的x軸座標,y座標如何肯定?函數

ctx.arc(
  x + j * 2 * (radius + 1) + (radius + 1),
  y + i * 2 * (radius + 1) + (radius + 1),
  radius,
  0,
  2 * Math.PI
)

假設圓的半徑爲R佈局

  • 圓的半徑爲R,包圍盒的方框爲R+1,因此每一個方框的邊長就是2*(R+1)
  • 假設點陣頂點爲(x,y)i表示行,j表示列
  • 因此第(i,j)位置的座標是:學習

    • x軸座標: x + j*2*(R+1)+(R+1)
    • y軸座標: y + i*2*(R+1)+(R+1)

這裏面x + j*2*(R+1) 是繪製到包圍盒的前面,在加上(R+1),使得繪製的點在圓心。優化

如何肯定第二個數的位置

第二個數字的起始位置怎麼肯定?動畫

marginLeft加上第一個數字的位置。咱們的數字點陣是7列,一個數字的佔的空間就是14*(R+1),爲了在兩個數字間空出點的距離,因此就用了15

這要注意第三個位置是冒號,而咱們冒號使用的是10 * 4的點陣系統,因此在繪製第四個數字時,加上的是9而不是15

renderDigit(marginLeft, marginTop, parseInt(hour / 10), ctx);
renderDigit(marginLeft + 15 * (radius + 1), marginTop, parseInt(hour % 10), ctx);
renderDigit(marginLeft + 30 * (radius + 1), marginTop, 10, ctx);
renderDigit(marginLeft + 39 * (radius + 1), marginTop, parseInt(minutes / 10), ctx);
renderDigit(marginLeft + 54 * (radius + 1), marginTop, parseInt(minutes % 10), ctx);
renderDigit(marginLeft + 69 * (radius + 1), marginTop, 10, ctx);
renderDigit(marginLeft + 78 * (radius + 1), marginTop, parseInt(seconds / 10), ctx);
renderDigit(marginLeft + 93 * (radius + 1), marginTop, parseInt(seconds % 10), ctx);

更新數字

怎麼樣在屏幕上顯示出時間在動的效果呢?

  1. 在屏幕上顯示倒計時,獲取下一次的時間nextShowTimeSeconds
  2. 比較當前的時間和下一次時間是否相同(只需比較 秒 便可)
  3. 若是nextSeconds不等於curSeconds,說明時間變化了,須要更新當前時間。
let nextShowTimeSeconds = getCurrentShowTimeSeconds();

  let nextHour = parseInt(nextShowTimeSeconds / 3600);
  let nextMinutes = parseInt((nextShowTimeSeconds - nextHour * 3600) / 60);
  let nextSeconds = nextShowTimeSeconds % 60;

  let curHour = parseInt(curShowTimeSeconds / 3600);
  let curMinutes = parseInt((curShowTimeSeconds - curHour * 3600) / 60);
  let curSeconds = curShowTimeSeconds % 60;

  if (nextSeconds !== curSeconds) {
    curShowTimeSeconds = nextShowTimeSeconds;
  }

繪製多彩小球

時間已經動起來了,在時間發生變化的時候,如何讓在時間的位置出現多彩小球呢?

在時間放生變化的時候,讓多彩小球出如今頁面中。

這裏就須要對每一個時間都進行判斷,在時間發生變化的時候,調用addBall函數

if (parseInt(curHour / 10) !== parseInt(nextHour / 10)) {
  addBall(marginLeft, marginTop, parseInt(curHour / 10));
}
if (parseInt(curHour % 10) !== parseInt(nextHour % 10)) {
  addBall(marginLeft + 15 * (radius + 1), marginTop, parseInt(curHour % 10));
}
if (parseInt(curMinutes / 10) !== parseInt(nextMinutes / 10)) {
  addBall(marginLeft + 39 * (radius + 1), marginTop, parseInt(curMinutes / 10));
}
if (parseInt(curMinutes % 10) !== parseInt(nextMinutes % 10)) {
  addBall(marginLeft + 54 * (radius + 1), marginTop,parseInt(curMinutes % 10));
}
if (parseInt(curSeconds / 10) !== parseInt(nextSeconds / 10)) {
  addBall(marginLeft + 78 * (radius + 1), marginTop, parseInt(curSeconds / 10));
}
if (parseInt(curSeconds % 10) !== parseInt(nextSeconds % 10)) {
  addBall(marginLeft + 93 * (radius + 1), marginTop, parseInt(curSeconds % 10));
}

addBall

addBall函數是對小球在指定位置進行渲染

addBall函數接收三個參數,小球的x座標,y座標以及數字。

在此遍歷全部數字,小球若是是1就須要再該位置繪製,並添加小球balls裏。

function addBall(x, y, num) {
  digit[num].forEach((element, i) => {
    element.forEach((item, j) => {
      let aBall = {};
      if (item === 1) {
        aBall = {
          x: x + j * 2 * (radius + 1) + (radius + 1),
          y: y + i * 2 * (radius + 1) + (radius + 1),
          g: 1.5 + Math.random(), //加速度
          vx: Math.pow(-1, Math.ceil(Math.random() * 1000)) * 4, //前面一段是取 -1 仍是 1
          vy: -5,
          color: colors[Math.floor(Math.random() * colors.length)]
        };
      }
      balls.push(aBall);
    });
  });
}

aball是要繪製在屏幕的小球,它接收7個參數:

  • x: x座標
  • y: Y座標
  • r: 半徑
  • g: 加速度
  • vx: x軸速度
  • vy: y軸速度
  • color: 顏色

這裏重點說下gvxvy這三個屬性的做用,這裏用到的實際上是初中物理知識——速度和加速度。

ball = {x:512, y:100, r:20, g:1.5, vx:-4, vy:0, color:'#058'}

更新小球

小球繪製完以後,須要讓小球有個落地的效果

對小球的數組balls進行比遍歷,讓每一個小球的xy座標分別加上在本身軸上的運動速度,y軸速度vy要加上小球的加速度。這樣就有一個小球落地的效果了。

balls.forEach(ball => {
  ball.x += ball.vx;
  ball.y += ball.vy;
  ball.vy += ball.g;
});

落地檢測

仍是在balls遍歷的數組裏面。

如何知道小球有沒落地呢?

檢測小球的y軸座標,若是小球y軸座標大於等於 屏幕的高度減去小球半徑,就說明小球已經觸底了。須要有一個回彈的效果。

回彈的時候受空氣阻力的影響有的阻力系數。

更新小球的y座標和y軸的速度,小球就會有一個回彈的效果。

if (ball.y >= windowHeight - radius) {
  ball.y = windowHeight - radius;
  ball.vy = -ball.vy * 0.6;
}

這個程序行運行一段時間後,會發現特別卡頓。這是由於咱們一直在添加小球,而沒有刪除小球,

console.log(balls.length)

在更新小球的時候,打印balls的長度,就會發現一直在不斷的增長,無論你電腦內存有多大,最後都會被佔用光,因此必須得對balls數組的長度進行限制。

優化

這裏的優化技巧就是,檢測小球是否在屏幕內,若是不在屏幕內,就將它刪除。

let cnt = 0;
balls.forEach(ball => {
  if (ball.x + radius > 0 && ball.x - radius < windowWidth) {
    balls[cnt++] = ball;
  }
});

如何檢測小球是否在屏幕內進行檢測呢?

仍是使用上面檢測落地的方法。

小球的x座標 + 小球的半徑 > 0 而且 小球的x座標 - 小球的半徑 < 畫布半徑,說明小球在畫布裏面。

使用cnt進行計數,將符合要求(在屏幕內)的小球,放到balls的前面

那麼前cnt個小球都是也畫布中的,只須要將cnt後的小球進行刪除就好了。

while (balls.length > Math.min(300, cnt)) {
  balls.pop();
}

Math.min(300, cnt))的意思是,若是cnt > 300,取300,不然取cnt

它的做用是控制屏幕中效果的個數。

總結

作這個動畫的最大收穫是:

  1. 使用點陣系統繪製數字
  2. 小球落地是一個簡單的初衷物理知識
  3. 小球落地檢測方法
相關文章
相關標籤/搜索