您好,登錄后才能下訂單哦!
用canvas實(shí)現(xiàn)水流和水池動(dòng)畫的案例?這個(gè)問題可能是我們?nèi)粘W(xué)習(xí)或工作經(jīng)常見到的。希望通過這個(gè)問題能讓你收獲頗深。下面是小編給大家?guī)淼膮⒖純?nèi)容,讓我們一起來看看吧!
利用Html 5的canvas標(biāo)簽繪制水流和水池動(dòng)畫
在利用HTML 5的canvas進(jìn)行動(dòng)畫繪制之前,首先先介紹一下基本知識(shí),包括canvas(如果對(duì)canvas比較熟悉的可以直接跳過該節(jié))、oCanvas框架、精靈動(dòng)畫。在了解了以上的基本知識(shí)后,就可以開始利用canvas做動(dòng)畫了。
canvas標(biāo)簽功能非常強(qiáng)大,既可以處理圖片,又可以進(jìn)行像素級(jí)的處理,完全可以取代瀏覽器端的flash,但是由于canvas發(fā)展還未完善,API還不是太全,元素的事件處理功能等還沒有提供接口,在實(shí)現(xiàn)一些復(fù)雜功能時(shí),尚需耗費(fèi)許多精力,于是出現(xiàn)了許多第三方的基于canvas 的框架,這些框架相比于原生的canvas標(biāo)簽,有了更多簡(jiǎn)單易用的API,大大提高了我們編碼的效率,在這里我選用的是oCanvas框架,相關(guān)的使用文檔和demo都可以去上面的鏈接中查看。
精靈動(dòng)畫一般由一組自定義的屬性值和3個(gè)子函數(shù)組成(init、advance、draw)。
三個(gè)函數(shù)的作用分別如下:
init:初始化精靈動(dòng)畫的屬性值
advance:再畫下一幀之前首先更新下一幀的狀態(tài)值
draw:將advance函數(shù)中更新的狀態(tài)值繪制到畫布中
以上三個(gè)函數(shù)的執(zhí)行順序是:init->advance->draw->advance->draw->…..一直循環(huán)下去。下面我以一個(gè)隨機(jī)產(chǎn)生上升氣泡的例子說明一下上面的執(zhí)行過程。
var constructor_bubble = function (settings, core) { return oCanvas.extend({ core: core, shapeType: "rectangular",//下面定義了上面我們提到的三個(gè)函數(shù):init(),advance(),draw()//在init中,我們map對(duì)象組、一個(gè)空的數(shù)組和一個(gè)代表高度的屬性值 init: function () { this.map=[ {r:2,speed:3}, {r:3,speed:3}, {r:4,speed:3}, {r:5,speed:3}, {r:6,speed:3}, {r:7,speed:3}, {r:8,speed:3}, {r:9,speed:3}, {r:10,speed:3} ]; this.points=[]; this.height=this.container.height_now; },//下面是advance函數(shù),在函數(shù)中我們利用if邏輯判斷是否添加新的氣泡以及進(jìn)行氣泡的位置更新,points數(shù)組利用隊(duì)列的先進(jìn)先出來存儲(chǔ)氣泡的 advance: function () { this.height=this.container.height_now; if(Math.random()>0.95){ var new_point={ x:this.start.x+this.offset*2*(Math.random()-0.5), y:this.start.y-this.map[0].r, r:this.map[0].r }; this.points.push(new_point); } if(this.points.length>0){ for(var i=0;i<this.points.length;i++){ this.points[i].x+=this.offset*2*(Math.random()-0.5); this.points[i].y-=3; if(this.start.y-this.points[i].y>this.height-this.points[i].r-33){ this.points.shift(); } } } },//draw函數(shù)中,利用canvas的圓弧繪制指令,將points數(shù)組中存儲(chǔ)的氣泡依次畫出 draw: function () { var canvas = this.core.canvas; canvas.lineJoin = 'round'; canvas.lineWidth = this.GDwidth; canvas.strokeStyle = "#fff"; if(this.points.length>0){ for(var i=0;i<this.points.length;i++){ canvas.beginPath(); canvas.arc(this.points[i].x,this.points[i].y,5,0,2*Math.PI); canvas.stroke(); canvas.closePath(); } } } }, settings); }; oCanvas.registerDisplayObject("bubble", constructor_bubble, "init");//下面是在應(yīng)用中定義和添加上面定義的精靈動(dòng)畫,其中:start數(shù)組代表了氣泡的產(chǎn)生點(diǎn),container代表了氣泡的存在區(qū)域var pp1=canvas.display.bubble({ start:{x:425,y:566}, container:SC02, width:50, offset:1, speed:5 }).add();
下面詳細(xì)介紹一下,項(xiàng)目中如何實(shí)現(xiàn)水流動(dòng)畫和水池動(dòng)畫的詳細(xì)步驟:
var constructor_show = function (settings, core) { return oCanvas.extend({ core: core, shapeType: "rectangular",//上面四行都是oCanvas框架的結(jié)構(gòu)語(yǔ)法/*下面init()、advance()、draw()分別是上節(jié)中說的動(dòng)畫精靈三元素,第一個(gè)用來初始化,第二個(gè)用來 更新操作,第三個(gè)用來繪制圖像/動(dòng)畫在管道對(duì)象中,定義了一些屬性,包括:x、y、height、width、start、 height_now、full、speed、fill、trail_flag、[trail]。其中x、y分別代表水池參考點(diǎn)相對(duì)畫布左 上角的位置,height、width是水池的寬高屬性,start表征了動(dòng)畫是否開始,height_now代表了水池中水 位的高度,full表征了水池是否填滿,speed水池上漲的速度,fill水的顏色,trail_flag表征了該水池 是否是一個(gè)標(biāo)準(zhǔn)的矩形,如果不是的話,配合trail屬性,指定水池的輪廓*/ init: function () { //默認(rèn)動(dòng)畫關(guān)閉,水池full為0,當(dāng)前高度為0 this.start=0; this.full=0; this.height_now=0; }, advance: function () { //如果水池未滿并且是開啟狀態(tài),水位未滿就更新當(dāng)前高度,否則將full置為1 if(this.start==1&&this.full!=1){ if (this.height_now < this.Height) { this.height_now += this.speed; } else { this.full = 1; } } }, draw: function () { var canvas = this.core.canvas, //先獲得水池的位置 origin = this.getOrigin(), x = this.abs_x - origin.x, y = this.abs_y - origin.y; //開始繪制 canvas.beginPath(); canvas.strokeStyle = "#000"; if (this.trail_flag == 1) { //如果是不規(guī)則圖形,描出輪廓 canvas.moveTo(this.trail[0].x_t, this.trail[0].y_t); for (var i = 1; i < this.trail.length; i++) { canvas.lineTo(this.trail[i].x_t, this.trail[i].y_t); } canvas.lineTo(this.trail[0].x_t, this.trail[0].y_t); canvas.clip(); } if (this.fill !== "") { //設(shè)置顏色,繪制矩形水池 canvas.fillStyle = this.fill; canvas.fillRect(x, y + this.Height - this.height_now, this.Width, this.height_now); } canvas.closePath(); } }, settings); };//將上面的動(dòng)畫精靈注冊(cè)進(jìn)oCanvas的display圖形庫(kù)中oCanvas.registerDisplayObject("SC_show", constructor_show, "init");
在管道水流模型中,定義了以下的屬性:
destination: 當(dāng)前水流最前端的位置
cells: 管道路徑數(shù)組
deta: 方向斜邊長(zhǎng)
deta_x: 方向x邊長(zhǎng)
deta_y: 方向y邊長(zhǎng)
flag_x: 余弦值
flag_y:正弦值
cellIndex: 當(dāng)前繪制邊的index
Speed: 水流速度
GDwidth:水流寬度
LineHeight:水流長(zhǎng)度
x_now: 當(dāng)前繪制點(diǎn)x坐標(biāo)
y_now: 當(dāng)前繪制點(diǎn)y坐標(biāo)
firstX: 管道第一個(gè)點(diǎn)坐標(biāo)x
firstY: 管道第一個(gè)點(diǎn)坐標(biāo)y
beginHeight: 第一段水流的起點(diǎn)位置
endHeight: 上一段管道遺留未畫完的水線
legacyHeight: 最前端點(diǎn)在上一個(gè)管道留下的長(zhǎng)度
paused: 是否開始
fill:水流顏色
full:是否填滿
//init函數(shù)主要完成初始化工作init: function () { this.x_now = this.cells[0].x_cell; this.y_now = this.cells[0].y_cell; this.firstX = this.x_now; this.firstY = this.y_now; this.endHeight = 0; this.beginHeight = 0; this.paused=0; this.full=0; this.cellIndex = 0; this.destination.x_d = this.cells[0].x_cell; this.destination.y_d = this.cells[0].y_cell; this.legacyHeight = -1; this.LineHeight=10; this.Speed=2*this.LineHeight/20; }
//advance函數(shù)主要實(shí)現(xiàn)每次動(dòng)畫的刷新步進(jìn)操作 advance: function () { if(this.paused==1){ if (this.cellIndex < this.cells.length - 1) { this.deta_x = this.cells[this.cellIndex + 1].x_cell - this.cells[this.cellIndex].x_cell; this.deta_y = this.cells[this.cellIndex + 1].y_cell - this.cells[this.cellIndex].y_cell; this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y); this.flag_x = this.deta_x / this.deta; this.flag_y = this.deta_y / this.deta; if (this.legacyHeight >= 0) { this.cellIndex++; if (this.cellIndex < this.cells.length - 1) { this.deta_x = this.cells[this.cellIndex + 1].x_cell - this.cells[this.cellIndex].x_cell; this.deta_y = this.cells[this.cellIndex + 1].y_cell - this.cells[this.cellIndex].y_cell; this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y); this.flag_x = this.deta_x / this.deta; this.flag_y = this.deta_y / this.deta; this.destination.x_d = this.cells[this.cellIndex].x_cell + this.flag_x * this.legacyHeight; this.destination.y_d = this.cells[this.cellIndex].y_cell + this.flag_y * this.legacyHeight; if (Math.abs(this.destination.x_d - this.cells[this.cellIndex + 1].x_cell) > this.Speed * Math.abs(this.flag_x) || Math.abs(this.destination.y_d - this.cells[this.cellIndex + 1].y_cell) > this.Speed * Math.abs(this.flag_y)) { this.legacyHeight = -1; this.destination.x_d += this.flag_x * this.Speed; this.destination.y_d += this.flag_y * this.Speed; } else { if (this.flag_x == 0) { this.legacyHeight = this.Speed - Math.abs(this.destination.y_d - this.cells[this.cellIndex + 1].y_cell) / Math.abs(this.flag_y); } else { this.legacyHeight = this.Speed - Math.abs(this.destination.x_d - this.cells[this.cellIndex + 1].x_cell) / Math.abs(this.flag_x); } } } } else { this.destination.x_d += this.flag_x * this.Speed; this.destination.y_d += this.flag_y * this.Speed; if (Math.abs(this.destination.x_d - this.cells[this.cellIndex + 1].x_cell) >= this.Speed * Math.abs(this.flag_x) && Math.abs(this.destination.y_d - this.cells[this.cellIndex + 1].y_cell) >= this.Speed * Math.abs(this.flag_y)) { this.legacyHeight = -1; } else { if (this.flag_x == 0) { this.legacyHeight = this.Speed - Math.abs(this.destination.y_d - this.cells[this.cellIndex + 1].y_cell) / Math.abs(this.flag_y); } else { this.legacyHeight = this.Speed - Math.abs(this.destination.x_d - this.cells[this.cellIndex + 1].x_cell) / Math.abs(this.flag_x); } } } }else{ this.full=1; } this.deta_x = this.cells[1].x_cell - this.cells[0].x_cell; this.deta_y = this.cells[1].y_cell - this.cells[0].y_cell; this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y); this.flag_x = this.deta_x / this.deta; this.flag_y = this.deta_y / this.deta; if (this.paused == 1) { if (Math.abs(this.firstX - this.cells[0].x_cell) >= this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.firstY - this.cells[0].y_cell) >= this.LineHeight * Math.abs(this.flag_y)) { this.firstX = this.cells[0].x_cell; this.firstY = this.cells[0].y_cell; this.beginHeight = 0; } else { if (this.beginHeight < this.LineHeight) { if (this.beginHeight + this.Speed >= this.LineHeight) { this.beginHeight = this.LineHeight; } else { this.beginHeight += this.Speed; } this.firstX = this.cells[0].x_cell; this.firstY = this.cells[0].y_cell; } else if (this.beginHeight == this.LineHeight) { this.firstX += this.flag_x * this.Speed; this.firstY += this.flag_y * this.Speed; } } } } }
//draw函數(shù)在每次advance之后執(zhí)行,將每次的步進(jìn)更新重新繪制到畫布上 draw: function () { var canvas = this.core.canvas; this.x_now = this.firstX; this.y_now = this.firstY; this.deta_x = this.cells[1].x_cell - this.cells[0].x_cell; this.deta_y = this.cells[1].y_cell - this.cells[0].y_cell; this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y); var myEnd = false; this.flag_x = this.deta_x / this.deta; this.flag_y = this.deta_y / this.deta; canvas.beginPath(); canvas.lineJoin = 'round'; canvas.lineCap="round"; this.endHeight = 0; canvas.lineWidth = this.GDwidth / 4; canvas.strokeStyle = this.fill; if (this.beginHeight > 0) { canvas.moveTo(this.x_now, this.y_now); canvas.lineTo(this.x_now + this.flag_x * this.beginHeight, this.y_now + this.flag_y * this.beginHeight); } this.x_now += this.flag_x * (this.beginHeight + this.LineHeight); this.y_now += this.flag_y * (this.beginHeight + this.LineHeight); for (var i = 1; i <= this.cellIndex; i++) { myEnd = false; this.deta_x = this.cells[i].x_cell - this.cells[i - 1].x_cell; this.deta_y = this.cells[i].y_cell - this.cells[i - 1].y_cell; this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y); this.flag_x = this.deta_x / this.deta; this.flag_y = this.deta_y / this.deta; if (this.endHeight > 0) { canvas.moveTo(this.cells[i - 1].x_cell, this.cells[i - 1].y_cell); canvas.lineTo(this.cells[i - 1].x_cell + this.flag_x * (this.endHeight ), this.cells[i - 1].y_cell + this.flag_y * this.endHeight); this.x_now = this.cells[i - 1].x_cell + this.flag_x * (this.LineHeight + this.endHeight); this.y_now = this.cells[i - 1].y_cell + this.flag_y * (this.LineHeight + this.endHeight); } if (this.endHeight < 0) { this.endHeight = Math.abs(this.endHeight); this.x_now = this.cells[i - 1].x_cell + this.flag_x * (this.endHeight); this.y_now = this.cells[i - 1].y_cell + this.flag_y * (this.endHeight); } if (this.endHeight == 0 && i != 1) { this.x_now = this.cells[i - 1].x_cell; this.y_now = this.cells[i - 1].y_cell; } while (Math.abs(this.x_now - this.cells[i].x_cell) >= this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.y_now - this.cells[i].y_cell) >= this.LineHeight * Math.abs(this.flag_y)) { canvas.moveTo(this.x_now, this.y_now); canvas.lineTo(this.x_now + this.flag_x * this.LineHeight, this.y_now + this.flag_y * this.LineHeight); this.x_now += this.flag_x * this.LineHeight; this.y_now += this.flag_y * this.LineHeight; if (Math.abs(this.x_now - this.cells[i].x_cell) <= this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.y_now - this.cells[i].y_cell) <= this.LineHeight * Math.abs(this.flag_y)) { if (this.flag_x == 0) { this.endHeight = Math.abs(this.y_now - this.cells[i].y_cell) / Math.abs(this.flag_y) - this.LineHeight; } else { this.endHeight = Math.abs(this.x_now - this.cells[i].x_cell) / Math.abs(this.flag_x) - this.LineHeight; } //this.endHeight = (Math.abs(this.y_now - this.cells[i].y_cell) + Math.abs(this.x_now - this.cells[i].x_cell) - this.LineHeight * (Math.abs(this.flag_y) + Math.abs(this.flag_x)))/2; myEnd = true; break; } else { this.x_now += this.flag_x * this.LineHeight; this.y_now += this.flag_y * this.LineHeight; } } if (myEnd == false && Math.abs(this.x_now - this.cells[i].x_cell) <= this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.y_now - this.cells[i].y_cell) <= this.LineHeight * Math.abs(this.flag_y)) { canvas.moveTo(this.x_now, this.y_now); canvas.lineTo(this.cells[i].x_cell, this.cells[i].y_cell); //this.endHeight = this.LineHeight - Math.abs(this.x_now - this.cells[i].x_cell)*flag.x_flag - Math.abs(this.y_now - this.cells[i].y_cell)*flag.y_flag; if (this.flag_x == 0) { this.endHeight = this.LineHeight - Math.abs(this.y_now - this.cells[i].y_cell) / Math.abs(this.flag_y); } else { this.endHeight = this.LineHeight - Math.abs(this.x_now - this.cells[i].x_cell) / Math.abs(this.flag_x); } //this.endHeight = ( this.LineHeight * (Math.abs(this.flag_y) + Math.abs(this.flag_x)) - Math.abs(this.y_now - this.cells[i].y_cell) + Math.abs(this.x_now - this.cells[i].x_cell)) / 2; } } if (this.cellIndex < this.cells.length - 1) { myEnd = false; this.deta_x = this.cells[this.cellIndex+1].x_cell-this.destination.x_d; this.deta_y = this.cells[this.cellIndex+1].y_cell-this.destination.y_d; this.deta = Math.sqrt(this.deta_x * this.deta_x + this.deta_y * this.deta_y); if (this.deta > 0) { this.flag_x = this.deta_x / this.deta; this.flag_y = this.deta_y / this.deta; if (this.endHeight > 0) { canvas.moveTo(this.cells[this.cellIndex].x_cell, this.cells[this.cellIndex].y_cell); canvas.lineTo(this.cells[this.cellIndex].x_cell + this.flag_x * (this.endHeight ), this.cells[this.cellIndex].y_cell + this.flag_y * this.endHeight); this.x_now = this.cells[this.cellIndex].x_cell + this.flag_x * ( this.endHeight); this.y_now = this.cells[this.cellIndex].y_cell + this.flag_y * ( this.endHeight); if(Math.abs(this.destination.x_d-this.x_now)>this.LineHeight*Math.abs(this.flag_x)||Math.abs(this.destination.y_d-this.y_now)>this.LineHeight*Math.abs(this.flag_y)){ this.x_now+=this.LineHeight*this.flag_x; this.y_now+=this.LineHeight*this.flag_y; } else{ this.x_now=this.destination.x_d; this.y_now=this.destination.y_d; } if (this.endHeight < 0) { this.endHeight = Math.abs(this.endHeight); this.x_now = this.cells[this.cellIndex].x_cell + this.flag_x * (this.endHeight); this.y_now = this.cells[this.cellIndex].y_cell + this.flag_y * (this.endHeight); } if (this.endHeight == 0 && this.cellIndex > 0) { this.x_now = this.cells[this.cellIndex].x_cell; this.y_now = this.cells[this.cellIndex].y_cell; } if (this.cellIndex == 0) { this.x_now = this.firstX; this.y_now = this.firstY; if (this.beginHeight > 0) { canvas.moveTo(this.x_now, this.y_now); canvas.lineTo(this.x_now + this.flag_x * this.beginHeight, this.y_now + this.flag_y * this.beginHeight); } this.x_now += this.flag_x * (this.beginHeight + this.LineHeight); this.y_now += this.flag_y * (this.beginHeight + this.LineHeight); } while ((Math.abs(this.x_now - this.destination.x_d) >= this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.y_now - this.destination.y_d) >this.LineHeight * Math.abs(this.flag_y))||(Math.abs(this.x_now - this.destination.x_d) > this.LineHeight * Math.abs(this.flag_x) && Math.abs(this.y_now - this.destination.y_d) >=this.LineHeight * Math.abs(this.flag_y))) { canvas.moveTo(this.x_now, this.y_now); canvas.lineTo(this.x_now + this.flag_x * this.LineHeight, this.y_now + this.flag_y * this.LineHeight); this.x_now += this.flag_x * this.LineHeight; this.y_now += this.flag_y * this.LineHeight; if (Math.abs(this.x_now - this.destination.x_d)<= this.LineHeight * Math.abs(this.flag_x)&&Math.abs(this.y_now - this.destination.y_d) <= this.LineHeight * Math.abs(this.flag_y)) { myEnd = true; break; } else { this.x_now += this.flag_x * this.LineHeight; this.y_now += this.flag_y * this.LineHeight; } } if (myEnd == false && Math.abs(this.x_now - this.destination.x_d) < this.LineHeight * Math.abs(this.flag_x) || Math.abs(this.y_now - this.destination.y_d) < this.LineHeight * Math.abs(this.flag_y)) { canvas.moveTo(this.x_now, this.y_now); canvas.lineTo(this.destination.x_d, this.destination.y_d); } } } canvas.stroke(); canvas.closePath(); }
下面代碼在定義了一個(gè)水池對(duì)象,并賦予了相應(yīng)的屬性值,最后將定義的對(duì)象添加到canvas畫布中。
var SC01 = canvas.display.SC_show({ x: 326, y: 200, Width: 181, Height: 438, height_now: 0, trail_flag: 0, t: 0, fill: color_SC, speed:speed_SC, full:0, start:0 });canvas.addChild(SC01);
下面的代碼定義了一個(gè)管道對(duì)象,并且給管道對(duì)象賦予了一些初始值,最后添加到canvas畫布中。
var GD01 = canvas.display.GD({ x: 0, y: 0, destination: { x_d: 0, y_d: 0 }, cells: [ {x_cell: 195, y_cell: 587}, {x_cell: 335, y_cell: 587} ], deta: 1, deta_x: 1, deta_y: 0, flag_x: 1, flag_y: 0, cellIndex: 0, Speed: speed_all, GDwidth: width_all, LineHeight: 10, x_now: 0, y_now: 0, firstX: 0, firstY: 0, beginHeight: 0, endHeight: 0, legacyHeight: 0, paused: 1, fill:color_GD, full:0 }); canvas.addChild(GD01);
具體動(dòng)畫的流程控制如下:
canvas.setLoop(function () { //下面6個(gè)advance函數(shù)實(shí)現(xiàn)每一幀中的動(dòng)畫對(duì)象的更新操作 GD01.advance(); SC01.advance(); SC02.advance(); GD02.advance(); SC03.advance(); GD03.advance(); //下面幾個(gè)if語(yǔ)句實(shí)現(xiàn)動(dòng)畫的流程控制 if(GD01.full==1){ SC01.start = 1; SC02.start = 1; } if(SC02.full==1){ GD02.paused = 1; } if(GD02.full==1) { SC03.start = 1; arrow_1.start(); arrow_2.start(); } if(SC03.full==1) { GD03.paused = 1; } canvas.redraw(); ?。乩L畫布 }).start(); //循環(huán)開始
感謝各位的閱讀!看完上述內(nèi)容,你們對(duì)用canvas實(shí)現(xiàn)水流和水池動(dòng)畫的案例大概了解了嗎?希望文章內(nèi)容對(duì)大家有所幫助。如果想了解更多相關(guān)文章內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。