物理動畫
這裡物理動畫說的是,圖形的動畫效果和實際生活中的物理效果類似,比如說重力影響的效果。
其實動畫的實現就是把一個動作拆分成間隔連續的去完成, 視覺上就感覺是在動的了。
比如把一個圖從左邊移到右邊, 設總距離100的話, 每隔 1/40秒 移到 1 的話,效果就會有了。
這個時間間隔和距離間隔設置多少為最佳,美學上是有一些定義的,這裡就不探討了。
一個拽動和撕扯窗布的例子
三個文件
tearCloth.html ; tearCloth.css ; tearCloth.js
tearCloth.html
<!--Add by oscar999--> <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE> New Document </TITLE> <META NAME="Author" CONTENT="oscar999"> <link rel="stylesheet" type="text/css" href="tearCloth.css" /> </HEAD> <BODY> <canvas id = "c" > </canvas> <center id="info"> <center id="top"> <a id="close" href="">close</a> </center> <p> <br> - Tear the cloth with your mouse.<br><br> - Right click and drag to cut the cloth<br><br> - Reduce physics_accuracy if it's laggy.<br><br> </p> </center> <script src="tearCloth.js"></script> </BODY> </HTML>
tearCloth.css
* { margin: 0; overflow:hidden; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; -o-user-select: none; user-select: none; } body { background:#333; } canvas { background:#333; width:100%; height:376px; margin:0 auto; display:block; } #info { position:absolute; left:-1px; top:-1px; width:auto; max-width:380px; height:auto; background:#f2f2f2; border-bottom-right-radius:10px; } #top { background:#fff; width:100%; height:auto; position:relative; border-bottom:1px solid #eee; } p { font-family:Arial, sans-serif; color:#666; text-align:justify; font-size: 16px; margin:10px; } a { font-family:sans-serif; color:#444; text-decoration:none; font-size: 20px; } #site { float:left; margin: 10px; color: #38a; border-bottom:1px dashed #888; } #site:hover { color: #7af; } #close { float:right; margin: 10px; } #p { font-family: Verdana, sans-serif; position:absolute; right:10px; bottom:10px; color:#adf; border: 1px dashed #555; padding:4px 8px; }
tearCloth.js
document.getElementById('close').onmousedown = function(e) { e.preventDefault(); document.getElementById('info').style.display = 'none'; return false; }; // settings var physics_accuracy = 6, mouse_influence = 20, mouse_cut = 5, gravity = 900, cloth_height = 30, cloth_width = 50, start_y = 20, spacing = 7, tear_distance = 60; window.requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60); }; var canvas, ctx, cloth, boundsx, boundsy, mouse = { down: false, button: 1, x: 0, y: 0, px: 0, py: 0 }; window.onload = function() { canvas = document.getElementById('c'); ctx = canvas.getContext('2d'); canvas.width = canvas.clientWidth; canvas.height = 376; canvas.onmousedown = function(e) { mouse.button = e.which; mouse.px = mouse.x; mouse.py = mouse.y; var rect = canvas.getBoundingClientRect(); mouse.x = e.clientX - rect.left, mouse.y = e.clientY - rect.top, mouse.down = true; e.preventDefault(); }; canvas.onmouseup = function(e) { mouse.down = false; e.preventDefault(); }; canvas.onmousemove = function(e) { mouse.px = mouse.x; mouse.py = mouse.y; var rect = canvas.getBoundingClientRect(); mouse.x = e.clientX - rect.left, mouse.y = e.clientY - rect.top, e.preventDefault(); }; canvas.oncontextmenu = function(e) { e.preventDefault(); }; boundsx = canvas.width - 1; boundsy = canvas.height - 1; ctx.strokeStyle = 'rgba(222,222,222,0.6)'; cloth = new Cloth(); update(); }; var Point = function(x, y) { this.x = x; this.y = y; this.px = x; this.py = y; this.vx = 0; this.vy = 0; this.pin_x = null; this.pin_y = null; this.constraints = []; }; Point.prototype.update = function(delta) { if (mouse.down) { var diff_x = this.x - mouse.x, diff_y = this.y - mouse.y, dist = Math.sqrt(diff_x * diff_x + diff_y * diff_y); if (mouse.button == 1) { if(dist < mouse_influence) { this.px = this.x - (mouse.x - mouse.px) * 1.8; this.py = this.y - (mouse.y - mouse.py) * 1.8; } } else if (dist < mouse_cut) this.constraints = []; } this.add_force(0, gravity); delta *= delta; nx = this.x + ((this.x - this.px) * .99) + ((this.vx / 2) * delta); ny = this.y + ((this.y - this.py) * .99) + ((this.vy / 2) * delta); this.px = this.x; this.py = this.y; this.x = nx; this.y = ny; this.vy = this.vx = 0 }; Point.prototype.draw = function() { if (this.constraints.length <= 0) return; var i = this.constraints.length; while(i--) this.constraints[i].draw(); }; Point.prototype.resolve_constraints = function() { if (this.pin_x != null && this.pin_y != null) { this.x = this.pin_x; this.y = this.pin_y; return; } var i = this.constraints.length; while(i--) this.constraints[i].resolve(); this.x > boundsx ? this.x = 2 * boundsx - this.x : 1 > this.x && (this.x = 2 - this.x); this.y < 1 ? this.y = 2 - this.y : this.y > boundsy && (this.y = 2 * boundsy - this.y); }; Point.prototype.attach = function(point) { this.constraints.push( new Constraint(this, point) ); }; Point.prototype.remove_constraint = function(lnk) { var i = this.constraints.length; while(i--) if(this.constraints[i] == lnk) this.constraints.splice(i, 1); }; Point.prototype.add_force = function(x, y ) { this.vx += x; this.vy += y; }; Point.prototype.pin = function(pinx, piny) { this.pin_x = pinx; this.pin_y = piny; }; var Constraint = function(p1, p2) { this.p1 = p1; this.p2 = p2; this.length = spacing; }; Constraint.prototype.resolve = function() { var diff_x = this.p1.x - this.p2.x, diff_y = this.p1.y - this.p2.y, dist = Math.sqrt(diff_x * diff_x + diff_y * diff_y), diff = (this.length - dist) / dist; if (dist > tear_distance) this.p1.remove_constraint(this); var px = diff_x * diff * 0.5; var py = diff_y * diff * 0.5; this.p1.x += px; this.p1.y += py; this.p2.x -= px; this.p2.y -= py; }; Constraint.prototype.draw = function() { ctx.moveTo(this.p1.x, this.p1.y); ctx.lineTo(this.p2.x, this.p2.y); }; var Cloth = function() { this.points = []; var start_x = canvas.width / 2 - cloth_width * spacing / 2; for(var y = 0; y <= cloth_height; y++) { for(var x = 0; x <= cloth_width; x++) { var p = new Point(start_x + x * spacing, start_y + y * spacing); x != 0 && p.attach(this.points[this.points.length - 1]); y == 0 && p.pin(p.x, p.y); y != 0 && p.attach(this.points[x + (y - 1) * (cloth_width + 1)]) this.points.push(p); } } }; Cloth.prototype.update = function() { var i = physics_accuracy; while(i--) { var p = this.points.length; while(p--) this.points[p].resolve_constraints(); } i = this.points.length; while(i--) this.points[i].update(.016); }; Cloth.prototype.draw = function() { ctx.beginPath(); var i = cloth.points.length; while(i--) cloth.points[i].draw(); ctx.stroke(); }; function update() { ctx.clearRect(0, 0, canvas.width, canvas.height); cloth.update(); cloth.draw(); requestAnimFrame(update); }
打開html 文件就可以看到效果了,最好是在Chrome和Firefox 下查看效果。
也可以通過以下link 在線看效果:
http://lonely-pixel.com/lab/cloth/
其他的例子
除了以上撕扯窗布的效果,還可以看一些更多的類似效果
http://lonely-pixel.com/
框架
在這個JS框架橫行的年代,你有可能會問,有沒有一個JS框架,讓讓我很容易的實現這些效果。
答案是肯定的: 有,
verlet-js 就是比較好一個。
verlet-js的定位就是一個簡單的使用javascript實現的物理引擎。
官方的網址是:http://subprotocol.com/verlet-js/
可以看的例子有。
Examples
Shapes (Hello world):http://subprotocol.com/verlet-js/examples/shapes.html
Fractal Trees:http://subprotocol.com/verlet-js/examples/tree.html
Cloth:http://subprotocol.com/verlet-js/examples/cloth.html
Spiderweb:http://subprotocol.com/verlet-js/examples/spiderweb.html
那個蜘蛛在網上爬行的例子很炫。