如何使所有的条以不同的速度同时平滑地移动?

How to make all the bars move smoothly at the same time even though they are moving at different speeds?

我有 5 个进度条,它们都在同一时间以不同的速度移动。 它们的速度是动态的,需要以编程方式修改,这就是为什么这是用 JS 实现的,而不是 CSS 使用动画的原因。这是一个 jsfiddle:https://jsfiddle.net/ezg97/ey319g48/7/
问题是这样的:进度条移动时断断续续的,我想让它们都平滑移动,我不知道如何实现这个?这是我正在制作的游戏。

如果它们以非常小的数字递增,例如 1,则它们看起来几乎是在平稳移动;然而,实际上他们将能够以任何速度移动。

代码也可以在这里查看:

// array to maintain progress bars
var pbArr = [{
    pid: 'bar1', // parent container id
    speed: 31, // max speed value
    accl: 10, // speed at which it will accelerate
    curr: 0, // current speed
    dist: 0 // total distance/displacement 
}, {
    pid: 'bar2',
    speed: 40,
        accl: 1,
    curr: 0,
    dist: 0
}, {
    pid: 'bar3',
    speed: 21,
        accl: 20,
    curr: 0,
    dist: 0
}, {
    pid: 'bar4',
    speed: 35,
        accl: 6,
    curr: 0,
    dist: 0
}, {
    pid: 'bar5',
    speed: 25,
    accl: 16,
    curr: 0,
    dist: 0
}];

var loopCnt = 1; // loop count to maintain width
var pb_timeout; // progress bar timeout function

// create progress bar funtion

var createPB = function () {

    var is_all_pb_complete = true; // flag to check whether all progress bar are completed executed

    for (var i = 0; i < pbArr.length; i++) {
        var childDiv = document.querySelector('#' + pbArr[i].pid + ' div'); // child div
        
        // When initially starting, set current speed to acceleration speed
        if (pbArr[i].curr === 0) {
            pbArr[i].curr = pbArr[i].accl;
        } else {
            // if the current speed + the acceleration is less than top speed, add more acceleration
            if ((pbArr[i].curr + pbArr[i].accl) < pbArr[i].speed) {
                pbArr[i].curr += pbArr[i].accl;
          } 
          // if the current speed + the acceleration is greater than acceleration, then make current speed equal to top speed
          else {
                pbArr[i].curr = pbArr[i].speed;
          }
        }
        // removed output: console.log(pbArr[i]);
        // add the current speed to the distance traveled already
        pbArr[i].dist += pbArr[i].curr;
        var newWidth = pbArr[i].dist; // new width
        
        //if your new distance traveled is less than 100, add it to the progress bar
        if (newWidth <= 100) {
            is_all_pb_complete = false;
            childDiv.style.width = newWidth + '%';
        } else {
            // if your new distance traveled is greater than or equal to 100, set the prgoress bar to 100%
            childDiv.style.width = '100%';
        }

    }

    if (is_all_pb_complete) { // if true, then clear timeout
        clearTimeout(pb_timeout);
        return;
    }

    loopCnt++; // increment loop count

    // recall function
    pb_timeout = setTimeout(function () {
        createPB();
    }, 1000);
}

// call function to initiate progress bars
createPB();
.bar{
    width:200px;
    height:20px;
    background-color:black;
    position:relative;
}
.child{
    position:abosoute;
    top:0px;
    left:0px;
    background-color:red;
    height:20px;
}
.clr{
    width:100%;
    height:2px;    
}
<div class="bar" id="bar1"><div class="child"></div></div>
<div class="clr"></div>
<div class="bar" id="bar2"><div class="child"></div></div>
<div class="clr"></div>
<div class="bar" id="bar3"><div class="child"></div></div>
<div class="clr"></div>
<div class="bar" id="bar4"><div class="child"></div></div>
<div class="clr"></div>
<div class="bar" id="bar5"><div class="child"></div></div>

transition: all 0.1s ease; 添加到您的 .child class 上 一点 有点帮助,但没有完全解决它。

这个问题刚来的时候就看到了,没时间问。另一个答案最近使它复活了。

当然,问题在于您每秒重绘一次。这不可能导致流畅的动画。我们可以尝试使用一些固定的 sub-second 增量来解决这个问题,并在固定的时间表上重新运行。这会比这更平滑,但仍然可能会不稳定,因为浏览器可能会根据其他负载以不同的速率接收您的请求。下一个技巧是使用 setTimeout/setInterval 调用下一步,并使用现在和我们开始确定我们在动画中的位置之间的时间差,进行适当的更改。这是十年前最先进的技术,但浏览器供应商意识到,如果他们提供一个 API 来使这更容易,那会更容易。即requestAnimationFrame。我们为这个函数提供一个回调,它用当前经过的时间调用我们。我们使用经过的时间来确定动画的当前状态并更新 DOM。 AFAICT,对于那些出于某种原因不能使用 CSS 动画的人来说,这仍然是最先进的。

