d3+Snap原理研讨和破碎动画实例

本文介绍了如何利用d3.js和Snap.svg库在前端制作SVG矢量动画,以破碎效果为例,详细阐述了从创建SVG画布、初始化Snap对象到绘制和动画矩形的步骤,最后实现矩形向四周破碎并消失的动画效果。通过理解这些基本概念和技巧,读者可以进一步探索和创建更复杂的SVG动画。
摘要由CSDN通过智能技术生成

最近很巧的了解了svg矢量动画的制作,并上手试了一下,用d3和Snap结合可以很好地做出一些很不错的简单动画效果

效果图

首先我们先了解一些基本概念

1.为什么不能直接在网页端绘制图案?

大家都知道前端一个很重要的概念叫‘元素’,我们的很多操作本质上就是在操作dom元素,即文档里面的某个html元素,但是html没有单纯的形状元素(比如圆形、三角形等等)但是我们可以通过div去构造,比如我们设置宽高相等就构建了一个正方形,设置样式圆角50%就构建了一个圆形,但这些都是一些旁门左道,我们想要画复杂图案,有两个思路;

(1)栅格图案 :说人话就是一张图片,栅格化图案都是由一个个像素点构成,但是栅格图案因为是像素构成的无法随意改变形状,更不要说动画了

可以把这些小方格看出像素

(2)矢量图案:用点与线直接构成的图案,因为是用代码定义出来的,所以可扩展性和可变性很强,但是复杂度不高

矢量图是由线段和点组成的

如果我们想做前端的动画的话,那肯定是用矢量图案好一点,现在的很多自定义图案的组件库(比如d3,Snap,Canvas等等都是基于矢量图案的)

html也有矢量图的标签<svg>

2.d3是啥

官方文档D3js: Data-Driven Documents

简而言之是一个直接操纵dom文档的js库,但是在本篇中的主角并不是他,我们只是用d3创建一个可控的<svg>元素

3.Snap是啥

官方文档Snap.svg - Home

简而言之是一个svg矢量图的库,我们可以用一种很直观的方式创建一些简单的矢量图案,更重要的是snap自带动画api


废话不多说我们直接实操,这里以破碎动画效果为例,点击鼠标后,在当前位置出现一些很细小的矢量图案,图案向着四周发射,

那么思路如下:

1.用d3创建一个svg元素,当做画布

2.用snap包裹画布

3.用snap在某一个点创建几个随机大小的矩形

4.使这些矩形执行动画,位移到随机坐标(随机坐标在一定范围内,防止破碎太远)

5.使这些矩形执行动画,透明度慢慢变成0(逐渐隐藏)

6.移除这些矩形

有了思路我们好办很多,首先

首先我们在我们的页面创建一个dom元素

这个是一个空的div,不占位置,随便写即可,但是要定义id来方便我们之后找到

之后我们通过d3定义一个svg对象

 var svg = d3.select("#container").append("svg")
        .attr("width", 1500)
        .attr("height", 3000)
        .attr("id", 'svg');

我们选择了刚刚id为container的dom对象,增加了一个svg对象,长1500像素,宽3000像素,id为svg

打开调试工具我们可以看到container这个dom下面新增了一个我们刚刚定义的svg

在这里,d3的使命就结束了,d3已经为我们创建了一个1500*3000的画布

之后我们初始化一个snap对象s

//初始化一个snap对象
s = Snap('#svg');

这一行就是把我们id为svg的svg对象包装成snap对象(原理见下图)

 那么对于我们创建的s,我们可以通过snap命令去创建一些snap矢量图案

我们通过rect命令,在某一点[centerPointX,centerPointY]创建大小随机的7个矩形

  //首先在中心点附近随机生成7个大小各异的矩形
  var rect1 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect2 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect3 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect4 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect5 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect6 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect7 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);

因为snap对象必须是实例,这里用了遍历的写法,也方便大家学习

centerPointX和centerPointY可以你自己定义,只要不超过我们的画布就行,是根据画布像素来的,比如[100,200]

之后对每一个对象填充黑色

rect1.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect2.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect3.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect4.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect5.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect6.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect7.attr({
    fill: "rgba(0,0,0,1)"
  });

 .attr是snap的一个方法,可以为一个snap对象修改属性

