console
function animate ({timing, draw, duration}) {
let start = performance.now();
return new Promise(resolve => {
requestAnimationFrame(function animate(time) {
let timeFraction = (time - start) / duration;
if (timeFraction > 1) timeFraction = 1;
let progress = timing(timeFraction)
draw(progress);
if (timeFraction < 1) {
requestAnimationFrame(animate);
} else {
resolve(timing);
}
});
});
}
const train = document.querySelector('.ball');
const linear = () => {
const draw = (progress) => {
train.style.transform = `translate(${progress}px, 0)`;
}
animate({
duration: 1000,
timing(timeFraction) {
return timeFraction * 100;
},
draw,
});
};
const gravity = () => {
const draw = (progress) => {
train.style.transform = `translate(0, ${500 * (progress - 1)}px)`;
};
animate({
duration: 1000,
timing(timeFraction) {
return timeFraction ** 2;
},
draw,
});
};
const friction = () => {
const draw = (progress) => {
train.style.transform = `translate(0, ${500 * (progress - 1)}px)`;
};
animate({
duration: 1000,
timing(timeFraction) {
return timeFraction * (2 - timeFraction);
},
draw,
});
};
const horizontalMotion = () => {
const draw = (progress) => {
train.style.transform = `translate(${500 * progress.x}px, ${500 * (progress.y - 1)}px)`;
};
animate({
duration: 1000,
timing(timeFraction) {
return {
x: timeFraction,
y: timeFraction ** 2,
}
},
draw,
});
};
const horizontalMotionWidthRotate = () => {
const draw = (progress) => {
train.style.transform = `translate(${500 * progress.x}px, ${500 * (progress.y - 1)}px) rotate(${2000 * progress.rotate}deg)`;
};
animate({
duration: 2000,
timing(timeFraction) {
return {
x: timeFraction,
y: timeFraction ** 2,
rotate: timeFraction,
}
},
draw,
});
};
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,
});
};
<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;
}