Javascript 对于D3.js,重新绘制或“绘制”是否更好;“移动”;物体?

Javascript 对于D3.js,重新绘制或“绘制”是否更好;“移动”;物体?,javascript,canvas,d3.js,Javascript,Canvas,D3.js,我一直在尝试动画 通过清除整个画布并在每一帧(或“勾选”)的新位置重新绘制对象,在画布上设置对象动画非常简单: 这是一个坏习惯还是我做得对 例如,如果您正在制作一个满是四处移动的对象的屏幕,那么通过更新它们在每个帧中的属性(例如,x,y坐标)来设置它们的动画是更好的做法吗 或者,也许还有其他我完全不知道的方法,不是吗 注意:我的动画一次可能包含100-200个视图中的对象。最好移动它们,因为这是唯一可以无错误地设置动画的方法 在d3.js中,对象是数据绑定的。清除和重画“画布”不是正确的方法。首

我一直在尝试动画

通过清除整个画布并在每一帧(或“勾选”)的新位置重新绘制对象,在画布上设置对象动画非常简单:

这是一个坏习惯还是我做得对

例如,如果您正在制作一个满是四处移动的对象的屏幕,那么通过更新它们在每个帧中的属性(例如,x,y坐标)来设置它们的动画是更好的做法吗

或者,也许还有其他我完全不知道的方法,不是吗


注意:我的动画一次可能包含100-200个视图中的对象。

最好移动它们,因为这是唯一可以无错误地设置动画的方法

在d3.js中,对象是数据绑定的。清除和重画“画布”不是正确的方法。首先,它不是画布,而是网页,任何清除和重画都由浏览器自己处理。基本上,您的工作是将数据绑定到SVG

您需要使用d3事件,
enter
exit
update
,它处理修改数据绑定基础数据时SVG的行为,并让d3处理动画

最简单的例子如下:

  • 选择元素,并将选择存储在变量中
  • var svg=d3.选择(“svg”)

    var circles=svg.selectAll('circle')

  • 现在我们需要将一些数据绑定到圆
  • var-databoundCircles=circles.data([12,13,14,15,66])

    这些数据可以是任何东西。通常我希望有一个对象列表,但这些都是简单的数字

  • 当数据出现时,处理事物的“生成”方式
  • databoundCircles.enter().append('circle')

  • 处理删除数据时发生的情况
  • databoundCircles.exit().remove()

  • 处理数据更新时发生的情况
  • databoundCircles.attr('r',函数(d,i){返回d*2;})

    这将在数据更改时更改半径

    并回顾该教程:

  • 输入-输入元素,进入阶段

  • 更新-持久元素,停留在舞台上

  • 退出-退出元素,退出舞台

  • 总之:不要像现在这样做。确保您专门使用这些事件来处理元素的生命周期

    专业提示:如果您使用的是对象列表,请确保通过
    id
    或某些唯一标识符绑定数据,否则动画可能会随着时间的推移表现异常。请记住,您是在将数据绑定到SVG,而不仅仅是在擦除和重画画布

    d3.selectAll('circle')。数据([{id:1},{id:2}],函数(d){return d.id;})

    请注意可选的第二个参数,它告诉我们如何绑定数据!非常重要

    var svg=d3.选择(“svg”);
    //数据如下所示。
    风险值数据=[{
    id:1,
    r:3,
    x:35,
    y:30
    }, {
    id:2,
    r:5,
    x:30,
    y:35
    }];
    //数据生成器生成上面的列表
    函数newList(){
    //只需制作一个包含数字1的简单数组
    var items=新数组(randoNum(1,10)).fill(1)
    //制作数据片段。ID很重要!
    返回项目。映射(函数(val,i){
    var r=randoNum(1,16)
    返回{
    id:我,
    r:r,
    x:randoNum(1200)+r,
    y:randoNum(1100)+r
    }
    });
    }
    //我只是用这个来做rando数字。
    函数randoNum(从,到){
    返回Math.floor(Math.random()*(to-from)+from);
    }
    功能更新(数据){
    //1.获得圆(第一次通过时没有圆!)
    var circles=svg.selectAll('circle');
    //2.绑定数据
    var databoundCircles=circles.data(数据,函数(d){
    返回d.id;
    });
    //3.进入
    var enter=databoundCircles.enter()
    .append('圆')
    .attr('r',0)
    //4.退出
    databoundCircles.exit()
    .transition()
    .attr('r',0)
    .remove();
    //5.更新
    //(过渡后的一切都是粗花呢)
    数据边界圆
    .attr('fill',函数(d,i){
    var h=parseInt(i.toString(16));
    返回“#”+[h,h,h].join(“”);
    })
    .transition()
    .持续时间(1000)
    .attr('r',函数(d,i){
    返回d.r*4
    })
    .attr('cx',函数(d,i){
    返回d.x*2;
    })
    .attr('cy',函数(d,i){
    返回d.y*2
    })
    ;
    }
    //第一次运行时,我使用上面的示例数据
    更新(数据);
    //现在我每隔几秒钟更新一次
    //观察d3如何“跟踪”每个圆圈
    setInterval(函数(){
    更新(newList());
    }, 2000);
    
    
    
    这是一个坏习惯还是我做得对

    是的,这是个坏习惯。在正常情况下,我喜欢称之为惰性编码:清除SVG(或其他)并再次绘制dataviz

    但是,在您的情况下,情况更糟:您最终将编写大量的代码(但不完全是懒惰),忽略了可以轻松完成您想要的任务的d3.transition()
    。这就引出了你的第二个问题:

    或者,也许还有其他我完全不知道的方法,不是吗

    是的,正如我刚才所说,它被称为
    transition()

    最后,你说:

    注意:我的动画一次可能包含100-200个视图中的对象

    首先,现代浏览器可以很好地处理这个问题。其次,您仍然需要手动删除并重新绘制所有这些元素。如果对这两种方法进行基准测试,可能情况会更糟

    因此,只需使用
    d3.transition()

    您可以随时更改元素的数据(或属性),并将它们“移动”(或转换)到调用转换的新值。例如
    // Inside requestAnimationFrame(...) callback
    
    // Clear canvas
    canvas.selectAll('*').remove();
    
    // ... calculate position of x and y
    // x, y = ...
    
    // Add object in new position
    canvas.append('circle')
        .attr('cx', x)
        .attr('cy', y)
        .attr('r', 10)
        .attr('fill', '#ffffff');