这个时候我们的7个黑色矩形只是停留在坐标,我们需要让每个矩形执行一个动画,动画的变化是变化坐标到25像素外,50像素内的某个随机范围

 rect1.animate({ x: Math.random() > 0.5 ? (centerPointX + 25 + Math.round(Math.random() * 24)) : (centerPointX - 25 - Math.round(Math.random() * 24)), y: Math.random() > 0.5 ? (centerPointY + 25 + Math.round(Math.random() * 24)) : (centerPointY - 25 - Math.round(Math.random() * 24)) }, 100, mina.easeinout, function () {})

//剩下6个都和上面一样

这里用到了许多Math.random()方法来生成随机数,主要是为了使矩形的新xy坐标在50*50和25*25的回字形范围内生成,产生一种‘破碎的’的效果

 动画效果还不错,但是感觉有点生硬了,我们在回调里面再加一个动画,动画的变化是把不透明度慢慢变成0

  rect1.animate({ x: Math.random() > 0.5 ? (centerPointX + 25 + Math.round(Math.random() * 24)) : (centerPointX - 25 - Math.round(Math.random() * 24)), y: Math.random() > 0.5 ? (centerPointY + 25 + Math.round(Math.random() * 24)) : (centerPointY - 25 - Math.round(Math.random() * 24)) }, 100, mina.easeinout, function () {
    //逐渐消失
    rect1.animate({ "opacity": 0 }, 800 + Math.round(Math.random() * 400), mina.easeinout, function () {})
  })

//剩下6个都和上面一样

 最后,我们再在不透明度变成0的动画回调里面,在300毫秒后把我们的矩形给删除

  rect1.animate({ x: Math.random() > 0.5 ? (centerPointX + 25 + Math.round(Math.random() * 24)) : (centerPointX - 25 - Math.round(Math.random() * 24)), y: Math.random() > 0.5 ? (centerPointY + 25 + Math.round(Math.random() * 24)) : (centerPointY - 25 - Math.round(Math.random() * 24)) }, 100, mina.easeinout, function () {
    //逐渐消失
    rect1.animate({ "opacity": 0 }, 800 + Math.round(Math.random() * 400), mina.easeinout, function () {
      //销毁元素
      setTimeout(() => {
        rect1.remove()
      }, 200 + Math.round(Math.random() * 200))
    })
  })

//剩下6个都和上面一样

 现在破碎效果就做好了,我们甚至可以可以通过指定坐标的加减,来使得我们的破碎效果是指定某个方向

 这就是通过d3+Snap制作简单前端动画的步骤,了解原理后,我们可以制作一些更复杂的动画

下面附上源码

