使用D3.js更好地重新绘制或"移动"对象?

oro*_*aki 2 javascript canvas d3.js

我一直在试验动画.

通过清除整个画布并在每个帧(或"tick")的新位置重绘它,可以非常简单地在画布上设置对象的动画:

// 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');
Run Code Online (Sandbox Code Playgroud)

这是一种不好的做法,还是我做得对?

例如,如果您正在制作一个充满物体移动的屏幕,那么通过更新每个帧中的属性(例如,x,y坐标)来设置动画是否更好?

或者,也许还有其他方法我完全没有意识到,不是吗?

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

the*_*ian 5

移动它们会更好,因为这是您可以无错误地制作动画的唯一方法.

在d3.js中,想法是对象是数据绑定的.清除和重绘'画布'不是正确的方法.首先它不是画布,它是一个网页,任何清除和重绘都由浏览器本身处理.你的工作就是将数据绑定到SVG.

你需要利用D3事件,enter,exit,update它处理当数据绑定基础数据被修改,让D3处理动画的SVG的行为.

最简单的例子是:https://bost.ocks.org/mike/circles/

  1. 选择元素,并将选择存储在变量中

var svg= d3.select("svg");

var circles = svg.selectAll('circle');

  1. 现在我们需要将某些东西数据绑定到圆圈.

var databoundCircles = circles.data([12,13,14,15,66]);

这些数据可以是任何东西.通常我会期待一个对象列表,但这些都是简单的数字.

  1. 处理数据出现时"如何制作"

databoundCircles.enter().append('circle');;

  1. 处理删除数据时发生的事情

databoundCircles.exit().remove()

  1. 处理数据更新时发生的事情

databoundCircles.attr('r', function(d, i) { return d * 2; })

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

并从该教程回顾:

  1. 输入 - 传入元素,进入舞台.

  2. 更新 - 持久性元素,留在舞台上.

  3. 退出 - 退出元素,退出舞台.

所以总结:不要像你这样做.确保您专门使用这些事件来处理元素的生命周期.

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

d3.selectAll('circle').data([{id:1},{id:2}], function(d) { return d.id; });

记下可选的第二个参数,告诉我们如何绑定数据!很重要!

var svg = d3.select("svg");

//the data looks like this.
var data = [{
    id: 1,
    r: 3,
    x: 35,
    y: 30
}, {
    id: 2,
    r: 5,
    x: 30,
    y: 35
}];


//data generator makes the list above
function newList() {
    //just make a simple array full of the number 1
    var items = new Array(randoNum(1, 10)).fill(1)
    //make the pieces of data. ID is important!
    return items.map(function(val, i) {
      
        var r = randoNum(1, 16)
      
        return {
            id: i,
            r: r,
            x: randoNum(1, 200) + r,
            y: randoNum(1, 100) + r
        }
    });
}

//im just making rando numbers with this.
function randoNum(from, to) {
    return Math.floor(Math.random() * (to - from) + from);
}

function update(data) {
  
    //1. get circles (there are none in the first pass!)
    var circles = svg.selectAll('circle');
    
    //2. bind data
    var databoundCircles = circles.data(data, function(d) {
        return d.id;
    });

    //3. enter
    var enter = databoundCircles.enter()
      .append('circle')
      .attr('r', 0)

    //4. exit
    databoundCircles.exit()
      .transition()
      .attr('r', 0)
      .remove();

    //5. update
    //(everything after transition is tweened)
    databoundCircles
        .attr('fill', function(d, i){
          var h =  parseInt(i.toString(16));   
          return '#' + [h,h,h].join('');
        })

        .transition()
        .duration(1000)
        .attr('r', function(d, i) {
            return d.r * 4
        })
        .attr('cx', function(d, i) {
            return d.x * 2;
        })
        .attr('cy', function(d, i){
            return d.y * 2
        })
 ;
}

//first time I run, I use my example data above
update(data);

//now i update every few seconds
//watch how d3 'keeps track' of each circle
setInterval(function() {
    update(newList());
}, 2000);
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="500" height="300">

</svg>
Run Code Online (Sandbox Code Playgroud)