iT邦幫忙

10

[筆記][HTML][JavaScript]canvas的基本用法(2)-這次來練習一些動畫的原理吧!

Hello!大家好!不知道大家有沒有喜歡的動畫,可能是海賊王、七龍珠、火影忍者、灌籃高手...等等,說到小弟我啊,最喜歡的非棋靈王莫屬了!但是我們今天不是要來聊動畫的,是要來講動畫!看了那麼久的動畫,記得以前還以為是電視機裡面有人,在裡面演給我看,不過一直到了國中,才慢慢結束了這美好的幻想XD,所謂動畫啊,原理其實很簡單,就是由一張張些微不同的圖片快速翻閱,讓我們的眼睛藉由視覺殘留的效果,感覺圖片好像真的動起來一樣!

上一段說了那麼多!就是為了接正文了!讓我們把動畫的原理套來網頁,記得我們上一篇已經可以用canvas畫出靜態的畫面了嗎?那接著我們讓他重複繪畫,並且在每次繪畫時都偷偷改變一些設定值試試看,不過首先我們還是先需要一張圖,當做複習,就來畫個台灣吧XD

讓我們來看看Google地圖上的台灣,由於小弟我覺得難度有點稍高,所以小改造一些XD::
https://ithelp.ithome.com.tw/upload/images/20180730/20106935npmgRaIt5t.jpg
好的!那就像上面一樣,我畫紅線把台灣大致上的輪廓用直線處理掉,那我們需要16個點,先來處理吧!我們把起手式打出來如下:
HTML

<canvas id="myCanvas"></canvas>

JavaScript

let canvas = document.getElementById("myCanvas");
let ctx = canvas.getContext("2d");
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;

然後...在JavaScript中畫出相對應的16個點之後相連,會變成以下的樣子,感覺我的台灣有點矮肥XD:

ctx.beginPath();
ctx.moveTo(200,50)
ctx.lineTo(180,55)
ctx.lineTo(165,80)
ctx.lineTo(130,100)
ctx.lineTo(70,250)
ctx.lineTo(60,300)
ctx.lineTo(80,350)
ctx.lineTo(120,380)
ctx.lineTo(140,420)
ctx.lineTo(160,425)
ctx.lineTo(160,385)
ctx.lineTo(175,330)
ctx.lineTo(220,290)
ctx.lineTo(240,200)
ctx.lineTo(260,150)
ctx.lineTo(250,110)
ctx.lineTo(270,90)
ctx.lineTo(250,75)
ctx.lineTo(215,65)
ctx.closePath()
ctx.stroke()

https://ithelp.ithome.com.tw/upload/images/20180730/20106935tffVWItAaH.jpg
好的,以上就是上一次課堂上所說的部分,那我們該如何讓他做變化呢,其實很簡單,既然需要多張靜態圖片,那我們就讓他一直重複繪製靜態圖片!首先我把以上繪製台灣的線條變成一個function,就叫draw好了,我們試著讓每一次執行前都用clearRect()把原本的圖案清掉,並且搭配setInterval重複執行draw,讓他會在網頁開著的期間一直重新繪製台灣,所以我們把JavaScript改成以下的樣子:

PS一下,我順便把,draw內的lineTo(x,y)整理一下,把所有的座標都放進一個陣列中,接著再跑迴圈去畫點的位置,這樣讓程式碼會比較乾淨一點。

let canvas = document.getElementById("myCanvas");
let ctx = canvas.getContext("2d");
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;

//把台灣的x,y座標整理到個陣列中
let arrTaiwan =[[200,50],[180,55],[165,80],[130,100],[70,250],[60,300],[80,350],[120,380],[140,420],[160,425],[160,385],[175,330],[220,290],[240,200],[260,150],[250,110],[270,90],[250,75],[215,65]]

//執行初始繪製
draw()

//繪製台灣
function draw(){
    /*清掉canvas內的元素
    第1,2個參數是x和y座標,指說從哪裡開始清掉繪製的圖形,
    這邊從(0,0)開始也就是網頁的最左上角,
    第3,4個參數是清掉的範圍,因為範圍是矩形,所以指定寬和高,
    這邊的寬高指定為canvas的寬高。*/
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    
    ctx.beginPath();
    ctx.moveTo(200,50)
    
    //使用迴圈去讀陣列中的內容畫線
    for(let i=1;i<=arrTaiwan.length-1;i++){
        ctx.lineTo(arrTaiwan[i][0],arrTaiwan[i][1])
    }

    ctx.closePath()
    ctx.stroke()
}

//再最後重複執行繪製,讓他50毫秒重新繪製一次
setInterval('draw()',50)

但是目前我們還是看不出他有重新繪製的感覺,因為他每一次繪製都是同一個樣子,所以我們來微調他每次重新繪製的一些屬性,先在function的外面建立一個全域變數time,並在draw的最後去增加他的值,代表重新繪製的次數,那要怎麼看到他的變化呢?我們使用最簡單ineWidth改變線條寬度來測試看看吧!

let canvas = document.getElementById("myCanvas");
let ctx = canvas.getContext("2d");
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;

//設定全域變數itme
let time = 0;

let arrTaiwan =[[200,50],[180,55],[165,80],[130,100],[70,250],[60,300],[80,350],[120,380],[140,420],[160,425],[160,385],[175,330],[220,290],[240,200],[260,150],[250,110],[270,90],[250,75],[215,65]]

draw()

function draw(){
    
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    
    //設定寬度為time,所以每一次繪製都會改變寬度
    ctx.lineWidth = time
    
    ctx.beginPath();
    ctx.moveTo(200,50)
    for(let i=1;i<=arrTaiwan.length-1;i++){
        ctx.lineTo(arrTaiwan[i][0],arrTaiwan[i][1])
    }
    ctx.closePath()
    ctx.stroke()
    
    //設定itme跑到10的時候歸0不然他會一直加深到永無止盡
    if (time == 10){
      time = 0
    }
    //結束時累加time的值
    time++
}

