用D3制作一个旋转的大风车SVG庆祝国际儿童节

简介: 写于2023年的六一国际儿童节,用D3做一个大风车庆祝一下这个快乐的节日,借此机会介绍一下SVG滤镜的使用,代码也是非常简短的

国际儿童节,尽管称之为国际,但实际上这个节日并非联合国定的,联合国定的儿童节叫“世界儿童节”,在11月20日庆祝。那六一是怎么回事呢?追溯到1925年,第一届世界儿童福利大会在日内瓦召开,当时就宣布了6月1日为国际儿童节,但是在当时,这个节日几乎没有国家过。一直到1949年国际民主妇女联合会代表大会召开,为纪念第二次世界大战特别是利迪策大屠杀中遇难的儿童,再一次将6月1日定为国际儿童节,许多社会主义国家响应,我们也是其中一员,所以今天我们过这样一个国际儿童节的节日,而并非欧美流行的世界儿童节

说起儿童的象征,莫过于小时候看的大风车了,借此佳节用D3制作一个这样的动画聊以自娱。

因为代码简短,可以直接都写在一个html中:

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>大风车</title>
    </head>
    <body style="text-align: center"></body>
    <script type="module">
        import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
        const _svg = d3
            .select("body")
            .append("svg")
            .attr("width", "500")
            .attr("height", "500");
        ...
    </script>
</html>

起手先做一个500x500的正方形svg,然后画出一个叶片(这一步需要耐心调出来):

_svg.append("path")
    .attr("stroke", "black")
    .attr("fill", "transparent")
    .attr("d", "M10 100 L10 50 L50 50 C100 50, 180 100, 180 100 L10 100 C20 100, 20 50, 55 50");

image.png

然后将定位到中心,因为后续还有3个叶片,所以把它包含在一个g组中,再将这个g进行定位:

const g = _svg
        .append("g")
        .style("transform", "translate(250px, 150px)");

g.append("path")
        .attr("stroke", "black")
        .attr("fill", "transparent")
        .attr("d", "M10 100 L10 50 L50 50 C100 50, 180 100, 180 100 L10 100 C20 100, 20 50, 55 50");

image.png

可以看到已经定位在中间了,然后更改一下path的制作,改成循环四份这样的叶片并且将其分别设置0, 90, 180, 260四个角度旋转:

[0, 90, 180, 270].map(e => {
   
    g.append("path")
        .attr("stroke", "black")
        .attr("fill", "transparent")
        .attr("transform", "rotate(" + e + " 10 100)")
        .attr("d", "M10 100 L10 50 L50 50 C100 50, 180 100, 180 100 L10 100 C20 100, 20 50, 55 50");
});

image.png

是理想中的效果,为什么rotate()的后面两个参数是10 100呢?这个其实就是叶片的左下角,也就是path画线的起始位置M10 100。后面就是再往上加颜色,滤镜之类的东西了,现在再看svg元素里面4个path就有些重复了,所以不妨改成defs+use xlink:href引用的方式,也是一样的效果,页面元素还会少很多,将上面的代码改成这样,效果是一样的:

const defs = _svg.append("defs");
  defs.append("path")
    .attr("id", "block")
    .attr("stroke", "black")
    .attr("fill", "transparent")
    .attr("d", "M10 100 L10 50 L50 50 C100 50, 180 100, 180 100 L10 100 C20 100, 20 50, 55 50");

[0, 90, 180, 270].map(e => {
   
    g.append("use")
    .attr("xlink:href", "#block")
            .attr("transform", "rotate(" + e + " 10 100)")
});

接下来就可以加滤镜,上色看一看了,滤镜是加到defs里,我决定加个模糊滤镜,毕竟风车转起来看上去有些模糊嘛。颜色则是把数字数组改成对象数组,然后放到数组里面,就定位红黄蓝绿几个基本色:

const defs = _svg.append("defs");
defs.append("filter")
    .attr("id", "f1")
    .append("feGaussianBlur")
    .attr("in", "SourceGraphic")
    .attr("stdDeviation", "1");
defs.append("path")
    .attr("id", "block")
    .attr("stroke", "black")
    .attr("filter", "url(#f1)")
    .attr("d", "M10 100 L10 50 L50 50 C100 50, 180 100, 180 100 L10 100 C20 100, 20 50, 55 50");
const _data = [
  {
    color: "red", deg: 0 },
  {
    color: "yellow", deg: 90 },
  {
    color: "blue", deg: 180 },
  {
    color: "green", deg: 270 },
];
g.selectAll("use")
  .data(_data)
  .join("use")
  .attr("fill", e => e.color)
  .attr("transform", e => "rotate(" + e.deg + " 10 100)");

SVG滤镜就是这样用的,可以一起放到defs里面,定好id,然后下面的pathfilter属性指向这个id,效果如下:

image.png

效果很完美,下一步就是做动画,转起来:

function comic() {
   
  g.selectAll("use")
    .data(_data)
    .transition()
    .duration(70)
    .attr("transform", e => {
   
      if (e.deg == 0) {
   
        e.deg = 360;
      }
      e.deg -= 5;
      return "rotate(" + e.deg + " 10 100)";
    })
    .on("end", comic);
}
comic();

这段代码很简单,每隔70毫秒逆时针转5度,不停的转,效果如下:

