编程技术文章分享与教程

网站首页 > 技术文章 正文

用JS做个自由落体的球

hmc789 2024-11-24 16:43:53 技术文章 1 ℃

因为用到了一点点物理知识, 我们可以称这为极简化的Javascript物理引擎

大叔惯例,先上效果

本例通过canvas画布来实现

虽然很简单, 也是先把舞台准备一下

1.准备个HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
 </head>
<body></body>
</html>


2.加个基础样式

    <style>
        * {
            box-sizing: border-box;
            padding: 0;
            margin: 0;
        }
        main {
            width: 100vw;height: 100vh;
            background: hsl(0deg, 0%, 10%);
        }
    </style>

样式的作用是去掉所有元素的外边距、内间距,并把 <main> 元素的宽高设置为与浏览器可视区域相同,背景色为深灰色。

hsl(hue, saturation, brightness) 为 css 颜色表示法之一,参数分别为色相,饱和度和亮度


3.添加 canvas 元素

    <main>
        <canvas id="gamecanvas"></canvas>
    </main>


4.然后就可以用JS来画图了

    const canvas = document.getElementById("gamecanvas");        //通过 canvas 的 id 获取 canvas 元素对象。
    const ctx = canvas.getContext("2d");                        // getContext()  需要一个参数,用于表明是绘制 2d 图像,还是使用 webgl 绘制 3d 图象,这里选择 2d
    canvas.width = window.innerWidth;                           //宽高设置为浏览器可视区域的宽高
    canvas.height = window.innerHeight;
    let _width = canvas.width;
    let _height = canvas.height;
    ctx.fillStyle = "hsl(170, 100%, 50%)";    //给 context 设置颜色
    ctx.beginPath();                                     //开始绘图
    ctx.arc(150, 100, 50, 0, 2 * Math.PI);     //绘制圆形,它接收 5 个参数,前两个为圆心的 x、y 坐标,第 3 个为半径长度, 第 4 个和第 5 个分别是起始角度和结束角度
    ctx.fill();                                                 //给圆形填上颜色

试运行看看

这个时候小球还是处于静止状态, 要让它动起来, 就要通过程序修改它的圆心坐标

让小球移动过程其实就是: 画圆 > 擦除 > 在新坐标1上画圆 > 擦除 > 在新坐标2上画圆...

因为人眼的视觉停留效应, 只要这个过程足够快, 那么在人眼看来这个球就是在做连续的运动而不会看到闪动.

需要达到多快呢?

画圆 > 擦除 > 再画圆 这么一个过程可以看作"一帧"


然后每秒超过24帧就可以, 帧数越高看上去运动就越平滑.

在 JavaScript 中,浏览器提供了 window.requestAnimationFrame() 方法,它接收一个回调函数作为参数,每一次执行回调函数就相当于 1 帧动画,我们需要通过递归或循环连续调用它,浏览器会尽可能地在 1 秒内执行 60 次回调函数。那么利用它,我们就可以对 canvas 进行重绘,以实现小球的移动效果。

基础代码结构看上去的样子:

function drawBall() {
  window.requestAnimationFrame(drawBall);
}
window.requestAnimationFrame(drawBall);

这个drawBall()函数, 就是60次/秒的函数

把刚才的代码重构一下

let x = 150;            //坐标x
let y = 100;            //坐标y
let r = 60;              //半径
function drawBall(now) {
  	ctx.fillStyle = "hsl(170, 100%, 50%)";
  	ctx.beginPath();
  	ctx.arc(x, y, r, 0, 2 * Math.PI);
  	ctx.fill();
    window.requestAnimationFrame(drawBall);
}
window.requestAnimationFrame(drawBall);

计算圆心坐标 x、y 的移动距离,我们需要速度和时间, 速度就是vy, 还需要有时间

