代码之家  ›  专栏  ›  技术社区  ›  Mevia

将动画重写为基于时间的(而不是基于平面速度的)【复制】

  •  3
  • Mevia  · 技术社区  · 7 年前

    这个问题已经有了答案:

    现在我用动画制作子弹,动画使子弹从开始点到结束点呈正弦轨迹。动画的唯一问题是它使用的是平面 speed 要移动的参数,我想将其转换为使用时间。这样你就可以说子弹会持续2秒钟,否则你就不知道它什么时候到达。

    经过一些研究,我发现子弹需要这些:

    • 时间(例如2秒,行程需要多少时间)
    • 已用时间(自启动以来经过了多少时间)
    • 开始(当旅行开始时)

    这些可以让你计算子弹被发射后经过的时间,并根据子弹的飞行时间,你可以知道它在特定的时间点应该在哪里(例如1.2秒后)。

    由于脚本使子弹以正弦方式运动,我不知道如何实现它。

    剧本:

    var cvs = document.querySelector('canvas'),
        ctx = cvs.getContext('2d'),
        w, h, cx, cy,
        resize = function() {
            w = cvs.width = window.innerWidth;
            cx = w / 2;
            h = cvs.height = window.innerHeight;
            cy = h / 2;
        },
        tools = {
            rnd: (min, max) => Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min) + 1)) + Math.ceil(min),
            flt: (min, max, dec) => parseFloat((Math.random() * (min - max) + max).toFixed(dec)),
            distance: (p1, p2) => Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)),
            rftv: (p1, p2) => Math.atan2(p2.y - p1.y, p2.x - p1.x)
        },
        loop = function() {
            ctx.fillStyle = 'rgba(0, 0, 0, 1)';
            ctx.fillRect(0, 0, w, h);
    
            for (var i = pool.length - 1; i >= 0; i--) {
                // move bullet
                pool[i].move();
    
                // bullet
                ctx.save();
                ctx.beginPath();
                ctx.arc(pool[i].x, pool[i].y, pool[i].r, Math.PI * 2, 0);
                ctx.fillStyle = 'hsl(100, 100%, 50%)';
                ctx.fill();
                ctx.closePath();
                ctx.restore();
    
                // start location
                ctx.save();
                ctx.beginPath();
                ctx.arc(pool[i].ix, pool[i].iy, pool[i].r, Math.PI * 2, 0);
                ctx.strokeStyle = 'hsl(100, 100%, 50%)';
                ctx.stroke();
                ctx.closePath();
                ctx.restore();
    
                // end location
                ctx.save();
                ctx.beginPath();
                ctx.arc(pool[i].dx, pool[i].dy, pool[i].r, Math.PI * 2, 0);
                ctx.strokeStyle = 'hsl(100, 100%, 50%)';
                ctx.stroke();
                ctx.closePath();
                ctx.restore();
    
                // remove bullet when it arrives
                if (pool[i].remaining <= 0) {
                    pool.splice(i, 1);
                }
            }
    
            requestAnimationFrame(loop);
        },
        pool = [],
        last_spawn = 0,
        spawn_interval = 0,
        spawn_limit = 1,
        spawn = function() {
            if (Date.now() - last_spawn > spawn_interval) {
                last_spawn = Date.now();
                for (var i = 0; i < spawn_limit; i++) {
                    pool.push(new particle());
                }
            }
        },
        particle = function() {
            var exvec = tools.rnd(20, w - 20),
                eyvec = tools.rnd(20, h - 20),
                svecs = {
                    x: cx,
                    y: cy
                },
                evecs = {
                    x: exvec,
                    y: eyvec
                },
                rad = tools.rftv(svecs, evecs),
                distance = tools.distance(svecs, evecs);
            this.time = 2 * 1000; // time in seconds for example 2 seconds === 2 * 1000 = 2000 ms
            this.elapsed = 0; // how much time passed since it started
            this.started = Date.now(); // time of departure
    
            this.ix = cx; // start x axis
            this.iy = cy; // start y axis
            this.dx = exvec; // end x axis
            this.dy = eyvec; // end y axis
            this.x = cx; // current x axis
            this.y = cy; // current y axis
            this.r = 10; // radius of bullet
            this.rad = rad; // needed for computation
            this.period = distance / 2; // how many axis changes
            this.distance = 0; // how much distance bullet travelled
            this.total = distance; // how much distance there is in total to be made
            this.remaining = distance; // difference between total and made
            this.amplitude = distance / 2; // how big hump
            this.speed = 2; // flat speed increase
            this.move = function() { // this is function for to calculate move
                this.elapsed = Date.now() - this.started;
                this.distance += this.speed;
                this.remaining = this.total - this.distance;
    
                this.x = this.ix + Math.cos(this.rad) * this.distance;
                this.y = this.iy + Math.sin(this.rad) * this.distance;
    
                const deviation = Math.sin(this.distance * Math.PI / this.period) * this.amplitude;
    
                this.x += Math.sin(this.rad) * deviation;
                this.y -= Math.cos(this.rad) * deviation;
            };
        };
    
    resize();
    loop();
    
    window.onresize = function() {
        resize();
    };
    
    spawn();
    body {
    	overflow:hidden;
    }
    
    canvas {
    	position:absolute;
    	top:0;
    	left:0;
    }
    <canvas></canvas>

    最重要的东西在里面 this.move 当它完成整个移动时,我已经实现了时间计算(希望它是正确的),但我不知道如何修改当前的移动代码,所以它受时间的影响,而不是 速度 .

    1 回复  |  直到 7 年前
        1
  •  2
  •   SergGr    7 年前

    你的问题,你的动画是基于 requestAnimationFrame 它不能保证在固定的时间间隔内进行调度,因此使用固定速度的逻辑

            this.distance += this.speed;
    

    不适合你,你想计算一下 this.distance 基于实际运行时间

            this.elapsed = Date.now() - this.started
    

    如果是这样的话,变化是微不足道的:对于任何给定的时刻,当前距离应该是整个距离的一部分,因为当前经过的时间是整个时间的一部分。所以就你的代码而言:

            this.distance = distance * this.elapsed / this.time;
    

    或者更新您的演示:

    var cvs = document.querySelector('canvas'),
        ctx = cvs.getContext('2d'),
        w, h, cx, cy,
        resize = function () {
            w = cvs.width = window.innerWidth;
            cx = w / 2;
            h = cvs.height = window.innerHeight;
            cy = h / 2;
        },
        tools = {
            rnd: (min, max) => Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min) + 1)) + Math.ceil(min),
            flt: (min, max, dec) => parseFloat((Math.random() * (min - max) + max).toFixed(dec)),
            distance: (p1, p2) => Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)),
            rftv: (p1, p2) => Math.atan2(p2.y - p1.y, p2.x - p1.x)
        },
        globalStart = Date.now(),
        loop = function () {
            ctx.fillStyle = 'rgba(0, 0, 0, 1)';
            ctx.fillRect(0, 0, w, h);
    
            var globalElapsed = Date.now() - globalStart;
            ctx.font = '48px serif';
            ctx.fillStyle = 'hsl(50, 100%, 50%)';
            var text = (pool.length > 0) ? globalElapsed / 1000 : "End";
            ctx.fillText(text, 40, 50);
    
            for (var i = pool.length - 1; i >= 0; i--) {
                // move bullet
                pool[i].move();
    
                // bullet
                ctx.save();
                ctx.beginPath();
                ctx.arc(pool[i].x, pool[i].y, pool[i].r, Math.PI * 2, 0);
                ctx.fillStyle = 'hsl(100, 100%, 50%)';
                ctx.fill();
                ctx.closePath();
                ctx.restore();
    
                // start location
                ctx.save();
                ctx.beginPath();
                ctx.arc(pool[i].ix, pool[i].iy, pool[i].r, Math.PI * 2, 0);
                ctx.strokeStyle = 'hsl(100, 100%, 50%)';
                ctx.stroke();
                ctx.closePath();
                ctx.restore();
    
                // end location
                ctx.save();
                ctx.beginPath();
                ctx.arc(pool[i].dx, pool[i].dy, pool[i].r, Math.PI * 2, 0);
                ctx.strokeStyle = 'hsl(100, 100%, 50%)';
                ctx.stroke();
                ctx.closePath();
                ctx.restore();
    
                // remove bullet when it arrives
                if (pool[i].remaining <= 0) {
                    pool.splice(i, 1);
                }
            }
    
            requestAnimationFrame(loop);
        },
        pool = [],
        last_spawn = 0,
        spawn_interval = 0,
        spawn_limit = 1,
        spawn = function () {
            if (Date.now() - last_spawn > spawn_interval) {
                last_spawn = Date.now();
                for (var i = 0; i < spawn_limit; i++) {
                    pool.push(new particle());
                }
            }
        },
        particle = function () {
            var exvec = tools.rnd(20, w - 20),
                eyvec = tools.rnd(20, h - 20),
                svecs = {
                    x: cx,
                    y: cy
                },
                evecs = {
                    x: exvec,
                    y: eyvec
                },
                rad = tools.rftv(svecs, evecs),
                distance = tools.distance(svecs, evecs);
            this.time = 10 * 1000; // time in seconds for example 2 seconds === 2 * 1000 = 2000 ms
            this.elapsed = 0; // how much time passed since it started
            this.started = Date.now(); // time of departure
    
            this.ix = cx; // start x axis
            this.iy = cy; // start y axis
            this.dx = exvec; // end x axis
            this.dy = eyvec; // end y axis
            this.x = cx; // current x axis
            this.y = cy; // current y axis
            this.r = 10; // radius of bullet
            this.rad = rad; // needed for computation
            this.period = distance / 2; // how many axis changes
            this.distance = 0; // how much distance bullet travelled
            this.total = distance; // how much distance there is in total to be made
            this.remaining = distance; // difference between total and made
            this.amplitude = distance / 4; // how big hump
            // this.speed = 2; // flat speed increase
            this.move = function () { // this is function for to calculate move
                this.elapsed = Date.now() - this.started;
                // this.distance += this.speed;
                this.distance = distance * this.elapsed / this.time;
                this.remaining = this.total - this.distance;
    
                this.x = this.ix + Math.cos(this.rad) * this.distance;
                this.y = this.iy + Math.sin(this.rad) * this.distance;
    
                const deviation = Math.sin(this.distance * Math.PI / this.period) * this.amplitude;
    
                this.x += Math.sin(this.rad) * deviation;
                this.y -= Math.cos(this.rad) * deviation;
            };
        };
    
    resize();
    loop();
    
    window.onresize = function () {
        resize();
    };
    
    spawn();
    body {
    	overflow:hidden;
    }
    
    canvas {
    	position:absolute;
    	top:0;
    	left:0;
    }
    <canvas></canvas>

    注意

    1. 我变了 this.time 到10秒到秒,这样动画更容易跟踪
    2. 我添加了 globalStart globalElapsed 在内部绘制实际时间 loop
    3. 我减小了曲线的尺寸,以便更好地适应较小的区域。