用D3实现鼠标滑动时的圆点随机跳动效果

本教程采用D3.js实现在鼠标滚轮滑动时,屏幕上出现的彩色圆点随机跳动效果。这个项目模仿了Shirley Wu为 The Pudding 制作的一个数据可视化项目的部分效果。

点击此处,在新的页面中查看。

准备工作

准备好第四版的D3和HTML文档。今天的项目里,我们绘图的show()函数被放置在draw_svg1.js文件中。准备就绪之后,就可以开始编写我们的Javascript代码了。

编写draw_svg1.js

draw_svg1.js中的全部代码,都包含在一个叫show()的函数之中。这个项目的实现,主要依靠D3的force布局来实现。首先,参考Mike Bostock 的这篇文章,创建一个用来为各个节点 (node) 独立进行力仿真的isolate()函数,稍后将用它来包裹D3的force.initialize()函数:

function isolate(force, filter) {
  var initialize = force.initialize;
  force.initialize = function() {
    initialize.call(force, nodes.filter(filter));
  };
  return force;
}

下一步,配置一些要用到的常量,包括色阶、窗口尺寸和圆点数量等等:

var n = 600;
var colorScale = d3.scaleSequential(d3.interpolateRainbow).domain([0, 1]);
var w = window.innerWidth || 580;
var heightWin = window.innerHeight;

var margin = { top: 20, bottom: 20, right: 0, left: 0 },
  width = w - margin.left - margin.right,
  height = 6000 - margin.top - margin.bottom;

nodes变量来存储我们随机生成的圆点。这里用到了map()函数,是函数式编程 (functional programming) 中经常用到的方式。如果需要详细了解,网络上有很多函数式编程的教程和例子。

var nodes = d3.range(n).map(function(i) {
  return {
    index: i,
    identifier: i.toString(),
    x: Math.random() * w,
    y: Math.random() * heightWin
  };
});

svg元素中添加g元素,准备绘制圆点的空间。

var canvas1 = d3
  .select('#canvas1')
  .attr('width', width + margin.left + margin.right)
  .attr('height', height + margin.top + margin.bottom)
  .append('g')
  .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

创建一个用于力仿真的对象simulationalphaDecay(0)确保了圆点能始终保持跳动效果,而不会在等待一段事件后便失去跳动效果。force('collision', d3.forceCollide(10).strength(1))让圆点之间产生碰撞的效果,不会重叠在一起。

var simulation = d3
  .forceSimulation(nodes)
  .velocityDecay(0.75)
  .alphaDecay(0)
  .force('collision', d3.forceCollide(10).strength(1));

forEach()函数遍历nodes,往svg画布上添加颜色随机,位置随机的圆点。

nodes.forEach(function(node) {
    canvas1
      .append('circle')
      .data([node])
      .attr('cx', function(d) {
        return d.x;
      })
      .attr('cy', function(d) {
        return d.y;
      })
      .attr('r', 3.5 + Math.random() * 2)
      .attr('fill', colorScale(Math.random()));
  });

力仿真过程中,每次节点位置的刷新,都会触发tick事件。将tick事件与ticked()回调函数绑定,然后编写该回调函数,以刷新圆点的位置。

simulation.on('tick', ticked);

function ticked() {
  canvas1
    .selectAll('circle')
    .attr('cx', function(d) {
      return d.x;
    })
    .attr('cy', function(d) {
      return d.y;
    });
}

将鼠标滚轮滑动的事件与scrollFunc()函数绑定。scrollFunc()函数中,用教程最开头部分的isolate函数,将各个节点的forceXforceY力度进行独立的设置。x坐标只在水平方向上做一定范围的随机摆动,y坐标则在窗口可见范围上下完全随机变化,以便产生上下跳动的效果。用simulation.restart()重启力仿真。代码的最后,主动调用一次scrollFunc()函数,使得页面在加载、刷新时也会有一个初始的跳动效果。

window.addEventListener('scroll', scrollFunc);

function scrollFunc() {
  var scrollY = window.scrollY || 0;
  nodes.forEach(function(node) {
    simulation.force(
      'y' + node.identifier,
      isolate(d3.forceY(Math.random() * (heightWin + 1200) - 600 + scrollY).strength(0.4), function(d) {
        return d.identifier == node.identifier;
      })
    );
    simulation.force(
      'x' + node.identifier,
      isolate(d3.forceX(((Math.random() - 0.5) * width) / 3 + node.x).strength(0.1), function(d) {
        return d.identifier == node.identifier;
      })
    );
  });
  simulation.restart();
}

scrollFunc();

大功告成,制作完毕!在HTML文档中添加一些解释文字和CSS效果即可。

本示例的完整代码,请在这里查看。

Written on October 7, 2018