SOURCE

console 命令行工具 X clear

                    
>
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;
}