console
function animate({ timing, draw, duration }) {
let start = performance.now();
return new Promise(resolve => {
requestAnimationFrame(function animate(time) {
//timeFraction为时间段,值为0-1之间 (timeFraction goes from 0 to 1)
let timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;
// 计算当前的动画状态(calculate the current animation state)
let progress = timing(timeFraction)
draw(progress); // draw it
if (timeFraction < 1) {
requestAnimationFrame(animate);
} else {
resolve(timing);
}
});
});
}
const train = document.querySelector('.ball');
// 沿着x轴匀速直线运动
const linear = () => {
const draw = (progress) => {
train.style.transform = `translate(${progress}px, 0)`;
}
// 沿着x轴匀速运动
animate({
duration: 1000,
timing(timeFraction) {
return timeFraction * 100;
},
draw,
});
};
// 重力
const gravity = () => {
const draw = (progress) => {
train.style.transform = `translate(0, ${500 * (progress - 1)}px)`;
};
// 沿着x轴匀速运动
animate({
duration: 1000,
timing(timeFraction) {
return timeFraction ** 2;
},
draw,
});
};
// 摩擦力(匀减速运动)
const friction = () => {
// 初始高度500px
const draw = (progress) => {
train.style.transform = `translate(0, ${500 * (progress - 1)}px)`;
};
// 沿着x轴匀速运动
animate({
duration: 1000,
timing(timeFraction) {
// 初始速度系数为2
return timeFraction * (2 - timeFraction);
},
draw,
});
};
// 平抛(x轴匀速,y轴加速)
const horizontalMotion = () => {
const draw = (progress) => {
train.style.transform = `translate(${500 * progress.x}px, ${500 * (progress.y - 1)}px)`;
};
// 有两个方向,沿着x轴匀速运动,沿着y轴加速运动
animate({
duration: 1000,
timing(timeFraction) {
return {
x: timeFraction,
y: timeFraction ** 2,
}
},
draw,
});
};
// 旋转 + 平抛(x轴匀速,y轴加速,旋转匀速)
const horizontalMotionWidthRotate = () => {
const draw = (progress) => {
train.style.transform = `translate(${500 * progress.x}px, ${500 * (progress.y - 1)}px) rotate(${2000 * progress.rotate}deg)`;
};
// 有两个方向,沿着x轴匀速运动,沿着y轴加速运动
animate({
duration: 2000,
timing(timeFraction) {
return {
x: timeFraction,
y: timeFraction ** 2,
rotate: timeFraction,
}
},
draw,
});
};
// 拉弓(x轴匀速 + y轴初始速度为负的匀加速)
const arrowMove = () => {
const back = (x, timeFraction) => {
return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x);
}
const draw = (progress) => {
train.style.transform = `translate(${200 * progress.x}px, ${- 500 * progress.y}px)`;
};
animate({
duration: 1000,
timing(timeFraction) {
return {
x: timeFraction,
y: back(2, timeFraction),
};
},
draw,
});
};
// 贝塞尔
const bezier = () => {
const bezierPath = (x1, y1, x2, y2, timeFraction) => {
const x = 3 * x1 * timeFraction * (1 - timeFraction) ** 2 + 3 * x2 * timeFraction ** 2 * (1 - timeFraction) + timeFraction ** 3;
const y = 3 * y1 * timeFraction * (1 - timeFraction) ** 2 + 3 * y2 * timeFraction ** 2 * (1 - timeFraction) + timeFraction ** 3;
return [x, y];
}
const draw = (progress) => {
const [px, py] = bezierPath(0.2, 0.6, 0.8, 0.2, progress);
train.style.transform = `translate(${300 * px}px, ${-300 * py}px)`;
}
animate({
duration: 2000,
timing(timeFraction) {
return timeFraction * (2 - timeFraction);
},
draw,
});
}
// 缓动函数实现弹球
const bounceMove = () => {
const bounce = (timeFraction) => {
if (timeFraction < 1 / 2.75) {
return 7.5625 * timeFraction * timeFraction
} else if (timeFraction < 2 / 2.75) {
return 7.5625 * (timeFraction -= 1.5 / 2.75) * timeFraction + 0.75
} else if (amount < 2.5 / 2.75) {
return 7.5625 * (timeFraction -= 2.25 / 2.75) * timeFraction + 0.9375
} else {
return 7.5625 * (timeFraction -= 2.625 / 2.75) * timeFraction + 0.984375
}
}
const draw = (progress) => {
train.style.transform = `translate(${200 * progress.x}px, ${200 * (progress.y - 1)}px)`;
};
animate({
duration: 1000,
timing(timeFraction) {
return {
x: timeFraction,
y: bounce(timeFraction),
};
},
draw,
});
};
// 弹球
const asyncBounce = () => {
(async function () {
let damping = 0.7, // 衰变系数
duration = 1000,
height = 300;
while (height >= 1) {
const down = (progress) => {
train.style.transform = `translate(0, ${height * (progress - 1)}px)`;
};
await animate({
duration: duration,
timing(timeFraction) {
return timeFraction ** 2;
},
draw: down,
});
height *= damping ** 2;
duration *= damping;
const up = (progress) => {
train.style.transform = `translate(0, ${-height * progress}px)`;
}
await animate({
duration: duration,
timing(timeFraction) {
return timeFraction * (2 - timeFraction);
},
draw: up,
});
}
}());
};
// 椭圆
const ellipsis = () => {
const draw = (progress) => {
const x = 150 * Math.cos(Math.PI * 2 * progress);
const y = 100 * Math.sin(Math.PI * 2 * progress);
train.style.transform = `translate(${x}px, ${y}px)`;
}
animate({
duration: 2000,
timing(timeFraction) {
return timeFraction * (2 - timeFraction);
},
draw,
});
};
// 延时函数
// await animate({
// duration: 1000,
// timing: () => {},
// draw: () => {},
// });
<div id="container">
<div class="axisX"></div>
<div class="axisY"></div>
<div class="ball"></div>
<div class="button-container">
<div class="button" onClick="linear()">匀速直线</div>
<div class="button" onClick="gravity()">重力</div>
<div class="button" onClick="friction()">摩擦力</div>
<div class="button" onClick="horizontalMotion()">平抛</div>
<div class="button" onClick="horizontalMotionWidthRotate()">旋转+平抛</div>
<div class="button" onClick="arrowMove()">拉弓</div>
<div class="button" onClick="bezier()">贝塞尔曲线</div>
<div class="button" onClick="bounceMove()">缓动函数实现弹球</div>
<div class="button" onClick="asyncBounce()">弹球</div>
<div class="button" onClick="ellipsis()">椭圆</div>
</div>
</div>
body {
margin: 0;
padding: 0;
}
#container {
position: absolute;
width: 100%;
height: 100vh;
}
.axisX {
height: 2px;
background: black;
width: 100%;
position: absolute;
top: 60%;
transform: translate(0, -1px);
}
.axisY {
width: 2px;
background: black;
height: 100vh;
position: absolute;
left: 50%;
transform: translate(-1px, 0);
}
.ball {
position: absolute;
width: 40px;
height: 40px;
border-radius: 20px;
background: red;
top: calc(60% - 20px);
left: calc(50% - 20px);
}
.ball::before {
content: "";
position: absolute;
left: 10px;
top: 10px;
background: yellow;
width: 10px;
height: 10px;
border-radius: 5px;
}
.button-container {
position: absolute;
bottom: 50px;
left: 100px;
}
.button {
cursor: pointer;
border: 2px solid black;
padding: 10px 20px;
font-size: 20px;
display: inline-block;
}
.button:hover {
background: gray;
}