//破碎动画
export const render = () => {
  //定义动画的坐标
  let centerPointX = 100
  let centerPointY = 200
  //首先在中心点附近随机生成7个大小各异的矩形
  var rect1 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect2 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect3 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect4 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect5 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect6 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  var rect7 = s.rect(centerPointX, centerPointY, 1 + Math.round(Math.random() * 4), 1 + Math.round(Math.random() * 4), 0, 0);
  //对于每个都填充黑色
  rect1.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect2.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect3.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect4.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect5.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect6.attr({
    fill: "rgba(0,0,0,1)"
  });
  rect7.attr({
    fill: "rgba(0,0,0,1)"
  });
  //之后向四周发散,最后淡化消失
  //xSpray和ySpray使所有矩形的喷射方向趋于一致
  let xSpray = Math.random()
  let ySpray = Math.random()
  //每一个矩形都执行动画
  rect1.animate({ x: xSpray > 0.5 ? (centerPointX + 25 + Math.round(Math.random() * 24)) : (centerPointX - 25 - Math.round(Math.random() * 24)), y: ySpray > 0.5 ? (centerPointY + 25 + Math.round(Math.random() * 24)) : (centerPointY - 25 - Math.round(Math.random() * 24)) }, 100, mina.easeinout, function () {
    //逐渐消失
    rect1.animate({ "opacity": 0 }, 800 + Math.round(Math.random() * 400), mina.easeinout, function () {
      //销毁元素
      setTimeout(() => {
        rect1.remove()
      }, 200 + Math.round(Math.random() * 200))
    })
  })
  rect2.animate({ x: xSpray > 0.5 ? (centerPointX + 25 + Math.round(Math.random() * 24)) : (centerPointX - 25 - Math.round(Math.random() * 24)), y: ySpray > 0.5 ? (centerPointY + 25 + Math.round(Math.random() * 24)) : (centerPointY - 25 - Math.round(Math.random() * 24)) }, 100, mina.easeinout, function () {
    //逐渐消失
    rect2.animate({ "opacity": 0 }, 800 + Math.round(Math.random() * 400), mina.easeinout, function () {
      //销毁元素
      setTimeout(() => {
        rect2.remove()
      }, 200 + Math.round(Math.random() * 200))
    })
  })
  rect3.animate({ x: xSpray > 0.5 ? (centerPointX + 25 + Math.round(Math.random() * 24)) : (centerPointX - 25 - Math.round(Math.random() * 24)), y: ySpray > 0.5 ? (centerPointY + 25 + Math.round(Math.random() * 24)) : (centerPointY - 25 - Math.round(Math.random() * 24)) }, 100, mina.easeinout, function () {
    //逐渐消失
    rect3.animate({ "opacity": 0 }, 800 + Math.round(Math.random() * 400), mina.easeinout, function () {
      //销毁元素
      setTimeout(() => {
        rect3.remove()
      }, 200 + Math.round(Math.random() * 200))
    })
  })
  rect4.animate({ x: xSpray > 0.5 ? (centerPointX + 25 + Math.round(Math.random() * 24)) : (centerPointX - 25 - Math.round(Math.random() * 24)), y: ySpray > 0.5 ? (centerPointY + 25 + Math.round(Math.random() * 24)) : (centerPointY - 25 - Math.round(Math.random() * 24)) }, 100, mina.easeinout, function () {
    //逐渐消失
    rect4.animate({ "opacity": 0 }, 800 + Math.round(Math.random() * 400), mina.easeinout, function () {
      //销毁元素
      setTimeout(() => {
        rect4.remove()
      }, 200 + Math.round(Math.random() * 200))
    })
  })
  rect5.animate({ x: xSpray > 0.5 ? (centerPointX + 25 + Math.round(Math.random() * 24)) : (centerPointX - 25 - Math.round(Math.random() * 24)), y: ySpray > 0.5 ? (centerPointY + 25 + Math.round(Math.random() * 24)) : (centerPointY - 25 - Math.round(Math.random() * 24)) }, 100, mina.easeinout, function () {
    //逐渐消失
    rect5.animate({ "opacity": 0 }, 800 + Math.round(Math.random() * 400), mina.easeinout, function () {
      //销毁元素
      setTimeout(() => {
        rect5.remove()
      }, 200 + Math.round(Math.random() * 200))
    })
  })
  rect6.animate({ x: xSpray > 0.5 ? (centerPointX + 25 + Math.round(Math.random() * 24)) : (centerPointX - 25 - Math.round(Math.random() * 24)), y: ySpray > 0.5 ? (centerPointY + 25 + Math.round(Math.random() * 24)) : (centerPointY - 25 - Math.round(Math.random() * 24)) }, 100, mina.easeinout, function () {
    //逐渐消失
    rect6.animate({ "opacity": 0 }, 800 + Math.round(Math.random() * 400), mina.easeinout, function () {
      //销毁元素
      setTimeout(() => {
        rect6.remove()
      }, 200 + Math.round(Math.random() * 200))
    })
  })
  rect7.animate({ x: xSpray > 0.5 ? (centerPointX + 25 + Math.round(Math.random() * 24)) : (centerPointX - 25 - Math.round(Math.random() * 24)), y: ySpray > 0.5 ? (centerPointY + 25 + Math.round(Math.random() * 24)) : (centerPointY - 25 - Math.round(Math.random() * 24)) }, 100, mina.easeinout, function () {
    //逐渐消失
    rect7.animate({ "opacity": 0 }, 800 + Math.round(Math.random() * 400), mina.easeinout, function () {
      //销毁元素
      setTimeout(() => {
        rect7.remove()
      }, 200 + Math.round(Math.random() * 200))
    })
  })
}

附录 SVG Attribute reference - SVG: Scalable Vector Graphics | MDN  这个网站可以查询svg的属性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值