<html>
<head>
<style>
.canvas {
position: relative;
height: 600px;
background: #000;
overflow: hidden;
margin-bottom: 5px;
}
.flying-charactor{
position: absolute;
color: #0f0;
}
h1{
font-size: 20px;
}
</style>
</head>
<body>
<div style="margin-top:-10px;margin-bottom:20px;">
<script>
function tweener_env(){
// functions & constants
var FPS = 60;
function default_easing(elapsed, from, distance, duration){
return distance * elapsed / duration + from;
};
var default_duration = 1000;
function normalize_property(name, prop){
return { name :name,
from :prop.from,
to :prop.to,
distance :prop.to - prop.from,
prefix :prop.prefix || '',
suffix :prop.suffix || 'px' };
}
function normalize_item(target, properties, options){
var props = [];
for (var name in properties)
props.push(normalize_property(name, properties[name]));
return { props :props,
target :target,
start_at :new Date() - 0,
duration :options.duration || default_duration,
easing :options.easing || default_easing,
onComplete :options.onComplete };
}
// procedures & mutable data
var items = [];
var timer_id;
function render(){
var now = new Date() - 0;
var n = items.length;
while (n--){
var item = items[n];
var elapsed = now - item.start_at;
if (elapsed < item.duration){
item.props.forEach(function(prop){
item.target[prop.name] = prop.prefix +
item.easing(elapsed, prop.from, prop.distance, item.duration) +
prop.suffix;
});
} else {
item.props.forEach(function(prop){
item.target[prop.name] = prop.prefix + prop.to + prop.suffix;
});
if (item.onComplete) item.onComplete();
items.splice(n, 1); // 前からループすると、ココで添字がズレるので後ろからループ
}
}
if (items.length === 0) finish();
// わざわざ while (n--) とかして逆向きのループにしているけど
// これは何度も配列を分割して、くっつけているのでそんなに速くないのかも
// 素直に、以下の様に2回にループを分けた方が速いかもしれない。
// 1回目: target の書換え と duration を過ぎたやつに null を入れる
// 2回目: Array.prototype.filter で null のやつをまとめて削除
// もちろん JS の処理系の実装によるけど
};
function start(){
timer_id = setInterval(render, 1000 / FPS);
}
function finish(){
items = [];
clearInterval(timer_id);
timer_id = null;
}
return function(target, properties, options){
if (items.length === 0) start();
items.push(normalize_item(target, properties, options));
};
}
</script>
<div id="canvas" class="canvas"></div>
<button onclick="run_or_stop()">アニメーションを実行</button>
<h1>参考</h1>
<ul>
<li><a href="http://gihyo.jp/dev/serial/01/crossbrowser-javascript/0017">第17回 アニメーションの基礎</a></li>
<li><a href="http://coderepos.org/share/wiki/JSTweener">JSTweener</a></li>
<li><a href="http://hosted.zeh.com.br/tweener/docs/en-us/misc/transitions.html">Tweener Documentation</a></li>
</ul>
<script>
var canvas = document.getElementById('canvas');
var timerId;
var add_target = tweener_env();
function random_charactor(){
return Math.floor(Math.random()*36).toString(36);
}
function run_or_stop(){
if(timerId){
clearInterval(timerId);
timerId = null;
} else {
timerId = setInterval(function(){
var i = document.createElement('span');
i.className = 'flying-charactor';
i.style.top = i.style.left = '50%';
i.style.fontSize = '50%';
i.appendChild(document.createTextNode(random_charactor()));
canvas.appendChild(i);
add_target(i.style,
{ top : {from:50, to:100*Math.random(), suffix:'%'},
left : {from:50, to:100*Math.random(), suffix:'%'},
fontSize : {from:50, to:300*Math.random(), suffix:'%'}},
{ duration : Math.random()*1000,
onComplete:function(){ canvas.removeChild(i); }});
}, 32);
}
}
</script>
</div>
</body>
</html>