iT邦幫忙

9

[筆記][HTML][JavaScript]canvas的基本用法(3)-最後用滑鼠互動動畫做個結尾!

嗨囉!大家好!這篇文章是這個系列的最後了,來說說如何用滑鼠和canvas內的繪圖做簡單的互動吧!

...這次的前言是不是有點少XD,但是前言真的真的真的很難想啊!這篇先簡單進入正文吧!

首先,今天的主題是要使用滑鼠和canvas做動畫,所以我們必須先取得滑鼠在畫面上的座標才行,這時候可以使用JavaScript的addEventListener監聽器,來監聽mousemove滑鼠移動的過程來觸發事件,透過事件可以取得滑鼠目前在畫面上的xy座標位置,說起來很簡單,做起來其實也不難,我們直接試試吧!

//使用addEventListener監聽器,監聽mousemove滑鼠移動,並觸發後面的function
window.addEventListener('mousemove',(event) => {
  /*在function內會傳入我們監聽的滑鼠物件,
    我們可以從這個物件中取得我們要的資料:
    x座標 event.pageX 及y座標 event.pageY
    並把它印在console中*/
  console.log(`${event.pageX},${event.pageY}`)
})

這時候打開console.log就會看見每一次滑鼠移動時的座標都會被記錄在console上。
https://ithelp.ithome.com.tw/upload/images/20180811/20106935uoV3S37qvI.jpg
接著把上面那段取得的滑鼠座標,加進canvas的起手式中:
HTML

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

JavaScript

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

//建立一個物件儲存滑鼠目前的x,y座標
let mouse = {
    x : 0,
    y : 0,
}

//加入監聽器
window.addEventListener('mousemove',(event) => {
  //在這裡把滑鼠座標寫到物件mouse中
  mouse.x = event.pageX;
  mouse.y = event.pageY;
})

那取了滑鼠座標後可以做什麼呢?來以這個座標當圓心畫個太陽吧!

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

let mouse = {
    x : 0,
    y : 0,
}

window.addEventListener('mousemove',(event) => {
  mouse.x = event.pageX;
  mouse.y = event.pageY;
})

//一個繪製的function
const draw = () =>{
    //先清掉cvanvas目前繪製的圖形
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    //開始作畫
    ctx.beginPath();
    //以滑鼠座標為圓心,畫一個半徑為30的圓形
    ctx.arc(mouse.x,mouse.y,30,0,Math.PI*2);
    //用橘色畫線
    ctx.strokeStyle="#FF5511";
    ctx.stroke();
    //黃色填滿
    ctx.fillStyle="#FFFF00";
    ctx.fill();
}

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

如此一來,畫面上應該會出現追著滑鼠跑的圓形(原諒我截不到鼠標...),因為滑鼠只要移動,新的座標就會一直被addEventListener抓到,並寫進mouse物件中,而我們每次繪製都會先清掉之前的圓,再去使用mouse物件中的座標當圓心重新繪製一次圓,所以在畫面上的感覺就像圓會跟著滑鼠跑:
https://ithelp.ithome.com.tw/upload/images/20180812/20106935cp3dxC1ioC.png
之後我們建立一個新的funcitonlightLine來繪製太陽周圍的光芒線條,可以看看下圖:
https://ithelp.ithome.com.tw/upload/images/20180812/20106935hC182f4qL9.png
黑色線條為太陽的半徑,綠色線條為空白的地方,因為線條通常不會黏在太陽的圓上,而橘線的長度就是我們光芒線條的長度了,所以依照圖解,從圓心開始把x座標+加上黑色線條的30,再加上綠色線條的長度10,而y軸的高度不變,我們可以得到橘色線條的起始點,而他的終點只需要再起始點的x軸座標再加上20,就可以畫出右邊的橫線了,把以上的解釋寫成程式碼:

const lightLine = () =>{
    //設定線條顏色為橘色
    ctx.strokeStyle="#FF5511";
    //開始繪圖
    ctx.beginPath();
    //起始點為滑鼠的x座標加上黑色線條的30及綠色線條的10
    ctx.moveTo(mouse.x+(30+10),mouse.y);
    //終點為x座標再加上20的距離
    ctx.lineTo(mouse.x+((30+10)+20),mouse.y);
    //畫線
    ctx.stroke();
}

