用D3.js 十分鐘實現字符跳動效果

本文基於 D3.js 做者 Mike Bostock例子javascript

原文分爲三部分, 在這裏筆者將其整合爲了一篇方便閱讀.html

該效果基於 D3.js, 主要使用到了 d3-selection. 若是對d3-selection的基本使用邏輯不太清楚能夠參見 這篇文章.java

效果圖

  • Step1 首先代碼會隨機生成一個字符串, 該字符以綠色進入畫面.git

  • Step2 接下來, 代碼隨機生成一個新字符串, 新生成的字符串會和原始字符串進行對比:github

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

    2.2 原始字符串中有, 而新字符串中沒有的字母, 會變成紅色,被移除屏幕dom

    2.3 新字符串中有, 而原始字符串中沒有的字母, 會變成綠色,被添加到屏幕svg

final demo

代碼實現

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>
複製代碼

代碼不長, 接下來一步步分析代碼邏輯:post

首先, 獲取svg的寬高信息. 並建立一個 元素用來承接接下來要建立的字符( 元素) 字體

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()來實現字符刷新. 如今咱們就獲得了下面的效果:

first step

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>
複製代碼

如今咱們獲得的效果:

second step

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>
複製代碼

最終效果:

final demo

想繼續瞭解 D3.js ?

這裏是個人 D3.js數據可視化 的github 地址, 歡迎 start & fork :tada:

D3-blog

若是以爲不錯的話, 不妨點擊下面的連接關注一下 : )

github主頁

知乎專欄

掘金

相關文章
相關標籤/搜索