這篇文章說如何用canvas畫出漂亮的下雨效果,先看看最後實現的效果吧。
效果圖
解釋看圖來分析下,我們需要達到哪些效果。
1.雨滴下落效果,移動滑鼠控制下墜方向
2.雨滴下落散成小水珠,小水珠的移動方向和滑鼠移動方向相同
3.雨滴下到滑鼠座標一定範圍內,散成小水珠,同樣的,小水珠的移動方向也和滑鼠移動方向相同
好的,我們把整個效果大致拆分成三個效果,達到這三個效果,就完成了。
我們一步一步來實現。
1.雨滴下落效果,移動滑鼠控制下墜方向實現整個效果的想法就是,
初始時用一個陣列保存雨滴物件。
一個雨滴物件裡面有各個屬性用來表示,雨滴的x座標,y座標,長度,下落速度,顏色,判斷是否刪除的標誌位
更新動畫時在數組中加入一定數量的雨滴對象,然後遍歷數組,修改每個雨滴對象的x坐標和y坐標,用canvas根據雨滴對象的坐標,畫出兩個點,連起來就是一個雨滴了。
所以實現效果的重點就在座標上
初始化一個雨滴的時候雨滴x座標:一個隨機數
雨滴y座標:-100,這樣是為了讓雨滴從可視區域外進來
更新動畫時雨滴x座標:原x坐标的值+ speed * speedx
speed 是固定的數值,表示雨滴下落速度,
speedx 是一個和滑鼠移動方向有關係的變量, speedx = speedx + (maxspeedx - speedx) / 50
而maxspeedx 是根據滑鼠移動方向得到的一個值
maxspeedx = (e.clientX - canvasEl.clientWidth / 2) / (canvasEl.clientWidth / 2)
,
e.clientX:滑鼠距離可視區域左邊的數值
canvasEl.clientWidth:整個可視區域的寬度
也就是說speedx 是一個逐漸接近maxspeedx 的值
maxspeedx 的值範圍是-1 到1,他的值越接近-1,表示方向越向左,值越接近1,表示方向越向右。
為什麼不直接用maxspeedx ?
這是為了讓雨滴變化方向的速度不要那麼快,不要跟著滑鼠變化方向立即改變,要有點點的延遲,看起來更好些。
如果用maxspeedx ,是這樣的效果
如果用speedx ,是這樣的效果
雨滴y座標:原y坐标的值+ speed
speed 和上面x座標中提到的一樣,是一個固定值,表示雨滴下墜速度,
好的,最後就是用canvas根據雨滴物件的座標,畫兩個點了,然後連起來,雨滴就畫出來了
第一個點座標比較簡單,直接取得雨滴物件的x座標和y座標,就是這個點的座標
第二個點的座標:
x坐标= 雨滴x坐标的值+ 雨滴长度* speedx
y坐标= 雨滴y坐标的值+ 雨滴长度
最後把這兩個點連起來,就有一條線了,就是一個雨滴了
當設定x座標時,又用上了變數speedx,這是為了讓雨滴方向和雨滴下落方向相同,
當不用speedx時,是這樣
用上speedx時,是這樣
2.雨滴下落散成小水珠,小水珠的移動方向和滑鼠移動方向相同這裡的思路其實,和上面的效果有些相似
初始時用一個陣列保存小水珠物件。
一個小水珠,其實就是畫一個圓弧。
一個小水珠物件裡面有各個屬性用來表示,小水珠的座標,x軸移動速度,y軸移動速度,圓的半徑,判斷是否刪除的標誌位。
更新動畫時在數組中加入一定數量的小水珠對象,然後遍歷數組,修改每個小水珠對象的x座標和y座標,用canvas根據小水珠對象的座標屬性和半徑屬性,畫一個圓弧。
所以實現效果的重點還在座標上
初始化一個小水珠的時候小水珠是雨滴消失的時候出現的,所以小水珠的座標也是根據雨滴的座標來的,刪除一個雨滴,就出現一些小水珠,而且小水珠的移動方向也是和雨滴下落方向,滑鼠移動方向一樣,所以還是會需要上面提到的變數speedx,
小水珠x坐标: 删除的雨滴x坐标+ 删除的雨滴长度* speedx
小水珠y坐标:删除的雨滴y坐标+ 删除的雨滴长度
這裡要用到小水珠物件的兩個屬性vx(x軸的值的變化速度) 和vy(y軸的值的變化速度),
小水珠的x座標
vx = vx + speedx / 2
小水珠的x坐标=原x坐标+ vx
,
speedx:上面提到的和滑鼠移動方向相關的一個變量,這裡的作用就是用來控制小水珠的移動方向和其他方向相同
speedx / 2
,除2是為了讓讓小水珠在x軸的移動距離短一點,看上去更真實點
小水珠的y座標
vy = vy + gravity
小水珠的y坐标= 原y坐标+ vy;
,
vy:一個負數
gravity:重力,一個正數,完整程式碼裡設定的是0.5
因為原y座標是一個正數,這樣小水珠y座標的值,就會先減小後增大,這是為了實現小水珠會先上升後下降的效果,看圖
最後就是用canvas根據小水珠的座標屬性和半徑屬性畫圓弧就可以了,弧度是隨機的
3.雨滴下到滑鼠座標一定範圍內,散成小水珠,同樣的,小水珠的移動方向也和滑鼠移動方向相同
要確定圖中圓的大小容易,假設圓的半徑是35,我們能取得到滑鼠的座標,以滑鼠的座標為圓心,35為半徑,就確定了圓的大小。
重點在於如何判斷,雨滴是否進入了這個範圍,這就要用勾股定理了,看圖。
因為雨滴是兩個點連起來的一條線,要看雨滴是不是進入了這個範圍內, 就是看雨滴靠下邊的點的座標,到滑鼠的直線距離是多少,就是圖中AB線段的長度。
勾股定理:直角三角形的兩條直角邊的平方和等於斜邊的平方。
AB = Math.sqrt(BC BC + AC AC)
BC = 雨滴x座標- 滑鼠x座標
AC = 雨滴y座標- 滑鼠y座標
Math.sqrt()方法用來計算一個數的平方根
我們知道雨滴到滑鼠的直線距離後,和圓的半徑比較下,大於半徑就不在範圍內,否則就是在了。
如果在範圍內,就刪除雨滴,畫一些小水珠。
總結
要實現這個效果,麻煩的地方在於方向,雨滴方向,雨滴下落方向,小水珠移動方向,而這些都和滑鼠移動方向相關,確定各種方向後,根據距離,用canvas不斷的畫線,畫圓弧就行了。
完整程式碼
<!doctype html><html lang=en><head> <meta charset=UTF-8> <style> * { margin: 0; padding: 0; } </style></head><body> <canvas id =canvas style=position: absolute; height: 100%; width:100%;></canvas> <script> window.onload = main; function main() { // 取得canvas元素var canvasEl = document.getElementById('canvas'); var ctx = canvasEl.getContext('2d'); // canvas畫布的背景顏色var backgroundColor = '#000'; / / canvas畫布的寬等於可視區域的寬canvasEl.width = canvasEl.clientWidth; // canvas畫布的高等於可視區域的高canvasEl.height = canvasEl.clientHeight; // 保存小水珠的陣列// 雨滴下落後散成小水珠,小水珠就是一些圓弧var dropList = []; // 重力// 雨滴下落後散成小水珠,小水珠會先上升後下降,主要是因為gravity 這個變數的緣故var gravity = 0.5; //儲存雨滴的陣列// 每個雨滴都是畫的一條線var linelist = []; // 儲存滑鼠的座標// mousePos[0] 代表x軸的值,mousePos[1] 代表y軸的值var mousePos = [0, 0]; // 跟著滑鼠, mouseDis 大小區域內的雨滴會消失,形成散落效果//以mousePos為圓心,mouseDis為半徑,這個範圍內的雨滴都會散開,形成許多小水珠var mouseDis = 35; // 更新一次動畫,畫lineNum 條雨滴,lineNum 值越大,下雨就越密集var lineNum = 3; // 跟隨滑鼠方向變化下雨方向的速度// 滑鼠移動後,下雨的方向會慢慢改變,主要靠speedx 這個變數var speedx = 0; // maxspeedx 為speedx 可以取的最大值// 當speedx = maxspeedx時,下雨方向會隨滑鼠移動方向立即改變var maxspeedx = 0; // 頁面大小改變時,重置canvas畫布大小window.onresize = function ( ) { canvasEl.width = canvasEl.clientWidth; canvasEl.height = canvasEl.clientHeight; } //移動滑鼠觸發事件window.onmousemove = function (e) { // 設定mousePos 等於滑鼠座標// e.clientX 為距離瀏覽器視窗可視區域左邊的距離// e.clientY 為距離瀏覽器視窗可視區域上邊的距離mousePos[0] = e.clientX; mousePos[1] = e.clientY; //透過滑鼠位置,設定maxspeedx的值,取值範圍是-1 到1 // maxspeedx的值,關係到// 1、雨滴的方向// 2、雨滴下墜的方向// 3、雨滴下墜方向跟著滑鼠移動方向變化的速率// 4、小水珠的移動方向// 值越接近1,表示方向越向右// 值越接近-1,表示方向越向左maxspeedx = (e.clientX - canvasEl.clientWidth / 2) / (canvasEl.clientWidth / 2); } // 根據參數,傳回一個rgb顏色,用於給雨滴設定顏色function getRgb(r, g, b) { return rgb( + r + , + g + , + b + ); } // 畫一滴雨(一條線) function createLine(e) { //隨機產生雨滴的長度var temp = 0.25 * (50 + Math.random() * 100); // 一個line 對象,代表一個雨滴var line = { // 雨滴下落速度speed: 5.5 * (Math.random() * 6 + 3), // 判斷是否刪除,值為true就刪除die: false, // 雨滴x座標posx: e, // 雨滴y座標posy: -50, // 雨滴的長度h: temp, // 雨滴的顏色color: getRgb(Math.floor(temp * 255 / 75), Math.floor(temp * 255 / 75), Math.floor(temp * 255 / 75)) }; // 把創建好的line(雨滴)對象,加到保存雨滴的數組linelist.push(line); } //畫一個小水珠(雨滴散開後的小水珠就是一個個的圓弧) function createDrop(x, y) { // 一個drop 對象,代表一個圓弧var drop = { // 判斷是否刪除,值為true就刪除die: false, // 圓弧圓心的x座標posx: x, // 圓弧圓心的y座標posy: y, // vx 表示x軸的值變化的速度vx: (Math.random() - 0.5) * 8, // vy 表示y軸的值變化的速度取值範圍:-3 到-9 vy: Math.random() * (-6) - 3, // 圓弧的半徑radius: Math.random() * 1.5 + 1 }; return drop; } // 畫一定數量的小水珠function madedrops(x, y) { //隨機產生一個數maxi // maxi 代表要畫小水珠的數量var maxi = Math.floor(Math.random() * 5 + 5); for (var i = 0; i < maxi; i++) { dropList. push(createDrop(x, y)); } } // 開始呼叫update函數,更新動畫window.requestAnimationFrame(update); // 更新動畫function update() { // 若儲存小水珠的陣列有內容if (dropList.length > 0) { // 遍歷儲存小水珠的陣列dropList.forEach(function (e) { //設定e.vx,vx表示x座標變化的速度// (speedx)/2 是為了,讓小水珠在x軸的移動距離短一點,看起來更真實點//也使小水珠的移動方向和雨滴方向,雨滴下行方向,滑鼠移動方向相同e.vx = e.vx + (speedx / 2); e.posx = e.posx + e.vx; //設定e .vy,vy表示y座標變化的速度// e.vy的範圍是-3 到-9,而這時e.posy(y座標)一定是正值,所以e.posy的值會先減少後增大//也就是實現雨滴散成小水珠,小水珠會先上升後下降的效果e.vy = e.vy + gravity; e.posy = e.posy + e.vy; // 如果小水珠y座標大於視覺區域的高度,設定die屬性為true // 小水珠如果超出視覺區域就刪除掉if (e.posy > canvasEl.clientHeight) { e.die = true; } }); } // 刪除die屬性為ture的陣列成員// 視覺區域外的小水珠刪除掉for (var i = dropList.length - 1; i >= 0; i--) { if (dropList[i].die) { dropList.splice( i, 1); } } // 設定下雨方向變換的速度,取值範圍: -1 到1 // 當speedx = maxspeedx時,下雨方向會隨滑鼠移動方向立即改變speedx = speedx + (maxspeedx - speedx) / 50; // 根據lineNum的值,畫一定數量雨滴for (var i = 0; i < lineNum; i++) { // 呼叫createLine 函數,參數是雨滴x座標createLine(Math.random() * 2 * canvasEl.width - (0.5 * canvasEl.width)); } // 設定結束線,也就是雨滴散開形成許多小水珠的位置var endLine = canvasEl.clientHeight - Math.random() * canvasEl.clientHeight / 5; // 遍歷保存雨滴的數組linelist.forEach(function (e) { // 利用勾股定理確定一個範圍,在這個範圍內雨滴會散開形成小水珠// e.posx + speedx * eh 是雨滴x座標// e.posy + eh 是雨滴y座標var dis = Math.sqrt(((e.posx + speedx * eh) - mousePos[0]) * ((e. posx + speedx * eh) - mousePos[0]) + (e.posy + eh - mousePos[1]) * (e.posy + eh - mousePos[1])); // 如果在mouseDis區域內,就刪除雨滴,畫一些小水珠(圓弧) // 實現滑鼠碰到雨滴,雨滴散成小水珠的效果if (dis < mouseDis) { // 刪除雨滴e.die = true; // 畫一些小水珠(圓弧) madedrops(e.posx + speedx * eh, e.posy + eh); } //如果雨滴超過結束線,刪除雨滴,畫一些小水珠(圓弧) if ((e.posy + eh) > endLine) { e.die = true; madedrops(e.posx + speedx * eh, e.posy + eh); } // 如果雨滴y座標大於視覺區域的高度,設定die屬性為true // 如果雨滴超出視覺區域就刪除掉if (e.posy >= canvasEl.clientHeight) { e.die = true; } else { // 逐漸增加雨滴y座標的值e.posy = e.posy + e.speed; // 變化雨滴x座標// * speedx 用來控制雨滴下落方向// 使雨滴下墜方向和滑鼠移動方向相同e.posx = e.posx + e.speed * speedx; } }); // 刪除die屬性為ture的陣列成員// 滑鼠區域內的,超過結束線的,可視區域外的雨滴刪除掉for (var i = linelist.length - 1; i >= 0; i--) { if (linelist[i] .die) { linelist.splice(i, 1); } } // 渲染render(); // 遞迴呼叫update,實作動畫效果window.requestAnimationFrame(update); } //渲染function render() { // 畫一個和視覺區域一樣大的矩形ctx.fillStyle = backgroundColor; ctx.fillRect(0, 0, canvasEl.width, canvasEl.height); // 畫雨滴效果ctx.lineWidth = 5; linelist.forEach(function (line) { ctx.strokeStyle = line.color; ctx.beginPath(); ctx.moveTo(line.posx, line.posy); // * speedx 用來控制雨滴方向// 使雨滴方向和滑鼠移動方向相同ctx.lineTo(line.posx + line.h * speedx, line.posy + line.h); ctx.stroke(); }); // 畫雨滴散開形成小水珠效果ctx.lineWidth = 1; ctx.strokeStyle = #fff; dropList.forEach(function (e) { ctx.beginPath(); ctx.arc(e.posx, e.posy, e.radius, Math.random() * Math.PI * 2, 1 * Math.PI); ctx.stroke(); }); // 解開註釋,可見滑鼠範圍/* ctx.beginPath(); ctx.arc(mousePos[0], mousePos[1], mouseDis, 0, 2 * Math.PI); ctx.stroke(); */ } } </script></body>< /html>
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。