之後把lightLine加到原本的程式中:

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

let mouse = {
    x : 0,
    y : 0,
}

window.addEventListener('mousemove',(event) => {
  mouse.x = event.pageX;
  mouse.y = event.pageY;
})

//一個繪製的function
const draw = () =>{
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    ctx.beginPath();
    ctx.arc(mouse.x,mouse.y,30,0,Math.PI*2);
    ctx.strokeStyle="#FF5511";
    ctx.stroke();
    ctx.fillStyle="#FFFF00";
    ctx.fill();
    
    //繪製光芒線條
    lightLine();
}

//繪製光芒線條的function
const lightLine = () =>{
    //設定線條顏色
    ctx.strokeStyle="#FF5511";
    //右橫線
    ctx.beginPath();
    ctx.moveTo(mouse.x+(30+10),mouse.y);
    ctx.lineTo(mouse.x+((30+10)+20),mouse.y);
    ctx.stroke();
}

setInterval('draw()',50)

加進去後可以看見原本的太陽的右邊多了一條橫線:
https://ithelp.ithome.com.tw/upload/images/20180812/20106935eR372D6eh1.png
另外左邊的橫線也可以用相同的方式,只是往右邊的距離是用加的,所以要繪製左橫線,就變成要把距離扣掉。上縱線的話得把原本變動的x座標變成y座標,y從上方開始越往下越大,所以上縱線必須用扣的,而下縱線則是用加的。知道原理後就可以在lightLine中繼續繪製其他線條:

//繪製光芒線條的function
const lightLine = () =>{
    //設定線條顏色
    ctx.strokeStyle="#FF5511";
    //右橫線
    ctx.beginPath();
    ctx.moveTo(mouse.x+(30+10),mouse.y);
    ctx.lineTo(mouse.x+((30+10)+20),mouse.y);
    ctx.stroke();
    //左橫線
    ctx.beginPath();
    ctx.moveTo(mouse.x-(30+10),mouse.y);
    ctx.lineTo(mouse.x-((30+10)+20),mouse.y);
    ctx.stroke();
    //上縱線
    ctx.beginPath();
    ctx.moveTo(mouse.x,mouse.y-(30+10));
    ctx.lineTo(mouse.x,mouse.y-((30+10)+20));
    ctx.stroke();
    //下縱線
    ctx.beginPath();
    ctx.moveTo(mouse.x,mouse.y+(30+10));
    ctx.lineTo(mouse.x,mouse.y+((30+10)+20));
    ctx.stroke();
}

都加完後太陽就會擁有十字的光芒線條了:
https://ithelp.ithome.com.tw/upload/images/20180812/20106935odPx0BJwWX.png
不過這樣子看起來還是有點單調,所以接著要繪製斜線的部分,這裡我借我PS一下,其實接下來的部分用一種「極座標」的方式處理會比較好,但是如果在這裡說明極座標的話,我就覺得不太基本了XD,所以本篇我會先用一些簡單的方式來處理斜線的部分,關於極座標我會再另外寫文章來說明。

那繪製斜線前一樣先上圖,應該會比較好理解吧?(我真的盡力畫好了XD):
https://ithelp.ithome.com.tw/upload/images/20180812/20106935DJ66ylJXlT.jpg
因為接下來要畫的是斜線,所以需要同時改變x及y軸座標,如上圖我們從圓心點的位置,讓y軸延著黑線向上加上30,再沿著藍線的部分把x軸的座標加上30,這時候的座標會在綠色線和橘色線的交接處,剛好在橘色線條的起始點上。

第二部分要取的是橘色線條的終點,如果有理解上面那段的話,就可以知道,x及y軸同時改變的距離越長,那最後的位置也會離圓心越遠。所以把x和y同時加上40,也就是黑色線條加上紫色線條的距離,最後所在的地方就是橘色線條的終點。

