當前位置:
首頁 > 知識 > canvas實現漂亮的下雨效果

canvas實現漂亮的下雨效果

說明

這篇文章說如何用canvas畫出漂亮的下雨效果,先看看最後實現的效果吧。

效果圖

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 ,是這樣的效果

canvas實現漂亮的下雨效果

如果用speedx ,是這樣的效果

canvas實現漂亮的下雨效果

雨滴y坐標:原y坐標的值 + speed

speed 和上面x坐標中提到的一樣,是一個固定值,表示雨滴下落速度,

好的,最後就是用canvas根據雨滴對象的坐標,畫兩個點了,然後連起來,雨滴就畫出來了

第一個點坐標比較簡單,直接獲取雨滴對象的x坐標和y坐標,就是這個點的坐標

第二個點的坐標:

x坐標 = 雨滴x坐標的值 + 雨滴長度 * speedx

y坐標 = 雨滴y坐標的值 + 雨滴長度

最後把這兩個點連起來,就有一條線了,就是一個雨滴了

當設置x坐標時,又用上了變數 speedx,這是為了讓 雨滴方向 和 雨滴下落方向相同,

當不用 speedx時,是這樣

canvas實現漂亮的下雨效果

當用上speedx時,是這樣

canvas實現漂亮的下雨效果

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實現漂亮的下雨效果

最後就是用canvas根據小水珠的坐標屬性和半徑屬性畫圓弧就可以了,弧度是隨機的

3、雨滴下落到滑鼠坐標一定範圍內,散成小水珠,同樣的,小水珠的移動方向也和滑鼠移動方向相同

canvas實現漂亮的下雨效果

確定圖中圓的大小容易,假設圓的半徑是35,我們能獲取到滑鼠的坐標,以滑鼠的坐標為圓心,35為半徑,就確定了圓的大小。

重點在於如何判斷,雨滴是不是進入了這個範圍,這就要用勾股定理了,看圖。

canvas實現漂亮的下雨效果

因為雨滴是兩個點連起來的一條線,要看雨滴是不是進入了這個範圍內, 就是看雨滴靠下邊的點的坐標,到滑鼠的直線距離是多少,就是圖中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" stylex="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 * e.h 是雨滴x坐標

// e.posy + e.h 是雨滴y坐標

var dis = Math.sqrt(((e.posx + speedx * e.h) - mousePos[0]) * ((e.posx + speedx * e.h) - mousePos[0]) + (e.posy + e.h - mousePos[1]) * (e.posy + e.h - mousePos[1]));

// 如果在mouseDis區域內,就刪除雨滴,畫一些小水珠(圓弧)

// 實現滑鼠碰到雨滴,雨滴散成小水珠的效果

if (dis < mouseDis) {

// 刪除 雨滴

e.die = true;

// 畫一些小水珠(圓弧)

madedrops(e.posx + speedx * e.h, e.posy + e.h);

}

// 如果雨滴超過 結束線,刪除雨滴,畫一些小水珠(圓弧)

if ((e.posy + e.h) > endLine) {

e.die = true;

madedrops(e.posx + speedx * e.h, e.posy + e.h);

}

// 如果 雨滴 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>

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

最後說一句

這個特效的作者是 sxq111本尊 大神,我只是站在巨人的肩膀上,喜歡的小夥伴們可以去github上給個小star哦。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

SQL中case when then else end用法
WebAssembly 的未來:將逐漸解鎖整個「技能樹」

TAG:程序員小新人學習 |