用D3.js 十分钟实现字符跳动效果

打个小广告:
如果你想获取更多前端干货、鹅厂工程师的前端面试指南,
欢迎关注我的个人微信公众号:
前端夜谈

用D3.js 十分钟实现字符跳动效果

本文基于 D3.js 作者 Mike Bostock例子

原文分为三部分, 在这里笔者将其整合为了一篇方便阅读.

该效果基于 D3.js, 主要使用到了 d3-selection. 如果对d3-selection的基本使用逻辑不太清楚可以参见 这篇文章.

效果图

  • Step1 首先代码会随机生成一个字符串, 该字符以绿色进入画面.

  • Step2 接下来, 代码随机生成一个新字符串, 新生成的字符串会和原始字符串进行对比:

    2.1 新字符串和原始字符串中相同的字母,会变成黑色保留在屏幕上

    2.2 原始字符串中有, 而新字符串中没有的字母, 会变成红色,被移除屏幕

    2.3 新字符串中有, 而原始字符串中没有的字母, 会变成绿色,被添加到屏幕

[图片上传失败...(image-823e8-1529128183234)]

代码实现

1. 字符切换

第一步要完成的效果是:

  • 完成基本字符切换
  • 进入时为绿色, 不变时为黑色
  • 被移除的字符直接被从界面中移除

先上代码, 点我运行

<script>
var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    g = svg.append("g").attr("transform", "translate(32," + (height / 2) + ")");

function update(data) {

  // DATA JOIN
  // Join new data with old elements, if any.
  var text = g.selectAll("text")
    .data(data);

  // UPDATE
  // Update old elements as needed.
  text.attr("class", "update");

  // ENTER
  // Create new elements as needed.
  //
  // ENTER + UPDATE
  // After merging the entered elements with the update selection,
  // apply operations to both.
  text.enter().append("text")
      .attr("class", "enter")
      .attr("x", function(d, i) { return i * 32; })
      .attr("dy", ".35em")
    .merge(text)
      .text(function(d) { return d; });

  // EXIT
  // Remove old elements as needed.
  text.exit().remove();
}

// The initial display.
update(alphabet);

// Grab a random sample of letters from the alphabet, in alphabetical order.
d3.interval(function() {
  update(d3.shuffle(alphabet)
      .slice(0, Math.floor(Math.random() * 26))
      .sort());
}, 1500);

</script>

代码不长, 接下来一步步分析代码逻辑:

首先, 获取svg的宽高信息. 并创建一个 <g> 元素用来承接接下来要创建的字符(<text> 元素)

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    g = svg.append("g").attr("transform", "translate(32," + (height / 2) + ")");

update() 方法中, 我们传进来一个字符串 data ** (data由26个字母中随机选出一些组成). 首先我们选中所有的 text 元素并将其和data** 进行绑定:

var text = g.selectAll("text")
    .data(data);

然后我们处理enter集合 (也就是新加入的字符), 为每一个新字符添加一个 text 元素, 并为其添加class属性, 使用index为其计算出横纵坐标:

text.enter().append("text")                            // 添加svg text元素
      .attr("class", "enter")                          // 添加class属性 (绿色)
      .attr("x", function(d, i) { return i * 32; })    // 按每个字符32像素, 为其计算x坐标
      .attr("dy", ".35em")                             // 设置y轴偏移量

接下来同时处理 enterupdate 集合, 将字符数据填入 text 元素中:

  .merge(text)
      .text(function(d) { return d; });

最后处理 exit集合, 我们暂时直接将其从屏幕中移除:

text.exit().remove();

我们用 d3.interval(callback, timeInterval) 定时调用update()来实现字符刷新. 现在我们就得到了下面的效果:

[图片上传失败...(image-cf7168-1529128183235)]

2. 为字符设定key值

如果你观察仔细的话, 你可能已经发现第一步实现的效果中: 新加的字符总是出现在最后. 这显然不是我们想要的效果, 新加的字符出现的位置应该是随机的. 那么出现现在这个效果的原因是什么呢?

答案在于我们在绑定字符数据时: data(data) 并没有指定data的key值, 那么d3会默认使用index作为key, 这样就是为什么新增加的字符总是出现在最后面.

所以我们为字符加入key值的accessor:

  var text = g.selectAll("text")
    .data(data, function(d) { return d; });

现在 key 值绑定好后, 已经存在的字符在update时text已经不会再改变, 但是坐标需要重新计算, 所以我们做以下改动:

text.enter().append("text")
      .attr("class", "enter")
      .attr("dy", ".35em")
      .text(function(d) { return d; })                 // 将text赋值移动到 enter中
    .merge(text)
      .attr("x", function(d, i) { return i * 32; });   // 将坐标计算移动到 merge后 (enter & update)

