You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
6.2 KiB
210 lines
6.2 KiB
<!doctype html> |
|
<html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<title>虫洞</title> |
|
|
|
<style> |
|
html, body{ |
|
width: 100%; |
|
height: 100%; |
|
margin: 0; |
|
padding: 0; |
|
overflow: hidden; |
|
background-color: #101010; |
|
} |
|
.container{ |
|
position: absolute; |
|
width: 500px; |
|
height: 500px; |
|
left: 50%; |
|
top: 50%; |
|
margin-top: -250px; |
|
margin-left: -250px; |
|
background-color: #000000; |
|
}</style> |
|
</head> |
|
<body> |
|
|
|
<div id="jsi-wormhole-container" class="container"></div> |
|
|
|
<script type="text/javascript" src="js/jquery.min.js"></script> |
|
<script> |
|
var RENDERER = { |
|
HUE_OFFSET : 30, |
|
THRESHOLD : 142, |
|
POWER : 0.8, |
|
|
|
init : function(){ |
|
this.setParameters(); |
|
this.calculateIndices(); |
|
this.reconstructMethods(); |
|
this.render(); |
|
}, |
|
setParameters : function(){ |
|
this.$container = $('#jsi-wormhole-container'); |
|
this.width = this.$container.width(); |
|
this.height = this.$container.height(); |
|
this.context = $('<canvas />').attr({width : this.width, height : this.height}).appendTo(this.$container).get(0).getContext('2d'); |
|
this.distance = Math.sqrt(Math.pow(this.width, 2) + Math.pow(this.height, 2)) / 4; |
|
this.swirls = []; |
|
this.indices = []; |
|
this.theta = 0; |
|
}, |
|
calculateIndices : function(){ |
|
var width = this.THRESHOLD * 2 + 1, |
|
cx = this.THRESHOLD, |
|
cy = this.THRESHOLD, |
|
rate = Math.PI / 180, |
|
maxDistance = this.THRESHOLD * this.THRESHOLD, |
|
indices = this.indices; |
|
|
|
for(var dx = -cx; dx <= cx; dx++){ |
|
for(var dy = -cy; dy <= cy; dy++){ |
|
var x = cx + dx, |
|
y = cy + dy, |
|
distance = dx * dx + dy * dy, |
|
destinationIndex = (x + y * width) << 2, |
|
sourceIndex = destinationIndex; |
|
|
|
if(distance < maxDistance){ |
|
var coefficient = 1 - distance / maxDistance, |
|
theta = (90 + coefficient * coefficient * 180 * this.POWER) * rate, |
|
sin = Math.sin(theta), |
|
cos = Math.cos(theta); |
|
|
|
x = (cx + dx * sin - dy * cos) | 0; |
|
y = (cy + dx * cos + dy * sin) | 0; |
|
sourceIndex = (x + y * width) << 2; |
|
} |
|
indices.push({s : sourceIndex, d : destinationIndex}); |
|
} |
|
} |
|
this.swirls.push(new SWIRL(this, this.width * 2 / 7, this.height * 2 / 7, false)); |
|
this.swirls.push(new SWIRL(this, this.width * 5 / 7, this.height * 5 / 7, true)); |
|
}, |
|
reconstructMethods : function(){ |
|
this.render = this.render.bind(this); |
|
}, |
|
render : function(){ |
|
requestAnimationFrame(this.render); |
|
|
|
var hueOffset = this.HUE_OFFSET * Math.sin(this.theta), |
|
gradient = this.context.createLinearGradient(0, 0, this.width, this.height); |
|
gradient.addColorStop(0, 'hsl(' + hueOffset + ', 100%, 20%)'); |
|
gradient.addColorStop(1, 'hsl(' + (180 + hueOffset) + ', 100%, 20%)'); |
|
this.context.fillStyle = gradient; |
|
this.context.fillRect(0, 0, this.width, this.height); |
|
|
|
for(var i = 0, count = this.swirls.length; i < count; i++){ |
|
this.swirls[i].render(this.context, this.theta); |
|
} |
|
this.theta += Math.PI / 500; |
|
this.theta %= Math.PI * 2; |
|
} |
|
}; |
|
var SWIRL = function(renderer, x, y, release){ |
|
this.renderer = renderer; |
|
this.x = x; |
|
this.y = y; |
|
this.release = release; |
|
this.init(); |
|
}; |
|
SWIRL.prototype = { |
|
PARTICLE_COUNT : 400, |
|
|
|
init : function(){ |
|
this.particles = []; |
|
this.base = this.release ? 180 : 0; |
|
|
|
for(var i = 0, count = this.PARTICLE_COUNT; i < count; i++){ |
|
this.particles.push(new PARTICLE(this.renderer, this)); |
|
} |
|
this.width = this.renderer.THRESHOLD * 2 + 1; |
|
this.destination = this.renderer.context.createImageData(this.width, this.width); |
|
|
|
var destinationData = this.destination.data; |
|
|
|
for(var i = 0, indices = this.renderer.indices, count = indices.length; i < count; i++){ |
|
destinationData[indices[i].d + 3] = 255; |
|
} |
|
}, |
|
createSwirl : function(context){ |
|
var sourceData = context.getImageData(this.x - this.renderer.THRESHOLD, this.y - this.renderer.THRESHOLD, this.width, this.width).data, |
|
destinationData = this.destination.data; |
|
|
|
for(var i = 0, indices = this.renderer.indices, count = indices.length; i < count; i++){ |
|
var indexData = indices[i], |
|
sourceIndex = indexData.s, |
|
destinationIndex = indexData.d; |
|
|
|
destinationData[destinationIndex] = sourceData[sourceIndex]; |
|
destinationData[destinationIndex + 1] = sourceData[sourceIndex + 1]; |
|
destinationData[destinationIndex + 2] = sourceData[sourceIndex + 2]; |
|
} |
|
context.putImageData(this.destination, this.x - this.renderer.THRESHOLD, this.y - this.renderer.THRESHOLD); |
|
}, |
|
render : function(context, theta){ |
|
var hue = this.base + this.renderer.HUE_OFFSET * Math.sin(theta); |
|
context.save(); |
|
context.globalCompositeOperation = 'lighter'; |
|
|
|
for(var i = 0, count = this.particles.length; i < count; i++){ |
|
this.particles[i].render(context, hue); |
|
} |
|
context.restore(); |
|
this.createSwirl(context); |
|
} |
|
}; |
|
var PARTICLE = function(renderer, swirl){ |
|
this.renderer = renderer; |
|
this.swirl = swirl; |
|
this.init(false); |
|
}; |
|
PARTICLE.prototype = { |
|
RADIUS : 20, |
|
THRESHOLD : 3, |
|
|
|
init : function(toReset){ |
|
var theta = this.getRandomValue(0, Math.PI * 2), |
|
sin = Math.sin(theta), |
|
cos = Math.cos(theta), |
|
velocity = this.getRandomValue(0.5, 1), |
|
position = this.swirl.release ? (toReset ? this.getRandomValue(0, this.THRESHOLD) : this.getRandomValue(0, this.renderer.distance)) : -(toReset ? (this.renderer.distance + this.RADIUS) : this.getRandomValue(0, this.renderer.distance * 1.5)); |
|
|
|
this.x = (position * cos + this.swirl.x) | 0; |
|
this.y = (position * sin + this.swirl.y) | 0; |
|
this.vx = velocity * cos; |
|
this.vy = velocity * sin; |
|
}, |
|
getRandomValue : function(min, max){ |
|
return min + (max - min) * Math.random(); |
|
}, |
|
render : function(context, hue){ |
|
var dx = this.swirl.x - this.x, |
|
dy = this.swirl.y - this.y, |
|
distance = Math.sqrt(dx * dx + dy * dy), |
|
rate = (this.renderer.distance - distance) / this.renderer.distance; |
|
|
|
context.save(); |
|
context.translate(this.x, this.y); |
|
context.fillStyle = 'hsl(' + hue + ', 80%, ' + 3 * rate * rate + '%)'; |
|
context.beginPath(); |
|
context.arc(0, 0, this.RADIUS, 0, Math.PI * 2, false); |
|
context.fill(); |
|
context.restore(); |
|
|
|
this.x += this.vx; |
|
this.y += this.vy; |
|
|
|
if(this.swirl.release && distance > this.renderer.distance || !this.swirl.release && distance < this.THRESHOLD){ |
|
this.init(true); |
|
} |
|
} |
|
}; |
|
$(function(){ |
|
RENDERER.init(); |
|
});</script> |
|
|
|
</body> |
|
</html>
|
|
|