如何使所有的条以不同的速度同时平滑地移动?
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 动画的人来说,这仍然是最先进的。
唯一的问题是您的代码没有使用连续技术来计算位置。相反,您使用加速度更新当前速度并使用当前速度更新位置。我们想要一个连续函数,它不仅可以接受 1s
、2s
、3s
、... 等时间,还可以接受 0.18641s
和 2.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 的练习。我们也可能决定不想要所有这些临时对象,并选择改变进度条静态列表中的值。不过,我只会在胁迫下这样做,因为我更喜欢不可变数据。但是,如果存在性能问题,这将不会太难实现。
我有 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 动画的人来说,这仍然是最先进的。
唯一的问题是您的代码没有使用连续技术来计算位置。相反,您使用加速度更新当前速度并使用当前速度更新位置。我们想要一个连续函数,它不仅可以接受 1s
、2s
、3s
、... 等时间,还可以接受 0.18641s
和 2.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 的练习。我们也可能决定不想要所有这些临时对象,并选择改变进度条静态列表中的值。不过,我只会在胁迫下这样做,因为我更喜欢不可变数据。但是,如果存在性能问题,这将不会太难实现。