本文作者html5tricks,转载请注明出处
还记得很早以前向大家分享的这款HTML5 Canvas模拟衣服撕扯动画吗?这绝对是一款非常具有创意而且很好玩的HTML5动画。今天我们来分享一下它的3D版本,在原来的基础上,衣服布料呈3D环形显示,你同样可以用鼠标拖拽衣服,不过和之前不同的是,鼠标左键用来拖拽衣服,鼠标右键用来切割衣服,失去了之前鼠标用力程度和衣服破碎程度的关系,似乎功能上有所缺失,但是HTML5 3D功能还是不错的。
下面我们来简单分享一下实现的源码,代码主要由HTML和JavaScript组成,主要还是JavaScript。
HTML代码:
<canvas width="800" height="800" id="c"></canvas>
只是定义了一个canvas标签,非常简单。
JavaScript代码:
canvas = document.getElementById('c'); ctx = canvas.getContext('2d'); //settings var physics_acc = 5, tear_distance = 40, auto_rotate = 1, field_of_view = 500, gravity = 0.2, friction = 0.99, cloth_rows = 20, pm_locked_c = 39; //pointmass locked count - prevents the cloth from falling to the ground //arrays var pointmass = [], constraints = []; vertex = []; //random var halfx = canvas.width / 2, halfy = canvas.height / 2, rotatex = 0, rotatey = 0, rotatez = 0, mouse = { down: false, x: 0, y: 0, ox: 0, oy: 0, button: 0 }; ctx.lineWidth = 0.5; ctx.strokeStyle = "#ddd"; window.requestAnimFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) { window.setTimeout(callback, 1000 / 60); }; function init() { //create top row for(i = 0; i < 40; i++) { x = Math.cos(2 * Math.PI * (i / 40)) * 100; z = Math.sin(2 * Math.PI * (i / 40)) * 100; create_pointmass(x, cloth_rows / 2 * -20, z); } //rest for(i = 0; i < 40; i++) { x = Math.cos(2 * Math.PI * (i / 40)) * 100; z = Math.sin(2 * Math.PI * (i / 40)) * 100; for(y = cloth_rows / 2 * -20 + 20; y < cloth_rows / 2 * 20; y+=20) { create_pointmass(x,y,z); } } //create constraints based on distance for(i = 0; i < pointmass.length; i++) { for(c = i + 1; c < pointmass.length; c++) { dist = Math.sqrt( Math.pow(pointmass[i][0] - pointmass[c][0], 2) + Math.pow(pointmass[i][1] - pointmass[c][1], 2) + Math.pow(pointmass[i][2] - pointmass[c][2], 2)); if(dist < 21) { create_constraint(i,c); } } } //mouse canvas.onmousemove = function(e) { mouse.ox = mouse.x; mouse.oy = mouse.y; mouse.x = e.pageX - canvas.offsetLeft, mouse.y = e.pageY - canvas.offsetTop; e.preventDefault(); }; canvas.onmousedown = function(e) { mouse.button = e.which; mouse.down = true; e.preventDefault(); }; canvas.oncontextmenu = function(e) { e.preventDefault(); }; canvas.onmouseup = function(e) { mouse.down = false; e.preventDefault(); }; render() } function draw3D() { vertex = []; for(i = 0; i < pointmass.length; i++) { vertex.push([pointmass[i][0],pointmass[i][1],pointmass[i][2]]) } if(auto_rotate == 1)rotatey+= 0.01; for(i = 0; i < vertex.length; i++) { xyz = vertex[i]; x = xyz[0]; y = xyz[1]; z = xyz[2]; xcosa = Math.cos(rotatex); xsina = Math.sin(rotatex); ycosa = Math.cos(rotatey); ysina = Math.sin(rotatey); zcosa = Math.cos(rotatez); zsina = Math.sin(rotatez); xy = xcosa*y - xsina*z; //x xz = xsina*y + xcosa*z; yz = ycosa*xz - ysina*x; //y yx = ysina*xz + ycosa*x; zx = zcosa*yx - zsina*xy; //z zy = zsina*yx + zcosa*xy; xyz[0] = zx; xyz[1] = zy; xyz[2] = yz; } ctx.beginPath(); for(i = 0; i < constraints.length; i++) { for(c = 0; c < 2; c++) { xyz = vertex[constraints[i][c]]; fov = field_of_view / (field_of_view + xyz[2]); x = xyz[0] * fov + halfx; y = xyz[1] * fov + halfy; if(c == 0) { ctx.moveTo(x,y); } else { ctx.lineTo(x,y); } } } ctx.closePath(); ctx.stroke(); } function update_pointmass() { for(i = 0; i < pointmass.length; i++) { x = pointmass[i][0]; y = pointmass[i][1]; z = pointmass[i][2]; ox = pointmass[i][3]; oy = pointmass[i][4]; oz = pointmass[i][5]; dx = x - ox; dy = y - oy; dz = z - oz; if(i > pm_locked_c) { pointmass[i][3] = x; pointmass[i][4] = y; pointmass[i][5] = z; pointmass[i][0] = x + dx * friction; pointmass[i][1] = y + dy + gravity; pointmass[i][2] = z + dz * friction; } else { pointmass[i][0] = ox; pointmass[i][1] = oy; pointmass[i][2] = oz; } } } function update_constraint() { for(i = 0; i < physics_acc; i++) { for(c = 0; c < constraints.length; c++) { c1 = pointmass[constraints[c][0]]; c2 = pointmass[constraints[c][1]]; diffx = c1[0] - c2[0]; diffy = c1[1] - c2[1]; diffz = c1[2] - c2[2]; dist = Math.sqrt(diffx * diffx + diffy * diffy + diffz * diffz); diff = (constraints[c][2] - dist) / dist; dx = c1[0] - c2[0]; dy = c1[1] - c2[1]; dz = c1[2] - c2[2]; dx = dx * 0.5; dy = dy * 0.5; dz = dz * 0.5; c1[0] = c1[0] + dx * diff; c1[1] = c1[1] + dy * diff; c1[2] = c1[2] + dz * diff; c2[0] = c2[0] - dx * diff; c2[1] = c2[1] - dy * diff; c2[2] = c2[2] - dz * diff; constraints[c][3] = dist; if(dist > tear_distance) { constraints.splice(c, 1); } } } } function create_pointmass(x,y,z) { pointmass.push([x, y, z, x, y, z]); } function create_constraint(f,s) { constraints.push([f, s, Math.sqrt( Math.pow((pointmass[f][0] - pointmass[s][0]), 2) + Math.pow((pointmass[f][1] - pointmass[s][1]), 2) + Math.pow((pointmass[f][2] - pointmass[s][2]), 2)), 1]) } function render() { ctx.clearRect(0, 0, canvas.width, canvas.height); update_mouse(); update_pointmass(); update_constraint(); draw3D(); requestAnimFrame(render); } function update_mouse() { if(mouse.down == true) { for(i = 0; i < pointmass.length; i++) { dist = Math.sqrt(Math.pow((vertex[i][0] - (mouse.x - halfx)), 2) + Math.pow((vertex[i][1] - (mouse.y - halfy)), 2) + Math.pow((vertex[i][2] + 50), 2)); if(dist < 100 && mouse.button == 1) { pointmass[i][3] = pointmass[i][3] - Math.min(1, (mouse.x - mouse.ox) / 10); pointmass[i][4] = pointmass[i][4] - Math.min(1, (mouse.y - mouse.oy) / 10); } //lazy cut for m2 if(dist < 21 && mouse.button == 3) { pointmass[i][4] = -1000; pointmass[i][3] = -1000; } } } }
最后在页面加载完后调用一下初始化的代码即可:
<script type="text/javascript"> init(); </script>
何等 牛b啊