//再最後重複執行繪製,讓他50毫秒重新繪製一次
setInterval('draw()',50)

如此一來應該可以看見畫面上的台灣,不斷的加深線條寬度後又變細,無限循環,這邊我就不再另外貼圖了,因為也看不出他動的效果,不過如果看到相同的畫面代表大家已經完成了最簡單的動畫了!

接著要與滑鼠做互動動畫有兩種方式,第一種是用addEventListener監聽器,讓滑鼠每次移動時都會觸發事件,把座標記錄下來,第二種是使用onmousemove配合canvas的isPointInPath(),這個方法放在每次繪製前,去判斷滑鼠的游標有沒有在剛剛繪製的圖形內,如果有的話就去改變圖形樣式。這篇文章的最後我們先來說說isPointInPath(),下一次再好好詳述addEventListener的處理方式。

那現在我們把onmousemove放到draw前,再另外使用isPointInPath()判斷目前滑鼠座標是否在剛剛繪製的路徑中,如果有的話我們就讓繪製區域變色!現在直接看程式碼吧!

let canvas = document.getElementById("myCanvas");
let ctx = canvas.getContext("2d");
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;

let time = 0

//這邊再新增一個全域變數fillColor,
//用來設定填滿圖形的色彩,預設為白色
let fillColor = "#FFFFFF"

let arrTaiwan =[[200,50],[180,55],[165,80],[130,100],[70,250],[60,300],[80,350],[120,380],[140,420],[160,425],[160,385],[175,330],[220,290],[240,200],[260,150],[250,110],[270,90],[250,75],[215,65]]

//執行初始繪製
draw()

//繪製台灣
function draw(){
    
    ctx.clearRect(0, 0, canvas.width, canvas.height)
    ctx.fillStyle = fillColor
    ctx.lineWidth = time
    ctx.beginPath();
    ctx.moveTo(200,50)
    for(let i=1;i<=arrTaiwan.length-1;i++){
        ctx.lineTo(arrTaiwan[i][0],arrTaiwan[i][1])
    }
    ctx.closePath()
    ctx.stroke()
    ctx.fill()
  
    if (time == 10){
      time = 0
    }
    //結束時累加time的值
    time++
    
}

//使用onmousemove事件去抓取滑鼠是否移動到canvas中
//如果有的話就觸發事件
canvas.onmousemove = function (e) {
  //先獲取目前游標在的座標
  var x = e.pageX, y =e.pageY;
  
  //使用isPointInPath()判斷滑鼠座標是否在上一次繪製圖形的path之內,
  //傳入參數為我們獲取的滑鼠x,y座標
  if(ctx.isPointInPath(x,y)) {
      //如果在繪製圖形內的話設定填滿顏色為紅色
      fillColor="#FF0000";
  } 
  else{
      //如果在繪製圖形外的話設定填滿顏色為白色
      fillColor="#FFFFFF";
  }
}
//接收我們剛剛的設定後繪製新的圖形
setInterval('draw()',50)

登愣!這麼一來,不只粗細會不斷變動,只要滑鼠移到台灣的範圍內,就會變成以紅色填滿,如果移開就會變回白色,就像下面的圖一樣,右邊是一般狀態,但滑鼠只要進入台灣的範圍,就會變成紅色的!話說剛剛在截圖的時候,還以為自己畫的是神奇寶貝的鐵甲蛹...
https://ithelp.ithome.com.tw/upload/images/20180801/20106935WyaMyrWB1G.jpg
所以只要以上面這種方式,就可以簡單地做出互動式的網頁動畫了!當然因為前端的世界真的很變化多端,所以一定會有更好的處理方式來做到同樣的效果,如果各位大大有任何想法都可以提出,我都會找時間去研究看看,像第一篇的留言真的出現很多高手XD,也很感謝他們做分享,如果以上文章中有任何問題或錯誤也都可以留言告訴我,我會盡快改進,那接著下一篇再把這些原理做更進一步地運用,繼續做出各種有趣的頁面吧!

另外這禮拜六日可能就不會有文章了,因為小弟我禮拜五開始就要上台北放風了/images/emoticon/emoticon07.gif,所以讓我偷懶一個禮拜吧!哈哈哈!我們下下禮拜見,謝謝大家/images/emoticon/emoticon41.gif


1
joneshong
iT邦新手 5 級 ‧ 2018-08-01 17:23:13

你很有毅力...尤其是點座標那邊/images/emoticon/emoticon12.gif

不得不說,我在畫出來之前,
一直有個聲音說不然放棄好了/images/emoticon/emoticon37.gif

1
fysh711426
iT邦研究生 5 級 ‧ 2018-08-01 22:28:58

好有趣,原來 canvas 的互動事件是這樣做出來的,
還有沒想到你真的把台灣畫出來了。 /images/emoticon/emoticon39.gif

哈哈哈,對啊!我那時候學到也有「哇!」的驚喜感!
雖然畫出台灣,但是天賦樹就又是另外一回事了/images/emoticon/emoticon37.gif

1
fillano
iT邦超人 1 級 ‧ 2018-08-03 09:25:47

isPointInPath()是好物阿...不然判斷多邊形的intersection有點麻煩(凸多邊形比較簡單,如果有地方是凹的...)

真的!我第一次學的時候只知道用addEventListener去取滑鼠座標,是剛好讓我爬文爬到這個function,算是救了我一命吧/images/emoticon/emoticon25.gif

我要留言

立即登入留言