window.requestAnimationFrame() 会把当前时间的毫秒数(即时间戳)传递给回调函数,我们可以把本次调用的时间戳保存起来,然后在下一次调用时计算出执行这 1 帧动画消耗了多少秒,然后根据这个秒数和 x、y 轴方向上的速度去计算移动距离,分别加到 x 和 y 上,以获得最新的位置。

改进代码如下

		let x = 100;            //坐标
    let y = 100;
    let r = 60;             //半径
    let vy = 25;            //移动Y轴的速度
    let startTime;
    function drawBall(now) {
        if (!startTime) {
            startTime = now;
        }
        let seconds = (now - startTime) / 1000;
        startTime = now;
        y += vy * seconds;          // 更新Y坐标
        ctx.clearRect(0, 0, width, height);	// 清除画布

        ctx.fillStyle = "hsl(170, 100%, 50%)";
        ctx.beginPath();
        ctx.arc(x, y, r, 0, 2 * Math.PI);
        ctx.fill();

      	window.requestAnimationFrame(drawBall);                  //每次执行完毕后继续调用 window.requestAnimationFrame(process)进行下一次循环
    }
    window.requestAnimationFrame(drawBall);

目前只是个匀速运动, 我们要为它加上重力效果

重力加速度的公式: v=gt2

加速度常量g是个恒定值9.8

//先在函数外添加一个常量
const gravity = 9.80;
...
//函数内部, 在计算y坐标前面加一行:
vy += gravity * (seconds^2); // 重力加速度      v=gt2
y += vy * seconds;

这里我们不希望球跑到屏幕外面去, 同时加个边界判断

        //边界检查
        let oy = y + r;        //y+r
        if(oy<height){
            window.requestAnimationFrame(drawBall);                  //每次执行完毕后继续调用 window.requestAnimationFrame(process)进行下一次循环
        }

这样就可以达成文章最开头的运动效果了

很简单, 同时其实也很有意思

比如加个半径变化控制, 就会看到球越往下掉就越小/大

r = r - 0.8;

下面是全部代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <style>
        * {
            box-sizing: border-box;
            padding: 0;
            margin: 0;
            font-family: sans-serif;
        }
        main {
            width: 100vw;
            height: 100vh;
            background: hsl(0deg, 0%, 10%);
        }
    </style>
</head>
<body>
    <main>
        <canvas id="gamecanvas"></canvas>
    </main>
</body>
<script>
    const canvas = document.getElementById("gamecanvas");        //通过 canvas 的 id 获取 canvas 元素对象。
    const ctx = canvas.getContext("2d");                        // getContext()  需要一个参数,用于表明是绘制 2d 图像,还是使用 webgl 绘制 3d 图象,这里选择 2d
    canvas.width = window.innerWidth;                           //宽高设置为浏览器可视区域的宽高
    canvas.height = window.innerHeight;
    let width = canvas.width;
    let height = canvas.height;

    const gravity = 9.80;
    let x = 100;            //坐标
    let y = 100;
    let r = 60;             //半径
    let vy = 25;            //移动Y轴的速度
    let startTime;
    function drawBall(now) {
        if (!startTime) {
            startTime = now;
        }
        let seconds = (now - startTime) / 1000;
        startTime = now;
        vy += gravity * (seconds^2); // 重力加速度      v=gt2
        y += vy * seconds;
        r = r - 0.8;

      	ctx.clearRect(0, 0, width, height);
        ctx.fillStyle = "hsl(170, 100%, 50%)";
        ctx.beginPath();
        ctx.arc(x, y, r, 0, 2 * Math.PI);
        ctx.fill();

        //边界检查
        let oy = y + r;
        if(oy<height){
            window.requestAnimationFrame(drawBall);                  //每次执行完毕后继续调用 window.requestAnimationFrame(process)进行下一次循环
        }
    }
    window.requestAnimationFrame(drawBall);
</script>


这期就酱,下期再见[酷拽]

复杂的问题简单化

每次只关注一个知识点

对技术有兴趣的小伙伴可以关注我, 我经常分享各种奇奇怪怪的技术知识

Tags:

标签列表
最新留言