一些动画的实现思路
先看一些实现思路
- 圆周运动
- 椭圆运动
- 一些复杂动画
css圆周运动
//html
<div id="ball"></div>
//css
#ball {
position: absolute;
width: 20px;
height: 20px;
background: green;
border-radius: 50%;
top: 100px;
left: 200px;
transform-origin: 10px 110px;
transform: rotate(0deg);
animation: roll 2s linear infinite;
}
@keyframes roll {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
JS圆周运动
//html
<div id="ball"></div>
//css
#ball {
position: absolute;
width: 20px;
height: 20px;
background: green;
border-radius: 50%;
top: 100px;
left: 200px;
transform-origin: 10px 110px;
}
//js
function startCircle(el) {
let startTime = Date.now(),
cycle = 2000;
requestAnimationFrame(function update() {
let currentTime = Date.now();
let p = (currentTime - startTime) / cycle;
ball.style.transform = `rotate(${360 * p}deg)`;
requestAnimationFrame(update);
})
}
startCircle();
JS椭圆运动
//html
<div id="ball"></div>
//css
#ball {
position: absolute;
width: 20px;
height: 20px;
background: green;
border-radius: 50%;
top: 200px;
left: 200px;
transform: translate(0, -100px);
}
//js
function startCircle(el) {
let startTime = Date.now(),
cycle = 4000,
a = 160,
b = 80;
requestAnimationFrame(function update() {
let currentTime = Date.now(),
p = (currentTime - startTime) / cycle,
[x, y] = [a * Math.cos(Math.PI * 2 * p), b * Math.sin(Math.PI * 2 * p)];
ball.style.transform = `translate(${x}px, ${y}px)`;
requestAnimationFrame(update)
})
}
startCircle();
复杂一些的动画
//html
<button id="playBtn">play</button>
<div class="wall"></div>
<div class="ball" id="ball1"></div>
<div class="ball ball--hidden" id="ball2"></div>
//css
.wall {
position: absolute;
height: 100%;
width: 100px;
padding: 0;
margin: 0;
margin-left: 300px;
background-color: black;
}
.ball {
position: absolute;
width: 20px;
height: 20px;
border-radius: 50%;
background-color: green;
}
.ball--hidden{
/*display: none;*/
}
//js
function drop(ball, from, to, onupdate, duration =2000) {
let startTime = Date.now(),
[x0, y0] = from,
[x1, y1] = to;
requestAnimationFrame(function update() {
let currentTime = Date.now(),
p = (currentTime - startTime) / duration;
p = Math.min(p, 1.0);
let x = x0 + (x1 - x0) * p,
y = y0 + (y1 - y0) * p * p;
onupdate(ball, x, y);
ball.style.left = x + 'px';
ball.style.top = y + 'px';
if (p < 1) {
requestAnimationFrame(update)
}
})
}
playBtn.onclick = function () {
drop(ball1, [50, 50], [400, 600], function (ball, x, y) {
if (x < 290) {
ball.className = 'ball';
}
if (x>= 290) {
ball.className = 'ball ball--hidden';
}
})
drop(ball2, [530, 50], [180, 600] ,function (ball, x, y) {
if (x < 290 ) {
ball.className = 'ball';
}
if (x >= 290) {
ball.className = 'ball ball--hidden';
}
})
}
动画是关于事件的函数
一个反例
//html
<div id="block"></div>
//css
#block {
position: absolute;
left: 200px;
top: 200px;
width: 100px;
height: 100px;
background-color: #0c8;
line-height: 100px;
text-align: center;
}
//js
var deg = 0;
block.addEventListener('click', function() {
var self = this;
requestAnimationFrame(function change() {
self.style.transform = 'rotate(' + (deg++) + 'deg)';
requestAnimationFrame(change);
});
});
直线运动
- 匀速运动
- 匀加速运动
- 匀减速运动
- 平面上的直线运动
匀速运动
//html
<h2>匀速直线运动,滑块在2秒内移动200px</h2>
<div id="block">click me</div>
//css
#block {
position: absolute;
left: 200px;
top: 200px;
width: 100px;
height: 100px;
background: #0c8;
line-height: 100px;
text-align: center;
}
//js
block.addEventListener('click', function () {
var self = this,
startTime = Date.now();
distance = 200, duration = 2000;
requestAnimationFrame(function step() {
var p = Math.min(1.0, (Date.now() - startTime)/ duration);
self.style.transform = 'translateX('+ (distance * p)+'px)';
if (p < 1.0) {
requestAnimationFrame(step)
}
})
})
匀加速直线运动
block.addEventListener('click', function(){
var self = this, startTime = Date.now(),
distance = 200, duration = 2000;
requestAnimationFrame(function step(){
var p = Math.min(1.0, (Date.now() - startTime) / duration);
self.style.transform = 'translateX(' + (distance * p * p) +'px)';
if(p < 1.0) requestAnimationFrame(step);
});
});
匀减速度直线运动
block.addEventListener('click', function(){
var self = this, startTime = Date.now(),
distance = 200, duration = 2000;
requestAnimationFrame(function step(){
var p = Math.min(1.0, (Date.now() - startTime) / duration);
self.style.transform = 'translateX('
+ (distance * p * (2-p)) +'px)';
if(p < 1.0) requestAnimationFrame(step);
});
});
平面上的简单运动
- 斜线运动
- 曲线运动
- 圆周运动
斜线运动
block.addEventListener('click', function(){
var self = this, startTime = Date.now(),
distance = 200, T = 2000;
requestAnimationFrame(function step(){
var p = Math.min(1.0, (Date.now() - startTime) / T);
var tx = distance * p;
var ty = tx;
self.style.transform = 'translate('
+ tx + 'px' + ',' + ty +'px)';
if(p < 1.0) requestAnimationFrame(step);
});
});
圆锥曲线运动
block.addEventListener('click', function() {
var self = this,
startTime = Date.now(),
disX = 200,
disY = 200,
duration = 500 * Math.sqrt(2 * disY / 98);
// 假设10px 是1米 disY= 20 米
requestAnimationFrame(function step() {
var p = Math.min(1.0, (Date.now() - startTime) / duration);
var tx = disX * p;
var ty = disY * p * p;
self.style.transform = 'translate(' +tx + 'px' + ',' + ty + 'px)';
if (p < 1.0) requestAnimationFrame(step);
})
})
正弦线运动
block.addEventListener('click', function() {
var self = this,
startTime = Date.now(),
distance = 100,
duration = 2000;
requestAnimationFrame(function step() {
var p = Math.min(1.0, (Date.now() - startTime) / duration);
var ty = distance * Math.sin(2 * Math.PI * p);
var tx = 2 * distance * p;
self.style.transform = 'translate(' + tx + 'px,' + ty + 'px)';
if (p < 1.0) {
requestAnimationFrame(step);
}
})
})
椭圆运动
block.addEventListener('click', function() {
var self = this,
startTime = Date.now(),
a = 150,
b = 100,
duration = 2000;
requestAnimationFrame(function step() {
var p = Math.min(1.0, (Date.now() - startTime) / duration);
var tx = a * Math.sin(2 * Math.PI * p);
var ty = b * Math.cos(2 * Math.PI * p);
self.style.transform = 'translate(' + tx + 'px,' + ty + 'px)';
if (p < 1.0) {
requestAnimationFrame(step);
}
})
})
圆的参数方程
连续的运动
- 动画的简易封装
- 周期和折线运动
- 动画队列
- 改良动画队列
- 连贯动画
- 弹跳和滚动
- 连续平滑动
对动画模型进行抽象
圆周运动
//html
<h2>圆周循环运动</h2>
<div id="block">click</div>
//css
#block {
position: absolute;
left: 150px;
top: 200px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #0c8;
line-height: 50px;
text-align: center;
transform: translateY(-100px);
}
//js
<script src="https://s4.ssl.qhres.com/!2fb39e02/animator-0.1.0.min.js"></script>
let animator = new Animator(2000, function(p) {
let tx = -100 * Math.sin(2 * Math.PI * p),
ty = -100 * Math.cos(2 * Math.PI * p);
block.style.transform = 'translate(' + tx + 'px,' + ty + 'px)';
});
block.addEventListener('click', async function(evt) {
let i = 0;
while(1) {
await animator.animate()
block.style.background = ['red', 'green', 'blue'][i++ % 3]
}
})
折线运动
//html
<h2>折线运动</h2>
<div id="block">click</div>
//css
#block {
position: absolute;
left: 150px;
top: 100px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #0c8;
line-height: 50px;
text-align: center;
}
//js
var a1 = new Animator(500, function(p) {
var tx = 100 * p;
block.style.transform = 'translateX(' + tx + 'px)'
});
var a2 = new Animator(1000, function(p) {
var ty = 100 * p;
block.style.transform = 'translate(100px, ' + ty + 'px)';
})
block.addEventListener('click', async function() {
await a1.animate()
await a2.animate()
})
连续的运动
//html
<h2>连续的运动</h2>
<div id="block">click</div>
//css
#block {
position: absolute;
left: 150px;
top: 100px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #0c8;
line-height: 50px;
text-align: center;
}
//js
var a1 = new Animator(500, function(p) {
var tx = 100 * p;
block.style.transform = 'translateX(' + tx + 'px)'
});
var a2 = new Animator(1000, function(p) {
var ty = 100 * p;
block.style.transform = 'translate(100px, ' + ty + 'px)';
})
var a3 = new Animator(1000, function(p){
var tx = 100 * (1-p);
block.style.transform = 'translate('
+ tx + 'px, 100px)';
});
var a4 = new Animator(1000, function(p){
var ty = 100 * (1-p);
block.style.transform = 'translateY('
+ ty + 'px)';
});
block.addEventListener('click', async function() {
await a1.animate()
await a2.animate()
await a3.animate()
await a4.animate()
})
弹跳的小球
//html
<h2>弹跳的小球</h2>
<div id="block">click</div>
//css
#block {
position: absolute;
left: 150px;
top: 100px;
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #0c8;
line-height: 50px;
text-align: center;
}
//js
var a1 = new Animator(1414, function(p) {
var ty = 200 * p * p;
block.style.transform = 'translateY(' + ty + 'px)'
})
var a2 = new Animator(1414, function (p) {
var ty = 200 - 200 * p * (2-p);
block.style.transform = 'translateY('+ ty +'px)';
})
block.addEventListener('click', async function () {
while(1) {
await a1.animate()
await a2.animate();
}
})
改进弹跳的小球
设置阻力系数:
- T’ = 0.7T
- S’ = 0.49S
//html
<h2>弹跳小球</h2>
<div id="block">click</div>
//css
#block{
position:absolute;
left: 150px;
top: 300px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #0c8;
line-height: 50px;
text-align: center;
transform: translateY(-200px);
}
//js
<script src="https://s4.ssl.qhres.com/!2fb39e02/animator-0.1.0.min.js"></script>
block.addEventListener('click', async function() {
var T = 1414,
loss = 0.7;
var a1 = new Animator(T, function(p) {
var s = this.duration * 200 / T;
var ty = s * (p * p -1);
console.log(this.duration);
block.style.transform = 'translateY(' + ty + 'px)'
})
var a2 = new Animator(T, function(p) {
var s = this.duration * 200 / T;
var ty = -s * p * (2 - p);
block.style.transform = 'translateY(' + ty + 'px)'
})
while(a2.duration > 0.001) {
await a1.animate();
a2.duration *= 0.7;
await a2.animate();
a1.duration *= 0.7
}
})
滚动的小球
//html
<h2>弹跳小球</h2>
<div id="block">click</div>
//css
#block{
position:absolute;
left: 50px;
top: 100px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #0c8;
line-height: 50px;
text-align: center;
}
//js
<script src="https://s4.ssl.qhres.com/!2fb39e02/animator-0.1.0.min.js"></script>
<script type="text/javascript">
var a1 = new Animator(4000, function(p) {
var rotation = 720 * p,
x = 50 + 314 * p;
block.style.transform = 'rotate(' + 720 * p + 'deg)'
block.style.left = x + 'px';
})
block.addEventListener('click', async function () {
await a1.animate();
})
</script>
小球被惯性甩出
//html
<h2>甩出小球</h2>
<div id="block">click</div>
//css
#block {
position: absolute;
left: 100px;
top: 100px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #0c8;
line-height: 50px;
text-align: center;
}
//js
<script src="https://s4.ssl.qhres.com/!2fb39e02/animator-0.1.0.min.js"></script>
<script type="text/javascript">
var a1 = new Animator(2800, function(p) {
var x = -100 * Math.sin(2.8 * Math.PI * p);
var y = 100 - 100 * Math.cos(2.8 * Math.PI * p);
block.style.transform = 'translate(' + x + 'px,' +
y + 'px)';
})
var a2 = new Animator(5000, function(p) {
var x = -100 * Math.sin(2.8 * Math.PI) - 100 * Math.cos(2.8 * Math.PI) * Math.PI * 5 * p;
var y = 100 - 100 * Math.cos(2.8 * Math.PI) +
100 * Math.sin(2.8 * Math.PI) * Math.PI * 5 * p;
console.log(y)
block.style.transform = 'translate(' + x + 'px,' + y + 'px)'
// block.style.transform = 'translate(' + x + 'px,' + y + 'px)';
});
block.addEventListener('click', async function() {
await a1.animate()
await a2.animate()
})
摩擦力
小球加速运动100px ,匀速运动100px, 减速运动50px 后停下
//html
<h2>摩擦力</h2>
<div id="block">click</div>
//css
#block {
position: absolute;
left: 100px;
top: 100px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #0c8;
line-height: 50px;
text-align: center;
}
//js
<script src="https://s4.ssl.qhres.com/!2fb39e02/animator-0.1.0.min.js"></script>
<script type="text/javascript">
var a1 = new Animator(1000, function(p) {
var x = 100 * p * p
block.style.transform = 'translateX(' + x + 'px)';
});
var a2 = new Animator(500, function(p) {
var x = 100 + 100 * p;
block.style.transform = 'translateX('+ x + 'px)';
});
var a3 = new Animator(500, function(p) {
var x = 200 + 50 * p * (2 - p);
block.style.transform = 'translateX(' + x + 'px)'
})
block.addEventListener('click', async function() {
await a1.animate();
await a2.animate();
await a3.animate();
})
</script>
动画与贝塞尔曲线
- 构造贝塞尔曲线
- 使用贝塞尔曲线实现动画
- 贝塞尔曲线的css3动画
cubic-bezier easing
贝塞尔曲线动画
//html
<h2>贝塞尔曲线</h2>
<div id="block">click</div>
//css
#block {
position: absolute;
left: 100px;
top: 100px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #0c8;
line-height: 50px;
text-align: center;
}
//js
var easing = BezierEasing(0.86, 0, 0.07, 1);
//easeInOutQuint
var a1 = new Animator(2000, function(ep, p) {
var x = 200 * ep;
block.style.transform = 'translateX(' + x + 'px)';
}, easing);
block.addEventListener('click', async function() {
await a1.animate();
});
//贝塞尔曲线动画2
var easing = BezierEasing(0.68, -0.55, 0.265, 1.55);
//easeInOutBack
var a1 = new Animator(2000, function(ep,p){
var x = 200 * ep;
block.style.transform = 'translateX(' + x + 'px)';
}, easing);
block.addEventListener('click', async function(){
await a1.animate();
});
//贝塞尔曲线动画3
var easing = BezierEasing(0.68, -0.55, 0.265, 1.55);
//easeInOutBack
var a1 = new Animator(2000, function(ep,p){
var x = 200 * p;
var y = -200 * ep;
block.style.transform = `translate(${x}px, ${y}px)`;
}, easing);
block.addEventListener('click', async function(){
await a1.animate();
});
贝塞尔曲线 CSS3 动画
//html
<h2>贝塞尔曲线</h2>
<div id="block">click</div>
//css
#block {
position: absolute;
left: 100px;
top: 100px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #0c8;
line-height: 50px;
text-align: center;
transition: transform 2s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
//js
<script type="text/javascript">
block.addEventListener('click', function() {
block.className = 'moved';
});
</script>
3D动画
- 翻转
- 逐帧动画
翻转动画
//html
<h2>翻转动画</h2>
<div id="block">click</div>
//css
#block {
position: absolute;
left: 100px;
top: 400px;
width: 50px;
height: 50px;
border-radius: 50%;
background: #0c8;
line-height: 50px;
text-align: center;
}
//js
<script src="https://s.ssl.qhimg.com/!bfe40c57/bezier-easing.min.js"></script>
<script src="https://s4.ssl.qhres.com/!2fb39e02/animator-0.1.0.min.js"></script>
<script type="text/javascript">
var easing = BezierEasing(0.68, -0.55, 0.265, 1.55);
var a1 = new Animator(2000, function(ep, p) {
var y = -200 * ep;
var x = 200 * p;
var r = 360 * ep;
block.style.transform = 'translate(' + x + 'px,' +y + 'px) rotateY(' + r + 'deg)';
}, easing)
block.addEventListener('click', async function() {
await a1.animate();
})
逐帧动画
//html
<div id="bird" class="sprite bird bird1"></div>
//css
.sprite {
display: inline-block;
overflow: hidden;
background-repeat: no-repeat;
background-image: url(https://p1.ssl.qhimg.com/d/inn/0f86ff2a/8PQEganHkhynPxk-CUyDcJEk.png);
}
.bird {
width: 86px;
height: 60px;
}
.bird0 {
background-position: -178px -2px
}
.bird1 {
background-position: -90px -2px
}
.bird2 {
background-position: -2px -2px
}
//js
<script type="text/javascript">
var i = 0;
setInterval(function () {
bird.className = 'sprite bird ' + 'bird' + ((i++)%3)
}, 1000/10);
</script>
Web Animations API
//html
<div id="block">click</div>
<div id="bird" class="sprite"></div>
//css
#block {
text-align: center;
color: #aaa;
line-height: 50px;
position: absolute;
width: 50px;
height: 50px;
left: 100px;
top: 100px;
background-color: #077;
}
#bird {
position: absolute;
left: 100px;
top: 100px;
zoom: 0.5;
display: inline-block;
overflow: hidden;
width: 86px;
height: 60px;
background: url(https://p1.ssl.qhimg.com/d/inn/0f86ff2a/8PQEganHkhynPxk-CUyDcJEk.png) no-repeat -178px -2px;
}
//js
block.addEventListener('click', e => {
block.animate([{
offset: 0,
transform: 'rotate(0deg)'
},
{
offset: 1,
transform: 'rotate(360deg)'
}
], {
duration: 3000,
direction: 'normal',
fill: 'both',
iterations: Infinity
});
function backgroundPosition(x, y) {
return `url(https://p1.ssl.qhimg.com/d/inn/0f86ff2a/8PQEganHkhynPxk-CUyDcJEk.png) no-repeat ${x}px ${y}px`;
}
bird.animate([{
offset: 0,
background: backgroundPosition(-178, -2)
},
{
offset: 0.33,
background: backgroundPosition(-178, -2)
},
{
offset: 1,
background: backgroundPosition(-2, -2)
}
], {
duration: 500,
direction: 'normal',
fill: 'both',
iterations: Infinity,
easing: 'steps(3, start)'
});
});
总结
本课通过例子学习了实现动画的基本原理和思路
- 动画是关于时间的函数
- 动画的数学计算
- 动画的封装和组合动画
- 贝塞尔曲线
- 3D和序列帧动画