现在我们的代码长这样, 点我在线运行:

<script>

var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    g = svg.append("g").attr("transform", "translate(32," + (height / 2) + ")");

function update(data) {

  // DATA JOIN
  // Join new data with old elements, if any.
  var text = g.selectAll("text")
    .data(data, function(d) { return d; });

  // UPDATE
  // Update old elements as needed.
  text.attr("class", "update");

  // ENTER
  // Create new elements as needed.
  //
  // ENTER + UPDATE
  // After merging the entered elements with the update selection,
  // apply operations to both.
  text.enter().append("text")
      .attr("class", "enter")
      .attr("dy", ".35em")
      .text(function(d) { return d; })
    .merge(text)
      .attr("x", function(d, i) { return i * 32; });

  // EXIT
  // Remove old elements as needed.
  text.exit().remove();
}

// The initial display.
update(alphabet);

// Grab a random sample of letters from the alphabet, in alphabetical order.
d3.interval(function() {
  update(d3.shuffle(alphabet)
      .slice(0, Math.floor(Math.random() * 26))
      .sort());
}, 1500);

</script>

现在我们得到的效果:

[图片上传失败...(image-dec9ce-1529128183235)]

3.添加动画

动画能让我们更好的观察元素的变化过程和状态, 给不同状态的元素赋予不同的动画可以更直观的展示我们的数据.

现在我们给字符的变化增加动画效果, 并把字符移除时的颜色变化补上.

首先我们定义一个 transition变量, 并设置其动画间隔为 750

var t = d3.transition()
      .duration(750);

在D3中使用动画非常简单, 在动画前指定元素的一些属性, 调用动画, 再指定动画后的一些属性. D3会自动根据插值器生成动画.

下面的代码对于离开界面的字符(exit selection)进行了处理:

text.exit()
      .attr("class", "exit")          // 动画前, 设置class属性, 字体变红
    .transition(t)                    // 设置动画
      .attr("y", 60)                  // 设置y坐标, 使元素向下离开界面 (y: 0 => 60)
      .style("fill-opacity", 1e-6)    // 设置透明度, 使元素渐变消失 (opacity: 1 => 0)
      .remove();                      // 最后将其移出界面

同样的, 我们对 enterupdate selection进行处理:

// UPDATE old elements present in new data.
  text.attr("class", "update")
      .attr("y", 0)
      .style("fill-opacity", 1)
    .transition(t)
      .attr("x", function(d, i) { return i * 32; });

  // ENTER new elements present in new data.
  text.enter().append("text")
      .attr("class", "enter")
      .attr("dy", ".35em")
      .attr("y", -60)
      .attr("x", function(d, i) { return i * 32; })
      .style("fill-opacity", 1e-6)
      .text(function(d) { return d; })
    .transition(t)
      .attr("y", 0)
      .style("fill-opacity", 1);

最终我们的代码长这样, 点我运行

<script>

var alphabet = "abcdefghijklmnopqrstuvwxyz".split("");

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    g = svg.append("g").attr("transform", "translate(32," + (height / 2) + ")");

function update(data) {
  var t = d3.transition()
      .duration(750);

  // JOIN new data with old elements.
  var text = g.selectAll("text")
    .data(data, function(d) { return d; });

  // EXIT old elements not present in new data.
  text.exit()
      .attr("class", "exit")
    .transition(t)
      .attr("y", 60)
      .style("fill-opacity", 1e-6)
      .remove();

  // UPDATE old elements present in new data.
  text.attr("class", "update")
      .attr("y", 0)
      .style("fill-opacity", 1)
    .transition(t)
      .attr("x", function(d, i) { return i * 32; });

  // ENTER new elements present in new data.
  text.enter().append("text")
      .attr("class", "enter")
      .attr("dy", ".35em")
      .attr("y", -60)
      .attr("x", function(d, i) { return i * 32; })
      .style("fill-opacity", 1e-6)
      .text(function(d) { return d; })
    .transition(t)
      .attr("y", 0)
      .style("fill-opacity", 1);
}

// The initial display.
update(alphabet);

// Grab a random sample of letters from the alphabet, in alphabetical order.
d3.interval(function() {
  update(d3.shuffle(alphabet)
      .slice(0, Math.floor(Math.random() * 26))
      .sort());
}, 1500);

</script>

最终效果:

[图片上传失败...(image-c0ea54-1529128183235)]

想继续了解 D3.js ?

这里是我的 D3.js数据可视化 的github 地址, 欢迎 start & fork :tada:

D3-blog

如果觉得不错的话, 不妨点击下面的链接关注一下 : )

github主页

知乎专栏

掘金

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容