你永远不知道前端大神们能玩儿出哪些让你看一眼就惊掉下巴,瞪掉眼珠子的特效。更神奇的是,看似复杂的效果,其实背后的代码远比你想象的要简单。上周公司团建,小编去感受了一把重力加速度的刺激(蹦!极!),吓die了好吗,于是乎小编跟重力杠上了,正好看到了几个重力模拟的特效,分享给大家,妈妈问我为什么跪着看完这篇文章,等你看完了也就知道了。。。。。。。
Gravity Test
这个特效模拟了泡泡球从天而降的效果,想必大家小时候都这样玩过泡泡球吧,使劲往地上扔然后看球弹起来,这个特效绝对满足你~
代码:
<html>
<head>
<style>
* {
margin: 0;
padding: 0;
}
html {
height: 100%;
}
body {
height: 100%;
overflow: hidden;
font-family: 'Source Code Pro';
}
h1 {
position: absolute;
top: 10vh;
left: 0;
right: 0;
z-index: 2;
color: #222;
font-size: 8vw;
text-align: center;
}
canvas {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
</style>
</head>
<body>
<h1>Gravity Test</h1>
<canvas id="canvas"></canvas>
<script>
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
module.exports = function(object, eventType, callback){
var timer;
object.addEventListener(eventType, function(event) {
clearTimeout(timer);
timer = setTimeout(function(){
callback(event);
}, 500);
}, false);
};
},{}],2:[function(require,module,exports){
var Vector2 = require('./vector2');
var exports = {
friction: function(vector, value) {
var force = vector.clone();
force.multScalar(-1);
force.normalize();
force.multScalar(value);
return force;
},
drag: function(vector, value) {
var force = vector.clone();
force.multScalar(-1);
force.normalize();
force.multScalar(vector.length() * value);
return force;
},
hook: function(v_velocity, v_anchor, k) {
var force = v_velocity.clone().sub(v_anchor);
var distance = force.length();
if (distance > 0) {
force.normalize();
force.multScalar(-1 * k * distance);
return force;
} else {
return new Vector2();
}
}
};
module.exports = exports;
},{"./vector2":6}],3:[function(require,module,exports){
var Util = require('./util');
var Vector2 = require('./vector2');
var Force = require('./force');
var Mover = require('./mover');
var debounce = require('./debounce');
var body_width = document.body.clientWidth * 2;
var body_height = document.body.clientHeight * 2;
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var last_time_activate = Date.now();
var vector_touch_start = new Vector2();
var vector_touch_move = new Vector2();
var vector_touch_end = new Vector2();
var is_touched = false;
var movers = [];
var count_movers = 0;
var unit_mover = 300;
var gravity = new Vector2(0, 1);
var init = function() {
poolMover();
renderloop();
setEvent();
resizeCanvas();
debounce(window, 'resize', function(event){
resizeCanvas();
});
};
var poolMover = function () {
for (var i = 0; i < unit_mover; i++) {
var mover = new Mover();
movers.push(mover);
}
count_movers += unit_mover;
};
var updateMover = function () {
for (var i = 0; i < movers.length; i++) {
var mover = movers[i];
if (!mover.is_active) continue;
if (mover.acceleration.length() < 2) {
mover.time ++;
}
if (mover.time > 20) {
mover.radius -= mover.radius / 10;
}
if (mover.radius < 10) {
mover.inactivate();
continue;
}
mover.applyForce(gravity);
mover.applyFriction();
mover.updateVelocity();
collideMover(mover, i, movers, true);
mover.collideBorder(false, body_width, body_height, 0, true);
collideMover(mover, i, movers, false);
collideMover(mover, i, movers, false);
collideMover(mover, i, movers, false);
mover.updatePosition();
movers[i].draw(ctx);
}
};
var collideMover = function(mover, i, movers, preserve_impulse) {
for (var index = 0; index < movers.length; index++) {
if (index === i) continue;
mover.collide(movers[index], preserve_impulse);
}
}
var activateMover = function () {
var vector = new Vector2(Util.getRandomInt(0, body_width), body_height / 2 * -1);
var radian = 0;
var scalar = 0;
var x = 0;
var y = 0;
var force = new Vector2();
for (var i = 0; i < movers.length; i++) {
var mover = movers[i];
if (mover.is_active) continue;
mover.activate();
mover.init(vector, (body_width + body_height) / 200);
break;
}
};
var render = function() {
ctx.clearRect(0, 0, body_width, body_height);
updateMover();
};
var renderloop = function() {
var now = Date.now();
requestAnimationFrame(renderloop);
render();
if (now - last_time_activate > 10) {
activateMover();
last_time_activate = Date.now();
}
};
var resizeCanvas = function() {
body_width = document.body.clientWidth * 2;
body_height = document.body.clientHeight * 2;
canvas.width = body_width;
canvas.height = body_height;
canvas.style.width = body_width / 2 + 'px';
canvas.style.height = body_height / 2 + 'px';
};
var setEvent = function () {
var eventTouchStart = function(x, y) {
vector_touch_start.set(x, y);
is_touched = true;
};
var eventTouchMove = function(x, y) {
vector_touch_move.set(x, y);
if (is_touched) {
}
};
var eventTouchEnd = function(x, y) {
vector_touch_end.set(x, y);
is_touched = false;
};
canvas.addEventListener('contextmenu', function (event) {
event.preventDefault();
});
canvas.addEventListener('selectstart', function (event) {
event.preventDefault();
});
canvas.addEventListener('mousedown', function (event) {
event.preventDefault();
eventTouchStart(event.clientX * 2, event.clientY * 2);
});
canvas.addEventListener('mousemove', function (event) {
event.preventDefault();
eventTouchMove(event.clientX * 2, event.clientY * 2);
});
canvas.addEventListener('mouseup', function (event) {
event.preventDefault();
eventTouchEnd();
});
canvas.addEventListener('touchstart', function (event) {
event.preventDefault();
eventTouchStart(event.touches[0].clientX * 2, event.touches[0].clientY * 2);
});
canvas.addEventListener('touchmove', function (event) {
event.preventDefault();
eventTouchMove(event.touches[0].clientX * 2, event.touches[0].clientY * 2);
});
canvas.addEventListener('touchend', function (event) {
event.preventDefault();
eventTouchEnd();
});
};
init();
},{"./debounce":1,"./force":2,"./mover":4,"./util":5,"./vector2":6}],4:[function(require,module,exports){
var Util = require('./util');
var Vector2 = require('./vector2');
var Force = require('./force');
var exports = function(){
var Mover = function() {
this.position = new Vector2();
this.velocity = new Vector2();
this.acceleration = new Vector2();
this.anchor = new Vector2();
this.radius = 0;
this.mass = 1;
this.direction = 0;
this.r = Util.getRandomInt(200, 255);
this.g = Util.getRandomInt(0, 180);
this.b = Util.getRandomInt(0, 50);
this.a = 1;
this.time = 0;
this.is_active = false;
};
Mover.prototype = {
init: function(vector, size) {
this.radius = Util.getRandomInt(size, size * 4);
this.mass = this.radius / 100;
this.position = vector.clone();
this.velocity = vector.clone();
this.anchor = vector.clone();
this.acceleration.set(0, 0);
this.a = 1;
this.time = 0;
},
updatePosition: function() {
this.position.copy(this.velocity);
},
updateVelocity: function() {
this.velocity.add(this.acceleration);
if (this.velocity.distanceTo(this.position) >= 1) {
this.direct(this.velocity);
}
},
applyForce: function(vector) {
this.acceleration.add(vector);
},
applyFriction: function() {
var friction = Force.friction(this.acceleration, 0.1);
this.applyForce(friction);
},
applyDragForce: function() {
var drag = Force.drag(this.acceleration, 0.5);
this.applyForce(drag);
},
hook: function() {
var force = Force.hook(this.velocity, this.anchor, this.k);
this.applyForce(force);
},
rebound: function(vector, e) {
var dot = this.acceleration.clone().dot(vector);
this.acceleration.sub(vector.multScalar(2 * dot));
this.acceleration.multScalar(e);
},
direct: function(vector) {
var v = vector.clone().sub(this.position);
this.direction = Math.atan2(v.y, v.x);
},
collide: function(target, preserve_impulse) {
var distance = this.velocity.distanceTo(target.velocity);
var rebound_distance = this.radius + target.radius;
var damping = 0.9;
if (distance < rebound_distance) {
var overlap = Math.abs(distance - rebound_distance);
var this_normal = this.velocity.clone().sub(target.velocity).normalize();
var target_normal = target.velocity.clone().sub(this.velocity).normalize();
this.velocity.sub(target_normal.clone().multScalar(overlap / 2));
target.velocity.sub(this_normal.clone().multScalar(overlap / 2));
if(preserve_impulse){
var scalar1 = target.acceleration.length();
var scalar2 = this.acceleration.length();
this.acceleration.sub(this_normal.multScalar(scalar1 / -2)).multScalar(damping);
target.acceleration.sub(target_normal.multScalar(scalar2 / -2)).multScalar(damping);
if (Math.abs(this.acceleration.x) < 1) this.acceleration.x = 0;
if (Math.abs(this.acceleration.y) < 1) this.acceleration.y = 0;
if (Math.abs(target.acceleration.x) < 1) target.acceleration.x = 0;
if (Math.abs(target.acceleration.y) < 1) target.acceleration.y = 0;
}
}
},
collideBorder: function(top, right, bottom, left, preserve_impulse) {
var damping = 0.6;
if (top !== false && this.position.y - this.radius < top) {
var normal = new Vector2(0, 1);
this.velocity.y = this.radius;
if (preserve_impulse) this.acceleration.y *= -1 * damping;
}
if (right !== false && this.position.x + this.radius > right) {
var normal = new Vector2(-1, 0);
this.velocity.x = right - this.radius;
if (preserve_impulse) this.acceleration.x *= -1 * damping;
}
if (bottom !== false && this.position.y + this.radius > bottom) {
var normal = new Vector2(0, -1);
this.velocity.y = bottom - this.radius;
if (preserve_impulse) this.acceleration.y *= -1 * damping;
}
if (left !== false && this.position.x - this.radius < left) {
var normal = new Vector2(1, 0);
this.velocity.x = this.radius;
if (preserve_impulse) this.acceleration.x *= -1 * damping;
}
},
draw: function(context) {
context.fillStyle = 'rgba(' + this.r + ',' + this.g + ',' + this.b + ',' + this.a + ')';
context.beginPath();
context.arc(this.position.x, this.position.y, this.radius, 0, Math.PI / 180, true);
context.fill();
},
activate: function () {
this.is_active = true;
},
inactivate: function () {
this.is_active = false;
}
};
return Mover;
};
module.exports = exports();
},{"./force":2,"./util":5,"./vector2":6}],5:[function(require,module,exports){
var exports = {
getRandomInt: function(min, max){
return Math.floor(Math.random() * (max - min)) + min;
},
getDegree: function(radian) {
return radian / Math.PI * 180;
},
getRadian: function(degrees) {
return degrees * Math.PI / 180;
},
getSpherical: function(rad1, rad2, r) {
var x = Math.cos(rad1) * Math.cos(rad2) * r;
var z = Math.cos(rad1) * Math.sin(rad2) * r;
var y = Math.sin(rad1) * r;
return [x, y, z];
}
};
module.exports = exports;
},{}],6:[function(require,module,exports){
//
// このVector2クラスは、three.jsのTHREE.Vector2クラスの計算式の一部を利用しています。
// https://github.com/mrdoob/three.js/blob/master/src/math/Vector2.js#L367
//
var exports = function(){
var Vector2 = function(x, y) {
this.x = x || 0;
this.y = y || 0;
};
Vector2.prototype = {
set: function (x, y) {
this.x = x;
this.y = y;
return this;
},
copy: function (v) {
this.x = v.x;
this.y = v.y;
return this;
},
add: function (v) {
this.x += v.x;
this.y += v.y;
return this;
},
addScalar: function (s) {
this.x += s;
this.y += s;
return this;
},
sub: function (v) {
this.x -= v.x;
this.y -= v.y;
return this;
},
subScalar: function (s) {
this.x -= s;
this.y -= s;
return this;
},
mult: function (v) {
this.x *= v.x;
this.y *= v.y;
return this;
},
multScalar: function (s) {
this.x *= s;
this.y *= s;
return this;
},
div: function (v) {
this.x /= v.x;
this.y /= v.y;
return this;
},
divScalar: function (s) {
this.x /= s;
this.y /= s;
return this;
},
min: function (v) {
if ( this.x < v.x ) this.x = v.x;
if ( this.y < v.y ) this.y = v.y;
return this;
},
max: function (v) {
if ( this.x > v.x ) this.x = v.x;
if ( this.y > v.y ) this.y = v.y;
return this;
},
clamp: function (v_min, v_max) {
if ( this.x < v_min.x ) {
this.x = v_min.x;
} else if ( this.x > v_max.x ) {
this.x = v_max.x;
}
if ( this.y < v_min.y ) {
this.y = v_min.y;
} else if ( this.y > v_max.y ) {
this.y = v_max.y;
}
return this;
},
floor: function () {
this.x = Math.floor( this.x );
this.y = Math.floor( this.y );
return this;
},
ceil: function () {
this.x = Math.ceil( this.x );
this.y = Math.ceil( this.y );
return this;
},
round: function () {
this.x = Math.round( this.x );
this.y = Math.round( this.y );
return this;
},
roundToZero: function () {
this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
return this;
},
negate: function () {
this.x = - this.x;
this.y = - this.y;
return this;
},
dot: function (v) {
return this.x * v.x + this.y * v.y;
},
lengthSq: function () {
return this.x * this.x + this.y * this.y;
},
length: function () {
return Math.sqrt(this.lengthSq());
},
normalize: function () {
return this.divScalar(this.length());
},
distanceTo: function (v) {
var dx = this.x - v.x;
var dy = this.y - v.y;
return Math.sqrt(dx * dx + dy * dy);
},
setLength: function (l) {
var oldLength = this.length();
if ( oldLength !== 0 && l !== oldLength ) {
this.multScalar(l / oldLength);
}
return this;
},
clone: function () {
return new Vector2(this.x, this.y);
}
}
return Vector2;
};
module.exports = exports();
},{}]},{},[3])
</script>
</body>
</html>
Gravity balls to mouse study
如果说上面这个特效还不够生动的话,那么这个特效将会更3D的呈现效果,并且小球的位置还会根据你的鼠标位置改变哟。
代码:
<html>
<head>
<style>
body {
margin: 0;
overflow: hidden;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<canvas></canvas>
<script>
// Initial Setup
const canvas = document.querySelector('canvas');
const c = canvas.getContext('2d');
canvas.width = innerWidth;
canvas.height = innerHeight;
// Variables
let mouse = {
x: innerWidth / 2,
y: innerHeight / 2
};
let ballCount = 750;
let balls = [];
let gravityPos = [];
let friction = .995;
let explosionDistance = 2;
let shouldExplode = false;
const colors = [
'#81C3D7',
'#D9DCD6',
'#3A7CA5',
'#2F6690'
];
const bgColor = '#16425B';
// Event Listeners
addEventListener("mousemove", function(event) {
mouse.x = event.clientX;
mouse.y = event.clientY;
gravityPos = [mouse.x, mouse.y];
});
addEventListener("mouseout", function(event) {
gravityPos = [canvas.width / 2, canvas.height / 2];
});
addEventListener("resize", function() {
canvas.width = innerWidth;
canvas.height = innerHeight;
init();
});
addEventListener("click", function() {
init();
});
// Utility Functions
function randomIntFromRange(min,max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function randomeFloatFromRange(min, max){
return Math.random() * (max - min) + min;
}
function randomColor(colors) {
return colors[Math.floor(Math.random() * colors.length)];
}
// Objects
function Ball(px, py, vx, vy, f, radius, color) {
this.p = [px, py];
this.v = [vx, vy];
this.gv = [0, 0];
this.gp = 0;
this.radius = radius;
this.color = color;
this.f = f;
this.update = function() {
// calculate gravity vector
this.gv = [gravityPos[0] - this.p[0], gravityPos[1] - this.p[1]];
// Calculate gravity intensity
let a = gravityPos[0] - this.p[0];
let b = gravityPos[1] - this.p[1];
this.gp = 1 / (Math.sqrt( a*a + b*b ));
// Explode if needed
if (shouldExplode){
this.v[0] *= randomeFloatFromRange(-10, 10);
this.v[1] *= randomeFloatFromRange(-10, 10);
}
// Reduce ball's own velocity with friction
this.v[0] *= this.f;
this.v[1] *= this.f;
// Calculate new velocity, add gravity
this.v[0] += this.gv[0] * this.gp * this.f;
this.v[1] += this.gv[1] * this.gp * this.f;
// Move
this.p[0] += this.v[0];
this.p[1] += this.v[1];
this.draw();
};
this.draw = function() {
c.save();
c.beginPath();
c.arc(this.p[0], this.p[1], this.radius, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
c.closePath();
c.restore();
};
}
// Implementation
function init() {
gravityPos = [canvas.width / 2, canvas.height / 2];
balls = [];
for(let i = 0 ; i < ballCount ; i++){
let rd = randomeFloatFromRange(1, 7);
let px = randomeFloatFromRange(0, canvas.width / 3) + (canvas.width / 3);
let py = randomeFloatFromRange(0, canvas.height / 3) + (canvas.height / 3);
let vx = randomeFloatFromRange(-10, 10);
let vy = randomeFloatFromRange(-10, 10);
let f = friction;
balls.push(new Ball(px, py, vx, vy, f, rd, randomColor(colors)));
}
}
// Animation Loop
function animate() {
requestAnimationFrame(animate);
resetCanvas(bgColor);
updateShouldExplode();
for(let i = 0 ; i < balls.length ; i++){
balls[i].update();
}
}
init();
animate();
function resetCanvas(color){
if(color){
c.save();
c.fillStyle = color;
c.fillRect(0, 0, canvas.width, canvas.height);
c.restore();
}else{
c.clearRect(0, 0, canvas.width, canvas.height);
}
}
function updateShouldExplode(){
let x = 0;
let y = 0;
for(let i = 0 ; i < balls.length ; i++){
x += balls[i].v[0] < 0 ? balls[i].v[0] * -1 : balls[i].v[0];
y += balls[i].v[1] < 0 ? balls[i].v[1] * -1 : balls[i].v[1];
}
shouldExplode = x / balls.length < explosionDistance && y / balls.length < explosionDistance;
}
</script>
</body>
</html>
更多好玩儿的效果请点击这里:http://igeekbar.com/igeekbar/post/246.htm
版权声明:本文为jjfatjjfat原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。