HTML 5 キャンバス タグを使用して水流とプールのアニメーションを描画します
HTML 5 キャンバスを使用してアニメーション化する前に、まずキャンバスを含む基本的な知識を紹介します (キャンバスに慣れている場合は、セクションを直接スキップできます) )、oCanvas フレームワーク、スプライト アニメーション。上記の基礎知識を理解したら、アニメーション用の Canvas を使い始めることができます。
この部分には多くのものがあり、複雑です。ここでは、車輪の再発明はしません。 HTML 5 キャンバスで描画する方法については、非常に優れたブログ投稿が詳しく紹介されているので、注意深く読むことをお勧めします。
Canvas タグは非常に強力で、画像だけでなくピクセルレベルの処理も行うことができます。ただし、Canvas はまだ開発されていません。完成度が高く、API が未完成であり、イベント処理機能などのインターフェースが提供されていないため、複雑な機能を実装するには依然として多大な労力が必要です。そのため、多くのサードパーティ製のキャンバスベースのフレームワークが登場しています。ネイティブの Canvas タグと比較すると、これらのフレームワークは使いやすいため、コーディングの効率が大幅に向上します。関連する使用法ドキュメントとデモは上記のリンクでご覧いただけます。
エルフ アニメーションは通常、一連のカスタム属性値と 3 つのサブ関数 (init、advanced、draw) で構成されます。
3つの関数の役割は以下の通りです:
init: スプライトアニメーションの属性値を初期化します
advance: 次のフレームのステータス値を更新してから次のフレームを描画します
draw: で更新されたステータス値を描画しますキャンバスへのadvanced関数
上記の3つの関数の実行順序は、init->advance->draw->advance->draw->...となり、ループが継続します。上昇するバブルをランダムに生成する例を使用して、上記の実行プロセスを説明します。
var constructor_bubble = function (settings, core) { return oCanvas.extend({ core: core, shapeType: "rectangular",//下面定义了上面我们提到的三个函数:init(),advance(),draw()//在init中,我们map对象组、一个空的数组和一个代表高度的属性值 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函数,在函数中我们利用if逻辑判断是否添加新的气泡以及进行气泡的位置更新,points数组利用队列的先进先出来存储气泡的 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函数中,利用canvas的圆弧绘制指令,将points数组中存储的气泡依次画出 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");//下面是在应用中定义和添加上面定义的精灵动画,其中:start数组代表了气泡的产生点,container代表了气泡的存在区域var pp1=canvas.display.bubble({ start:{x:425,y:566}, container:SC02, width:50, offset:1, speed:5 }).add();
以下は、プロジェクトに水の流れのアニメーションとプールのアニメーションを実装する方法の詳細な手順を詳しく紹介します:
var constructor_show = function (settings, core) { return oCanvas.extend({ core: core, shapeType: "rectangular",//上面四行都是oCanvas框架的结构语法/*下面init()、advance()、draw()分别是上节中说的动画精灵三元素,第一个用来初始化,第二个用来 更新操作,第三个用来绘制图像/动画在管道对象中,定义了一些属性,包括:x、y、height、width、start、 height_now、full、speed、fill、trail_flag、[trail]。其中x、y分别代表水池参考点相对画布左 上角的位置,height、width是水池的宽高属性,start表征了动画是否开始,height_now代表了水池中水 位的高度,full表征了水池是否填满,speed水池上涨的速度,fill水的颜色,trail_flag表征了该水池 是否是一个标准的矩形,如果不是的话,配合trail属性,指定水池的轮廓*/ init: function () { //默认动画关闭,水池full为0,当前高度为0 this.start=0; this.full=0; this.height_now=0; }, advance: function () { //如果水池未满并且是开启状态,水位未满就更新当前高度,否则将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) { //如果是不规则图形,描出轮廓 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 !== "") { //设置颜色,绘制矩形水池 canvas.fillStyle = this.fill; canvas.fillRect(x, y + this.Height - this.height_now, this.Width, this.height_now); } canvas.closePath(); } }, settings); };//将上面的动画精灵注册进oCanvas的display图形库中oCanvas.registerDisplayObject("SC_show", constructor_show, "init");
パイプラインの水の流れモデルでは、次の属性が定義されています:
destination: 現在の水の流れの前端の位置
cells: パイプラインのパス配列
deta: 方向 斜辺の長さ
deta_x: 方向x辺の長さ
deta_y: y方向の辺の長さ
flag_x: コサイン値
flag_y: サイン値
cellIndex: 現在の描画エッジのインデックス
Speed: 水流速度
GDwidth: 水流幅
LineHeight: 水流長さ
x_now:現在の描画点 x 座標
y_now: 現在の描画点 y 座標
firstX: パイプ番号 A 点座標 x
firstY: パイプラインの最初の点の座標 y
beginHeight: 水の流れの最初のセクションの開始点
endHeight : パイプラインの前のセクションに残った未完成の給水ライン
legacyHeight: 前のパイプラインの長さで残されたフロントエンドポイント
paused: 開始するかどうか
fill: 水の流れの色
full: 埋めるかどうか
//init函数主要完成初始化工作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函数主要实现每次动画的刷新步进操作 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函数在每次advance之后执行,将每次的步进更新重新绘制到画布上 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(); }
次のコードは、プール オブジェクトを定義し、対応する属性値を割り当て、最後に定義されたオブジェクトをキャンバスに追加します。
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);
次のコードは、パイプライン オブジェクトを定義し、いくつかの初期値をパイプライン オブジェクトに割り当て、最後にキャンバスに追加します。
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);
特定のアニメーションのプロセス制御は次のとおりです:
canvas.setLoop(function () { //下面6个advance函数实现每一帧中的动画对象的更新操作 GD01.advance(); SC01.advance(); SC02.advance(); GD02.advance(); SC03.advance(); GD03.advance(); //下面几个if语句实现动画的流程控制 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(); //重绘画布 }).start(); //循环开始
【関連推奨事項】
1. 特別な推奨事項: 「php Programmer Toolbox」V0.1バージョンのダウンロード
2 落ちる雪の結晶を実装するための h5canvas 特殊効果コード
3. h5 Canvas の塗りつぶしとストロークのテキスト効果の例
以上がキャンバスを使用して水の流れとプールのアニメーションを実装するコードを共有します。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。