把上面兩段寫進lightLine中,另外我把繪製十字光芒射線的部分改成用迴圈處理:

//繪製光芒線條的function
const lightLine = () =>{
    //設定線條顏色
    ctx.strokeStyle="#FF5511";
    
    //分別是起始點和終點需要增加的距離
    let moveLength = 40
    let lineLength = 60
    
    //第一次迴圈加上距離,第二次減掉距離
    for(let i=-1;i<=1;i+=2){
        //橫線
        ctx.beginPath();
        ctx.moveTo(mouse.x+(moveLength*i),mouse.y);
        ctx.lineTo(mouse.x+(lineLength*i),mouse.y);
        ctx.stroke();
        //縱線
        ctx.beginPath();
        ctx.moveTo(mouse.x,mouse.y+(moveLength*i));
        ctx.lineTo(mouse.x,mouse.y+(lineLength*i));
        ctx.stroke();
    }
    
    /*因為x和y都是變多,
     所以x軸往右,y軸往下,
     會繪製出下右斜線*/
    ctx.beginPath();
    //把x軸加上藍色線條,y軸加上黑色線條,長度都是30
    ctx.moveTo(mouse.x+30,mouse.y+30);
    //把x和y軸座標加上黑色線條30和紫色線條的長度10
    ctx.lineTo(mouse.x+(30+10),mouse.y+(30+10));
    ctx.stroke();
}

來看看加上斜線的太陽會長什麼樣子吧!
https://ithelp.ithome.com.tw/upload/images/20180812/20106935zgCfaDBcf0.png
感覺斜線的距離有點短XD,下一段把紫色距離的10變成15好了,另外再加上其他三條斜線:

//繪製光芒線條的function
const lightLine = () =>{
    //設定線條顏色
    ctx.strokeStyle="#FF5511";
    
    //分別是起始點和終點需要增加的距離
    let moveLength = 40
    let lineLength = 60
    
    //第一次迴圈加上距離,第二次減掉距離
    for(let i=-1;i<=1;i+=2){
        //橫線
        ctx.beginPath();
        ctx.moveTo(mouse.x+(moveLength*i),mouse.y);
        ctx.lineTo(mouse.x+(lineLength*i),mouse.y);
        ctx.stroke();
        //縱線
        ctx.beginPath();
        ctx.moveTo(mouse.x,mouse.y+(moveLength*i));
        ctx.lineTo(mouse.x,mouse.y+(lineLength*i));
        ctx.stroke();
    }
    
    //下右斜線
    ctx.beginPath();
    ctx.moveTo(mouse.x+30,mouse.y+30);
    ctx.lineTo(mouse.x+(30+15),mouse.y+(30+15));
    ctx.stroke();
    
    //下左斜線
    ctx.beginPath();
    ctx.moveTo(mouse.x-30,mouse.y+30);
    ctx.lineTo(mouse.x-(30+15),mouse.y+(30+15));
    ctx.stroke();
    
    //上右斜線
    ctx.beginPath();
    ctx.moveTo(mouse.x+30,mouse.y-30);
    ctx.lineTo(mouse.x+(30+15),mouse.y-(30+15));
    ctx.stroke();
    
    //上左斜線
    ctx.beginPath();
    ctx.moveTo(mouse.x-30,mouse.y-30);
    ctx.lineTo(mouse.x-(30+15),mouse.y-(30+15));
    ctx.stroke();
}

完成lightLine後就可以不用管他了XD,來看看完整版的太陽長什麼樣子吧!
https://ithelp.ithome.com.tw/upload/images/20180812/20106935ndl07Cye04.png
最後來模擬一下天色的變化吧!首先要先預設canvas的背景顏色,這部分我們在draw中設置:

const draw = () =>{
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    //設定背景顏色
    //用rgb的方式把填滿的色彩設為天空藍
    ctx.fillStyle = 'rgb(0, 180, 255)';
    //使用fillRect繪製一個和canvas一樣寬高的方形
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    //之後繪製太陽
    ctx.beginPath();
    ctx.arc(mouse.x,mouse.y,30,0,Math.PI*2);
    ctx.strokeStyle="#FF5511";
    ctx.stroke();
    ctx.fillStyle="#FFFF00";
    ctx.fill();
    
    //繪製光芒線條
    lightLine();
}