唯一的问题是您的代码没有使用连续技术来计算位置。相反,您使用加速度更新当前速度并使用当前速度更新位置。我们想要一个连续函数,它不仅可以接受 1s2s3s、... 等时间,还可以接受 0.18641s2.5184s。为此,我们需要使用一些简单的微积分。

您可能记得给定时间的速度函数是给定时间的位置函数的导数。加速度的函数是速度的导数。有了一些初始值,我们可以用反导数走另一条路。如果这听起来很复杂,给定一个固定的加速度,就像在这个问题中一样,但实际上并非如此。唯一的复杂性来自最大速度值。如果没有,给定加速度 a、初始速度 0 和初始位置 0,我们可以计算速度 v 和位置 p,像这样:

v = a * t
p = (a/2) * t^2 

仅通过最简单的反导数。

知道我们有一个最大速度,比如 m,我们将不得不调整这些。这些现在更加复杂,但并非绝对可怕:

v = a * t                           when t < m/a
v = m                               when t >= m/a

p = (a/2) * t^2                     when t < m/a
p = m ( t - m/a) + (a/2 * (m/a)^2)  when t >= m/a

最后一个可以简化为

p = (a/2) * t^2                     when t < m/a
p = m ( t - m/a) + (m^2 / 2a)       when t >= m/a

我们现在可以使用不可变数据结构(我的首选)编写此代码,通过从时间、初始加速度和最大速度应用此函数,这些数据结构在每一步都转换为新的数据结构,以获得新的dist 属性。它可能看起来像这样:

const processPbs = (pbs) => (time) => {
  const t = time / 1000
  const newPbs = pbs .map (setPosition (t))
  displayPbs (newPbs)
  if (newPbs .some (({dist}) => dist < 100)) {
    window .requestAnimationFrame (processPbs (newPbs))
  }
}

const setPosition = (t) => (pb) => ({
  ... pb,
  dist: Math .min (100, t < pb .speed / pb .accl
    ? (pb .accl /2) * t ** 2
    : pb .speed * (t - pb .speed / pb .accl) + ((pb .speed) ** 2 / (2 * pb.accl)))
})

const displayPbs = (pbs) => pbs .forEach (({pid, dist}) =>
  document.querySelector('#' + pid + ' div') .style .width = dist + '%'
)

var pbArr = [{pid: 'bar1', speed: 31, accl: 10, dist: 0}, {pid: 'bar2', speed: 40, accl: 1,  dist: 0}, {pid: 'bar3', speed: 21, accl: 20, dist: 0}, {pid: 'bar4', speed: 35, accl: 6,  dist: 0}, {pid: 'bar5', speed: 25, accl: 16, dist: 0}]

window .requestAnimationFrame (processPbs (pbArr))
.bar {width: 200px; height: 20px; background-color: black; position: relative;} .child {position: abosoute; top: 0px; left: 0px; background-color: red; height:20px;} .clr {width:100%; height:2px;}
<div class="bar" id="bar1"><div class="child"></div></div><div class="clr"></div> <div class="bar" id="bar2"><div class="child"></div></div><div class="clr"></div> <div class="bar" id="bar3"><div class="child"></div></div><div class="clr"></div> <div class="bar" id="bar4"><div class="child"></div></div><div class="clr"></div> <div class="bar" id="bar5"><div class="child"></div></div>

我们的主要功能是 processPbs,它接受进度条列表和 returns 一个需要时间的函数,使用它来创建进度条的新版本,使用公式上面设置 dist 属性,然后显示新柱,如果有任何柱尚未达到神奇的 100 阈值,则请求额外的动画帧以重复该过程.

它使用两个助手。 setPosition 使用该公式进行实际计算,displayPbs 更新 DOM。一个强烈的建议是您始终将 DOM 更改从您的逻辑中隔离到单独的函数中。

这里有一些我们可能想要更改的地方。我们应该将 DOM 节点缓存在我们的对象中,而不是在每次迭代时重新计算它们。我将其留作 reader 的练习。我们也可能决定不想要所有这些临时对象,并选择改变进度条静态列表中的值。不过,我只会在胁迫下这样做,因为我更喜欢不可变数据。但是,如果存在性能问题,这将不会太难实现。