({X)UZJKVO8S41UH2$NHQ%J.gif

最后想起来应该加个棍,这个部分应该放在写入use标签之前,也就是放在叶片的底层才行:

const line = g.append("line")
  .attr("stroke", "darkgoldenrod")
  .attr("stroke-width", 2)
  .attr("x1", 10)
  .attr("y1", 100)
  .attr("x2", 0)
  .attr("y2", 400);

尺寸超出画布了,要调一下gtransformtranslate(250px, 100px),往上靠,然后棍子稍微倾斜一点好看一点:

image.png

下面再加一段动画,让这个风车整体在旋转的同时摇晃起来,毕竟风车怎么能不晃动嘛:

const ran = d3.randomInt(20);
function shake() {
   
  g.transition()
    .duration(300)
    .style("transform", `translate(${250 + ran() * 2}px, ${100 + ran()}px)`);
  line
    .transition()
    .duration(300)
    .attr("x2", ran() * 3)
    .attr("y2", 400 + ran())
    .on("end", shake);
}
shake();

顺便再给刚才的第一段动画调快到50,显得转的很快才抖动起来

WR(QL~1F`33YM)}6RJRWG(A.gif

完整源代码

import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
const _svg = d3
  .select("body")
  .append("svg")
  .attr("width", "500")
  .attr("height", "500");

const g = _svg.append("g").style("transform", "translate(250px, 100px)");
const defs = _svg.append("defs");
defs
  .append("filter")
  .attr("id", "f1")
  .append("feGaussianBlur")
  .attr("in", "SourceGraphic")
  .attr("stdDeviation", "1");
defs
  .append("path")
  .attr("id", "block")
  .attr("stroke", "black")
  .attr("filter", "url(#f1)")
  .attr(
    "d",
    "M10 100 L10 50 L50 50 C100 50, 180 100, 180 100 L10 100 C20 100, 20 50, 55 50"
  );

const line = g
  .append("line")
  .attr("stroke", "darkgoldenrod")
  .attr("stroke-width", 2)
  .attr("x1", 10)
  .attr("y1", 100)
  .attr("x2", 0)
  .attr("y2", 400);

const _data = [
  {
    color: "red", deg: 0 },
  {
    color: "yellow", deg: 90 },
  {
    color: "blue", deg: 180 },
  {
    color: "green", deg: 270 },
];

g.selectAll("use")
  .data(_data)
  .join("use")
  .attr("xlink:href", "#block")
  .attr("fill", (e) => e.color)
  .attr("transform", (e) => "rotate(" + e.deg + " 10 100)");

function comic() {
   
  g.selectAll("use")
    .data(_data)
    .transition()
    .duration(50)
    .attr("transform", (e) => {
   
      if (e.deg == 0) {
   
        e.deg = 360;
      }
      e.deg -= 5;
      return "rotate(" + e.deg + " 10 100)";
    })
    .on("end", comic);
}
comic();
const ran = d3.randomInt(20);
function shake() {
   
  g.transition()
    .duration(300)
    .style("transform", `translate(${250 + ran() * 2}px, ${100 + ran()}px)`);
  line
    .transition()
    .duration(300)
    .attr("x2", ran() * 3)
    .attr("y2", 400 + ran())
    .on("end", shake);
}
shake();

很可爱的小风车,以此祝大家六一国际儿童节快乐~

本文写作于2023年6月1日并发布于lyrieek的掘金,于2023年7月18日进行修订发布于lyrieek的阿里云开发者社区。

目录
相关文章
|
6月前
|
前端开发
例举一些常见的中国古典色,总有一款靓到你!
例举一些常见的中国古典色,总有一款靓到你!
158 1
例举一些常见的中国古典色,总有一款靓到你!
|
1月前
Threejs制作大海效果
这篇文章详细介绍了使用Three.js制作大海效果的技术细节,包括创建水面模型、应用波纹纹理以及实现动态波浪效果的方法。
27 0
|
5月前
|
定位技术 图形学
【用unity实现100个游戏之1】制作类元气骑士、挺进地牢——俯视角射击游戏多种射击效果(一)(附源码)
【用unity实现100个游戏之1】制作类元气骑士、挺进地牢——俯视角射击游戏多种射击效果(一)(附源码)
136 0
|
6月前
超简单的html+css魔幻霓虹灯文字特效
超简单的html+css魔幻霓虹灯文字特效
51 3
超简单的html+css魔幻霓虹灯文字特效
|
6月前
网页星光闪耀背景动画特效
网页星光闪耀背景动画特效
38 0
网页星光闪耀背景动画特效
Photoshop使用路径描边制作梦幻潮流光丝字
Photoshop使用路径描边制作梦幻潮流光丝字
64 0
|
前端开发 程序员 容器
颜值即正义,献礼就业季,打造多颜色多字体双飞翼布局技术简历模板(Resume)
一年好景君须记,最是橙黄橘绿时。金三银四,秣马厉兵,没有一个好看的简历模板怎么行?无论是网上随便下载还是花钱买,都是一律千篇的老式模版,平平无奇,味同嚼蜡,没错,蜡都要沿着嘴角流下来了。本次我们基于Html和Css3打造一款独立实现的高颜值简历模板,就像看岛国的爱情片儿一样,也许你会找自己喜欢的主题和类型,但最终,还是要看脸。
颜值即正义,献礼就业季,打造多颜色多字体双飞翼布局技术简历模板(Resume)
|
前端开发 容器
「CSS畅想」七夕寄情,我绘制了一副双色莲花图
用技术实现梦想,用梦想打开创意之门。七夕寄情,我用CSS绘制了一副双色莲花图。
197 1
「CSS畅想」七夕寄情,我绘制了一副双色莲花图
|
JavaScript 前端开发 程序员
【中秋征文】手把手教你海面月亮升起中秋节特效制作
【中秋征文】手把手教你海面月亮升起中秋节特效制作
232 0
【中秋征文】手把手教你海面月亮升起中秋节特效制作
|
计算机视觉 索引
七夕礼物:火柴人特效制作
七夕礼物:火柴人特效制作
323 1
七夕礼物:火柴人特效制作