先繪製一個藍色的方形,再繪製太陽及線條,因為順序的關係,方形在最下面就成了背景顏色,設定好後會如下圖:
https://ithelp.ithome.com.tw/upload/images/20180812/20106935VlkSu6kBqY.png
這邊因為有了背景顏色,所以可能會注意到一個之前沒有發現的問題,就是一開始設定canvas的寬高的這兩行:

canvas.height = window.innerHeight;
canvas.width = window.innerWidth;

他會在新視窗打開時去取目前視窗的寬高,並把這個數值設定給canvas,所以如果我一開始的視窗是小的,運行時再把它給拉大,就會出現以下的情形:
https://ithelp.ithome.com.tw/upload/images/20180812/20106935M3aHeDxDvR.png
canvas的寬高還是維持在一開始的長度,不會隨著視窗大小改變,超過那個距離就不會繪製圖形,要解決這個問題也很簡單,應該很多大大在發現這個問題時就馬上解決了!我們只需要把以上兩行放進draw中,讓他每一次繪製前都重新去抓目前的視窗大小,重新給canvas,再開始繪製,就搞定這個問題了!

那為什麼我要設定背景顏色呢?本篇文章的最後,我要讓背景顏色隨著太陽的高低有所變化,就像天色一樣,太陽下山就暗掉了!眼尖的大大應該也有發覺,我在設定背景顏色的時候是使用rgb,這可不是因為心血來潮哦,而是因為使用grb就可以更輕易的去改變些微的顏色,首先把會變動數值的greenblue都設成變數:

const draw = () =>{
    //把視窗大小重新設定給canvas
    canvas.height = window.innerHeight;
    canvas.width = window.innerWidth;
    
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    //設定背景顏色
    let green = 180;
    let blue = 255;
    ctx.fillStyle = `rgb(0, ${green}, ${blue})`;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    //之後繪製太陽
    ctx.beginPath();
    ctx.arc(mouse.x,mouse.y,30,0,Math.PI*2);
    ctx.strokeStyle="#FF5511";
    ctx.stroke();
    ctx.fillStyle="#FFFF00";
    ctx.fill();
    
    //繪製光芒線條
    lightLine();
}

這裡最簡單的方法就是將green和blue直接減掉滑鼠目前在的y座標,因為最上方的y是0,所以滑鼠如果視窗下方移動,就等於太陽往下方移動,y就會越來越大,而green和blue扣掉越來越大的y後就會越來越小,直到變成代表黑色的rgb(0, 0, 0)

但是這樣會有個問題,由於我們的green及blue最大也只有255而已,所以其實y座標只要超過255,背景就會變成黑色。不會再有變化了,我們必須想個方法讓他等比例的被扣掉數值。

先處理green,他的基本數值是180,這裡先把180除上目前視窗的高度,會得到每一點高度等於180中的多少比例,當我們得到這個比例後,再將他乘上目前滑鼠的y軸高度,就會是green最後要扣掉的數值了。

藍色的部分因為初始值比較大,是255,所以當綠色的180被扣掉1的時候,藍色的255也許會被扣掉1.5左右,這麼一來滑鼠的y軸到一個高度時,天空會慢慢變成綠色,這並不是我想要的天色,所以這裡我一樣把藍色的下降比例設成180和green一樣,這樣藍色的比例就會一直高於綠色。

不過以上的比例是我自己是出來認為最適合的,各位大大也可以試著調整不同的數值觀察變化,來看看最後draw的樣子:

