Javascript 如何";动画化;ASCII艺术网格中的更改,一次一个节点,而不冻结浏览器?
我有一个ASCII艺术的“寻路可视化工具”,我是模仿一个流行的。ASCII art显示一个n*m大小的板,板上有n*m个节点 我目前的目标是慢慢地改变面向用户板上文本的外观,一个角色一个角色地改变,直到“动画”完成。我打算通过寻路算法为节点的“扫描”和从开始节点到结束节点的最短路径设置动画。动画只是在一系列div中更改文本,应该需要几秒钟的时间。我还计划添加一个带有颜色或其他东西的CSS动画 基本上,用户最终会看到这样的情况,其中*是开始节点,x是结束节点,+表示路径:Javascript 如何";动画化;ASCII艺术网格中的更改,一次一个节点,而不冻结浏览器?,javascript,html,Javascript,Html,我有一个ASCII艺术的“寻路可视化工具”,我是模仿一个流行的。ASCII art显示一个n*m大小的板,板上有n*m个节点 我目前的目标是慢慢地改变面向用户板上文本的外观,一个角色一个角色地改变,直到“动画”完成。我打算通过寻路算法为节点的“扫描”和从开始节点到结束节点的最短路径设置动画。动画只是在一系列div中更改文本,应该需要几秒钟的时间。我还计划添加一个带有颜色或其他东西的CSS动画 基本上,用户最终会看到这样的情况,其中*是开始节点,x是结束节点,+表示路径: .... ..*. ..
....
..*.
..+.
.++.
.x..
.... (. represents an empty space)
打开setTimeout和其他选项后,我可以告诉您:
JavaScript的设计并不是为了允许用户延迟浏览器中的代码执行
我找到了很多冻结浏览器的方法。我还尝试在setTimeout(resolve,毫秒)
发生后设置一系列承诺以解决问题,其中毫秒持续增加(请参见下面的代码)。我的期望是,numOfAnimationsForPath
承诺的数量将被设置,并在每个承诺解决时触发电路板外观的变化(形成路径的外观)。但是,当我单击“动画”按钮时看到路径显示时,它们似乎都会立即解决(?)
const shortestpath和scanningorder=dijkstras(网格);
承诺描述(1000,最短路径和扫描顺序[0]。路径,最短路径和扫描顺序[1])
函数承诺描述(动画延迟、算法路径、扫描目标){
const numOfAnimationsForScanning=扫描目标.length;
const numOfAnimationsForPath=algoPath.length;
for(设i=1;isetTimeout(解析,毫秒))
}
函数renderNode(x,y,type){
如果(类型==“扫描”){
const targetDiv=getLocationBy坐标(x,y);
targetDiv.innerHTML=已访问\u节点;
}else if(类型==“路径”){
const targetDiv=getLocationBy坐标(x,y);
targetDiv.innerHTML=最短路径节点;
}否则{
throw“向'type'参数传递了不正确的参数”
}
}
在我的另一次尝试中,我尝试生成pathLength
setTimeout
s的数量,如下所示:
const shortestPathAndScanningOrder = dijkstras(grid);
renderByTimer(10000, shortestPathAndScanningOrder[0].path, shortestPathAndScanningOrder[1])
function renderByTimer(animationDelay, algoPath, scanTargets) {
const numOfAnimations = algoPath.length;
for (let i = 1; i < numOfAnimations - 1; i++) {
const xCoordinate = algoPath[i][0];
const yCoordinate = algoPath[i][1];
setTimeout(i * animationDelay, updateCoordinatesWithTrailMarker(xCoordinate, yCoordinate))
}
}
谢谢@torbinsky 使用
setTimeout
应该完全可以做到这一点。可能问题是您立即注册了10000个超时。你的道路越长,这种方法就越糟糕
因此,与立即安排所有更新不同,您应该使用递归算法,其中每个“帧”都安排下一帧的超时。大概是这样的:
const shortestPathAndScanningOrder = dijkstras(grid);
renderByTimer(10000, shortestPathAndScanningOrder[0].path, shortestPathAndScanningOrder[1])
function renderByTimer(animationDelay, algoPath, scanTargets) {
const numOfAnimations = algoPath.length;
// Renders the current frame and schedules the next frame
// This repeats until we have exhausted all frames
function renderIn(msToNextFrame, frameNum){
if(frameNum >= numOfAnimations){
// end recursion
return
}
// Immediately render the current frame
const xCoordinate = algoPath[frameNum][0];
const yCoordinate = algoPath[frameNum][1];
updateCoordinatesWithTrailMarker(xCoordinate, yCoordinate);
// Schedule the next frame for rendering
setTimeout(msToNextFrame, function(){
renderIn(msToNextFrame, frameNum + 1)
});
}
// Render first frame
renderIn(1000, 1)
}
注意:我在StackOverflow代码snipppet中编写了这段代码。因此,我无法测试它,因为我没有您的其余代码来完全运行它。将其视为伪代码,即使它可能有效;)
在任何情况下,我使用的方法是在任何给定时间只安排1个超时。这样,您就不会在浏览器上同时出现1000次超时。这种方法将支持非常长的路径 这是一种通用的动画技术,并不是ASCII艺术独有的,除了老式ASCII艺术一次渲染一个(慢速)字符,而不是一次渲染一个快速像素帧。(我已经足够大了,还记得在本地主机上以9600bps的速度通过硬连线甘道夫调制解调器到z19终端观看ASCII“电影”流……一切旧的东西都是新的!:) 无论如何,在我看来,排队等待一系列设置超时并不是最好的计划。相反,你应该做的是用window.requestAnimationFrame或setTimeout将下一个事件排队。我推荐rAF,因为它不会在浏览器选项卡未显示时触发 接下来,一旦进入事件,您将查看时钟增量(使用performance.now()的快照),以确定在“now”和上次函数运行之间应该绘制什么。然后更新显示,并触发下一个事件
这将产生一个平滑的动画,可以很好地利用系统资源播放。只需稍加修改。干得好,谢谢你的帮助!
const shortestPathAndScanningOrder = dijkstras(grid);
renderByTimer(10000, shortestPathAndScanningOrder[0].path, shortestPathAndScanningOrder[1])
function renderByTimer(animationDelay, algoPath, scanTargets) {
const numOfAnimations = algoPath.length;
// Renders the current frame and schedules the next frame
// This repeats until we have exhausted all frames
function renderIn(msToNextFrame, frameNum){
if(frameNum >= numOfAnimations){
// end recursion
return
}
// Immediately render the current frame
const xCoordinate = algoPath[frameNum][0];
const yCoordinate = algoPath[frameNum][1];
updateCoordinatesWithTrailMarker(xCoordinate, yCoordinate);
// Schedule the next frame for rendering
setTimeout(msToNextFrame, function(){
renderIn(msToNextFrame, frameNum + 1)
});
}
// Render first frame
renderIn(1000, 1)
}