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

发布于 2024-10-10 12:28:55 字数 8899 浏览 16 评论 0

该效果基于 D3.js,主要使用到了 d3-selection 。

效果图

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

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

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

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

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

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>

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

首先,获取 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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

以歌曲疗慰

暂无简介

0 文章
0 评论
22 人气
更多

推荐作者

忆伤

文章 0 评论 0

眼泪也成诗

文章 0 评论 0

zangqw

文章 0 评论 0

旧伤慢歌

文章 0 评论 0

qq_GlP2oV

文章 0 评论 0

旧时模样

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文