const draw = () =>{
    //把視窗大小重新設定給canvas
    canvas.height = window.innerHeight;
    canvas.width = window.innerWidth;
    
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    //設定背景顏色
    //先用180除上目前視窗的高度,再乘上目前滑鼠的y軸高度
    let green = 180-(mouse.y*(180/window.innerHeight));
    
    /*藍色部分沒有用255除上目前視窗高度是因為
     如果使用255那藍色被扣掉的幅度會比綠色被扣掉的幅度還大,
     所以這裡我選擇設定和綠色相同的比例,而最後y到視窗最下方時,
     也會顯示很深很深的藍色,而不是黑色*/
    let blue = 255-(mouse.y*(180/window.innerHeight));
    
    ctx.fillStyle = `rgb(0, ${green}, ${blue})`;
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    
    //之後繪製太陽
    ctx.beginPath();
    ctx.arc(mouse.x,mouse.y,30,0,Math.PI*2);
    ctx.strokeStyle="#FF5511";
    ctx.stroke();
    ctx.fillStyle="#FFFF00";
    ctx.fill();
    
    //繪製光芒線條
    lightLine();
}

終於完成的現在,來感受一下成果吧!
https://ithelp.ithome.com.tw/upload/images/20180812/20106935Py4qCeY88f.jpg
附上這篇文章的codePen連結

以上就是這個系列的最終回了,這篇文章打了很久很久,中途還因為突發事件重打了一次XD,讓很少熬夜的我大爆氣,不過很快就接受現實繼續打完了,弄到現在感覺可以去麥當勞吃個滿福堡在睡覺,哈哈哈。

那最後如果對以上文章有任何問題或是哪些地方搞錯了,都可以在留言告訴我,或是各位大大可以留下各自的經驗或想法可以互相討論的都很歡迎,謝謝各位大大的觀看/images/emoticon/emoticon41.gif


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
1
暐翰
iT邦大師 1 級 ‧ 2018-08-12 10:11:23

網頁滑鼠滑了滑,玩了玩,酷
/images/emoticon/emoticon12.gif

神Q超人 iT邦研究生 5 級 ‧ 2018-08-12 19:51:06 檢舉

讓畫面動起來真的是一件很酷的事情!
不過真的是需要非常的有耐心XD

話說好久沒有看到大大發文了/images/emoticon/emoticon31.gif

暐翰 iT邦大師 1 級 ‧ 2018-08-13 08:01:34 檢舉

最近在忙處理公司的事情 /images/emoticon/emoticon06.gif

神Q超人 iT邦研究生 5 級 ‧ 2018-08-13 13:09:55 檢舉

哈哈,大大辛苦了!
期待你有空在分享知識!

1
小碼農米爾
iT邦高手 1 級 ‧ 2018-08-12 12:22:46

好有趣喔,顏色的變換原來可以這樣設計。
/images/emoticon/emoticon32.gif

看更多先前的回應...收起先前的回應...
神Q超人 iT邦研究生 5 級 ‧ 2018-08-12 19:52:55 檢舉

對啊,其實只要知道一些基本的原理就可以做出很多東西了XD

不過下一篇的極座標讓以前沒學好三角函數的我頭很痛/images/emoticon/emoticon70.gif

哈哈哈,最近為了解 Code Jam 第四題也在看三角函數,期待下一篇極座標文章!!

說到重打,大家都有過的痛,哈哈哈,
推薦一下我之前寫的替代方案,現在都先打在 VSCode 然後順便推 git 做版控。
/images/emoticon/emoticon37.gif

[VSCode] Visual Studio Code 寫 Markdown 使用 iT邦樣式
不過可能最新版的 VSCode 會有問題,自從更新某一版壞掉後,就再也不敢更新了。
/images/emoticon/emoticon16.gif

神Q超人 iT邦研究生 5 級 ‧ 2018-08-13 13:14:54 檢舉

我現在還卡在第三題,要讓他跑完3乘3,再進行下一個3乘3的土地,
一樣理解題目就花了非常久XD

沒想到VSCode有這種套件可以用!
這禮拜也來試試看,有問題再詢問您,
我的VSCode下載下來後,從來沒更新過XD,
所以每次開起來都再右上角不斷通知我,哈哈哈。

那和我一樣,繼續不要更新。 /images/emoticon/emoticon01.gif

0
阿展展展
iT邦好手 1 級 ‧ 2019-08-03 14:47:25

神奇的日出日落
100行內完成
大大解釋得清楚又有力!

感謝大大

我要留言

立即登入留言