我们可以通过新的HTML5画布(Canvas)标签添加一个画布到网页中,以下代码用于定义一个 800 x 600 大小的画布。
再通过以下 JavaScript 代码控制在画布中的绘图。
// 通过ID取画布元素 var mc = document.getElementById("MyCanvas"); var ctx = null; var width = mc.width; var height = mc.height; var mouseX = 0; var mouseY = 0; var pmouseX = 0; var pmouseY = 0; var drawRate = 6; var frameCount = 0; var mousePressed = false; var PI = Math.PI; var TWO_PI = Math.PI * 2; var HALF_PI = Math.PI * 0.5; var worms; var colors = []; colors[0] = '#2cd9fe'; colors[1] = '#2cfecf'; colors[2] = '#373fdf'; colors[3] = '#88fe1f'; colors[4] = '#48d6ff'; colors[5] = '#b3fcff'; colors[6] = '#f76cad'; colors[7] = '#505083'; colors[8] = '#113a7e'; colors[9] = '#014050'; colors[10] = '#ccf3ef'; colors[11] = '#009437'; colors[12] = '#8fb300'; if(mc.getContext) { // 创建 context 对象 ctx = mc.getContext("2d"); // 鼠标移动事件 mc.addEventListener('mousemove', ev_mousemove, false); mc.addEventListener('mousedown', ev_mousedown, false); mc.addEventListener('mouseup', ev_mouseup, false); worms = new Array(); drawInternal(); } function ev_mousemove(ev) { if (ev.layerX || ev.layerX == 0) { // 在Firefox中取鼠标坐标 mouseX = ev.layerX; mouseY = ev.layerY; } else if (ev.offsetX || ev.offsetX == 0) { // 在Opera中取鼠标坐标 mouseX = ev.offsetX; mouseY = ev.offsetY; } } function ev_mousedown(ev) { mousePressed = true; } function ev_mouseup(ev) { mousePressed = false; } function drawInternal() { draw(); setTimeout('drawInternal()', drawRate); pmouseX = mouseX; pmouseY = mouseY; frameCount++; } // 清画布 function clear() { ctx.clearRect(0, 0, width, height); } // 圆形填充 function fillCircle(x, y, radius) { if(radius <= 0) radius = 0; ctx.beginPath(); ctx.arc(x, y, radius, 0, TWO_PI, true); ctx.closePath(); ctx.fill(); } // 伪随机数 function Marsaglia(i1, i2) { var z=i1 || 362436069, w= i2 || 521288629; var nextInt = function() { z=(36969*(z&65535)+(z >> 16)) & 0xFFFFFFFF; w=(18000*(w&65535)+(w >> 16)) & 0xFFFFFFFF; return (((z&0xFFFF) << 16) | (w&0xFFFF)) & 0xFFFFFFFF; }; this.nextDouble = function() { var i = nextInt() / 4294967296; return i < 0 ? 1 + i : i; }; this.nextInt = nextInt; } Marsaglia.createRandomized = function() { var now = new Date(); return new Marsaglia((now / 60000) & 0xFFFFFFFF, now & 0xFFFFFFFF); }; // Perlin Noise(由Ken Perlin发明的自然噪声生成算法) function PerlinNoise(seed) { var rnd = seed != undefined ? new Marsaglia(seed) : Marsaglia.createRandomized(); var i, j; var p = new Array(512); for(i=0;i < 256;++i) { p[i] = i; } for(i=0;i < 256;++i) { var t = p[j = rnd.nextInt() & 0xFF]; p[j] = p[i]; p[i] = t; } for(i=0;i < 256;++i) { p[i + 256] = p[i]; } function grad3d(i,x,y,z) { var h = i & 15; var u = h < 8 ? x : y, v = h < 4 ? y : h == 12||h == 14 ? x : z; return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v); } function grad2d(i,x,y) { var v = (i & 1) == 0 ? x : y; return (i&2) == 0 ? -v : v; } function grad1d(i,x) { return (i&1) == 0 ? -x : x; } function lerp(t,a,b) { return a + t * (b - a); } this.noise3d = function(x, y, z) { var X = Math.floor(x)&255, Y = Math.floor(y)&255, Z = Math.floor(z)&255; x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z); var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y, fz = (3-2*z)*z*z; var p0 = p[X]+Y, p00 = p[p0] + Z, p01 = p[p0 + 1] + Z, p1 = p[X + 1] + Y, p10 = p[p1] + Z, p11 = p[p1 + 1] + Z; return lerp(fz, lerp(fy, lerp(fx, grad3d(p[p00], x, y, z), grad3d(p[p10], x-1, y, z)), lerp(fx, grad3d(p[p01], x, y-1, z), grad3d(p[p11], x-1, y-1,z))), lerp(fy, lerp(fx, grad3d(p[p00 + 1], x, y, z-1), grad3d(p[p10 + 1], x-1, y, z-1)), lerp(fx, grad3d(p[p01 + 1], x, y-1, z-1), grad3d(p[p11 + 1], x-1, y-1,z-1)))); }; this.noise2d = function(x, y) { var X = Math.floor(x)&255, Y = Math.floor(y)&255; x -= Math.floor(x); y -= Math.floor(y); var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y; var p0 = p[X]+Y, p1 = p[X + 1] + Y; return lerp(fy, lerp(fx, grad2d(p[p0], x, y), grad2d(p[p1], x-1, y)), lerp(fx, grad2d(p[p0 + 1], x, y-1), grad2d(p[p1 + 1], x-1, y-1))); }; this.noise1d = function(x) { var X = Math.floor(x)&255; x -= Math.floor(x); var fx = (3-2*x)*x*x; return lerp(fx, grad1d(p[X], x), grad1d(p[X+1], x-1)); }; } var noiseProfile = { generator: undefined, octaves: 4, fallout: 0.5, seed: undefined}; function noise(x, y, z) { if(noiseProfile.generator == undefined) { noiseProfile.generator = new PerlinNoise(noiseProfile.seed); } var generator = noiseProfile.generator; var t=1, k=1, sum=0; for(var i=0;i < noiseProfile.octaves;++i) { t *= noiseProfile.fallout; switch(arguments.length) { case 1: sum += t * (1 + generator.noise1d(k*x)) / 2; break; case 2: sum += t * (1 + generator.noise2d(k*x, k*y)) / 2; break; case 3: sum += t * (1 + generator.noise3d(k*x, k*y, k*z)) / 2; break; } k *= 2; } return sum; } function Worm(x,y) { this.x = x; this.y = y; this.lx = x; this.ly = y; this.heading = Math.sin(frameCount*0.083) * PI;; this.rotation = Math.random() * (PI / (Math.random() * 70)); this.rate = 0; this.maxLength = 15 + (noise(frameCount * 0.0025, frameCount*0.1) * 15); this.detail = 2; this.thickness = 3; this.thicknessTarget = 5 + Math.random() * 10; var cIndex = parseInt( Math.random() * colors.length ); this.c = colors[cIndex]; this.life = 30 + Math.random() * 120; this.segments = new Array(); this.ooo = true; this.counter = noise(frameCount * 0.1, frameCount*.1); this.update = function() { this.life--; if(this.life > 0) { this.thickness += (this.thicknessTarget-this.thickness) * 0.1; this.thickness += 0.1; if(this.thickness > this.thicknessTarget) this.thickness = this.thicknessTarget; this.heading += this.rotation; this.rate = Math.cos( this.counter / 200.0) * (10 + noise(frameCount*0.05) * 10); this.rotation = Math.sin(this.counter/this.rate) * (this.segments.length+1) * 0.010; this.counter++; var speedMod = (this.segments.length * this.segments.length * this.segments.length) * 0.0015 * this.thickness / this.thicknessTarget; var totalSpeed = (this.detail + speedMod); var nx = Math.cos(this.heading) * totalSpeed; var ny = Math.sin(this.heading) * totalSpeed; this.walk(nx,ny); } else { if(this.segments.length > 1) { this.segments.pop(); } else { this.thickness *= 0.95; this.thickness -= 0.2; if(this.thickness < 0.1) this.ooo = true; return; } } this.ooo = true; for(var i=0;i < this.segments.length;i++) { var segment = this.segments[i]; if(segment.ooo() == false) { this.ooo = false; break; } } } this.walk = function(nx,ny) { this.lx = this.x; this.ly = this.y; this.x += nx; this.y += ny; var newSegment = new Segment(this.lx,this.ly,this.x,this.y,this.thickness); this.segments.unshift(newSegment); if(this.segments.length > 1) { this.segments[this.segments.length-1].smoothAgainst(this.segments[this.segments.length-2]); } if(this.segments.length >= this.maxLength) { this.segments.pop(); } } } function Segment(x1,y1,x2,y2,thickness) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; this.thickness = thickness; var angle = Math.atan2(y2-y1,x2-x1); this.lAngle = angle - HALF_PI; var lDeltaX = Math.cos(this.lAngle) * thickness; var lDeltaY = Math.sin(this.lAngle) * thickness; this.leftX1 = x1 + lDeltaX; this.leftY1 = y1 + lDeltaY; this.leftX2 = x2 + lDeltaX; this.leftY2 = y2 + lDeltaY; this.rAngle = angle + HALF_PI; var rDeltaX = Math.cos(this.rAngle) * thickness; var rDeltaY = Math.sin(this.rAngle) * thickness; this.rightX1 = x1 + rDeltaX; this.rightY1 = y1 + rDeltaY; this.rightX2 = x2 + rDeltaX; this.rightY2 = y2 + rDeltaY; this.smoothAgainst = function(last) { this.leftX1 = last.leftX2 = (last.leftX2 + this.leftX1) * 0.5; this.leftY1 = last.leftY2 = (last.leftY2 + this.leftY1) * 0.5; this.rightX1 = last.rightX2 = (last.rightX2 + this.rightX1) * 0.5; this.rightY1 = last.rightY2 = (last.rightY2 + this.rightY1) * 0.5; } this.ooo = function() { if(this.x1 < 0 || this.y1 < 0 || this.x1 > width || this.y1 > height || this.x2 < 0 || this.y2 < 0 || this.x2 > width || this.y2 > height) return true; else return false; } } function draw() { clear(); for(var w=0; w < worms.length; w++) { var worm = worms[w]; worm.update(); ctx.fillStyle = worm.c; ctx.lineWidth = 2; ctx.strokeStyle = '#FFFFFF'; if(worm.segments.length>1) { ctx.beginPath(); ctx.moveTo(worm.segments[0].leftX1, worm.segments[0].leftY1); for(var i=0; i < worm.segments.length; i++) { var segment = worm.segments[i]; ctx.lineTo(segment.leftX1,segment.leftY1); } ctx.lineTo(worm.segments[worm.segments.length-1].rightX1, worm.segments[worm.segments.length-1].rightY1); for(var i=worm.segments.length-1; i >= 0; i--) { var segment = worm.segments[i]; ctx.lineTo(segment.rightX1,segment.rightY1); } ctx.closePath(); ctx.fill(); ctx.stroke(); } if(worm.segments.length>=1) { var x = worm.segments[0].x1; var y = worm.segments[0].y1; var thickness = worm.segments[0].thickness; if(worm.thickness < thickness) thickness = worm.thickness; if(thickness <= 0) thickness = 0.000001; ctx.beginPath(); ctx.arc(x, y, thickness, worm.segments[0].lAngle, worm.segments[0].rAngle, false); ctx.closePath(); ctx.stroke(); var xEnd = worm.segments[worm.segments.length-1].x1; var yEnd = worm.segments[worm.segments.length-1].y1; thickness = worm.segments[worm.segments.length-1].thickness; if(worm.thickness < thickness) thickness = worm.thickness; if(thickness <= 0) thickness = 0.000001; ctx.beginPath(); ctx.arc(xEnd, yEnd, thickness, worm.segments[worm.segments.length-1].lAngle, worm.segments[worm.segments.length-1].rAngle, true); ctx.closePath(); ctx.stroke(); thickness = worm.thickness-0.6; if(worm.thickness < thickness) thickness = worm.thickness; fillCircle(worm.segments[0].x1,worm.segments[0].y1,thickness); thickness = worm.segments[worm.segments.length-1].thickness-0.6; if(worm.thickness < thickness) thickness = worm.thickness; fillCircle(worm.segments[worm.segments.length-1].x1, worm.segments[worm.segments.length-1].y1,thickness); if(worm.life > 0) { ctx.fillStyle = "#FFFFFF"; fillCircle(worm.segments[0].x1,worm.segments[0].y1,thickness * 0.72); } } } for(var w=0; w < worms.length; w++) { var worm = worms[w]; if(worm.ooo) { worms.splice(w,1); w--; } } if(frameCount %2 ==0 && mousePressed && worms.length < 50) { var direction = Math.atan2(pmouseY-mouseY, pmouseX-mouseX) + PI; var newWorm = new Worm(mouseX,mouseY); worms.push( newWorm ); if(mouseX != pmouseX && mouseY != pmouseY) { newWorm.heading = direction; } } }