【译】为SVG路径上的任何东西制作动画

2,117 阅读7分钟

学习链接

tympanus.net/codrops/202…

image.png

学习如何使用SVG路径和getPointAtLength()函数编写创意动画。


SVG是一种非常整洁的格式,可以在网站上显示任何插图、图标或标识。此外,它们还可以在CSS或JavaScript中进行动画处理,使其更具吸引力。但是,SVG也可以用来显示它们的数据,而不显示视觉效果!让我解释一下...

SVG是一种矢量图像格式,这意味着它不是由彩色像素构成的,而是由数学函数构成的,一旦被解释,就可以在屏幕上呈现。由于浏览器必须将文件从函数转换为实际的像素,所以它也让我们可以访问各种各样的方法来操作,或者从数学中获取数据。

在今天的文章中,我们将探讨getPointAtLength()函数,看看我们如何使用SVG路径的数据来实现创造性的使用案例,比如下面的演示。

codepen地址

001.gif

If you are not familiar with SVG, this CSS-Tricks article is a good start! - 这里可以学到SVG的知识!包括SVG的创建、控制SVG、SVG转换为URL

The method getPointAtLength()

如果我们看一下关于该方法的MDN文档页,它说。 SVGGeometryElement.getPointAtLength()方法返回路径上指定距离的点。

该方法将给我们提供一个点的坐标,该点正好在我们作为参数发送的特定距离的路径上。

例如,path.getPointAtLength(10)将返回一个SVGPoint(一个对象)的x和y坐标

image.png

由于我们需要给出我们的点的距离,这意味着我们很可能需要知道我们的路径有多长。幸运的是,SVG API有一个getTotalLength()方法,可用于任何SVGGeometryElement,它可以返回我们元素的总长度

SVGGeometryElement变量指的是所有由几何图形(路径、矩形、圆......)创建的SVG标签,所以这不包括图像、过滤器、剪辑路径......标签。

让我们看看我们如何使用这个函数,用GreenSock库沿给定路径制作一个圆的动画。

要做到这一点,我们需要一个JavaScript对象,它将包含动画值(因为gsap不能直接对数字变量进行动画处理),并设置一个属性距离为0。 然后我们创建一个Tween,将距离值从0更新到我们路径的总长度

最后在每一帧上,我们根据动画的距离值沿路径检索一个点,并更新我们圆的cx和cy属性,使其移动✨

// Create an object that gsap can animate 创建对象,可以由gasp控制
const val = { distance: 0 };

// Create a tween 创建动画
gsap.to(val, {

  // Animate from distance 0 to the total distance 计算总长度
  distance: path.getTotalLength(),
  
  // Function call on each frame of the animation 更新函数
  onUpdate: () => {
  
    // Query a point at the new distance value 该点的距离从0到总长度。
    const point = path.getPointAtLength(val.distance);
    
    // Update the circle coordinates 更新位置!
    circle.setAttribute('cx', point.x);
    circle.setAttribute('cy', point.y);
  }
});

效果如下:

codepen地址

001.gif

如果你想达到的效果只是沿着SVG路径为一个元素做动画,比如上面的演示,你可以看看GreenSock的MotionPathPlugin。它可以让你轻松地从你提供的路径上为任何DOM元素制作动画。(另外它是免费的!) ---- 加了个autorotate属性就可以了👍︎👍︎👍︎

Using the points coordinates for particles

我喜欢粒子,这不是什么突发新闻。这就是为什么,当我学到一种新的技术时,我总是试图用它们来实现一些东西!

让我们来看看,我们可以让更多的圆圈像炸弹引信一样爆炸,而不是一个单一的圆圈沿着路径移动

这个动画的整体逻辑和之前的完全一样,只是在每一帧我们将创建一个新的圆形元素并为其制作动画。正如你所看到的,设置是非常相似的。

const svg = document.querySelector('svg');
const fuse = svg.querySelector('.fuse'); // 爆炸引线

const val = { distance: 0 };

gsap.to(val, {

  distance: fuse.getTotalLength(),
  repeat: -1,
  duration: 5,
  
  onUpdate: () => {
  
    // Query a point at the new distance value
    const point = fuse.getPointAtLength(val.distance); //点沿着 爆炸引线移动
    
    // Create a new particle
    createParticle(point); // 在该点位置上,创建粒子!
  }
});

createParticle函数将在每一帧被调用,以使一个新的粒子弹出并淡出。下面是动画的步骤。

  1. 创建一个新的圆形元素,并将其附加到SVG中
  2. 从我们用getPointAtLength计算的点开始设置坐标
  3. 为每个粒子定义一个随机的半径和颜色
  4. 将该粒子的cx和cy属性做成动画,使其处于一个随机位置
  5. 一旦动画完成,从DOM中删除该粒子
function createParticle (point) {

  // Create a new circle element 创建一个圆形svg 
  // [Document.createElementNS()](https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElementNS)
  const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
  
  // Prepend the element to the SVG 添加进SVG中
  svg.prepend(circle);
  
  // Set the coordinates of that circle 设置位置
  circle.setAttribute('cx', point.x);
  circle.setAttribute('cy', point.y);
  
  // Define a random radius for each circle 设置随机位置
  circle.setAttribute('r', (Math.random() * 2) + 0.2);
  
  // Define a random color 设置随机颜色
  circle.setAttribute('fill', gsap.utils.random(['#ff0000', '#ff5a00', '#ff9a00', '#ffce00', '#ffe808']));
  
  // Animate the circle 给新创建的圆形设置动画
  gsap.to(circle, { 
  
    // Random cx based on its current position 随机位置X
    cx: '+=random(-20,20)',
    
    // Random cy based on its current position 随机位置Y
    cy: '+=random(-20,20)',
    
    // Fade out 淡出
    opacity: 0,
    
    // Random duration for each circle 随机持续时间
    duration: 'random(1, 2)',
    
    // Prevent gsap from rounding the cx & cy values 防止gsap对cx和cy值进行四舍五入,不然就清零了。
    autoRound: false,
    
    // Once the animation is complete 动画结束,移除圆形SVG
    onComplete: () => {
    
      // Remove the SVG element from its parent
      svg.removeChild(circle);
    }
  });
}

codepen地址

001.gif

⚠️ To make the animation prettier I’m also animating the stroke-dashoffset of the fuse to make it more realistic. You can check this article for more details on such animation. ----- 这个是让引线跟着消失的动画内容,文章中指出,stroke-dasharray让你指定线条的渲染部分的长度,然后是间隙的长度。 stroke-dashoffset让你改变破折号的起始位置。效果如下。

How SVG Line Animation Works | CSS-Tricks - CSS-Tricks

001.gif

Using the coordinates in WebGL

到目前为止,我们只对路径旁边的SVG元素进行了动画处理。但有时我们需要的只是路径的原始坐标,而不是路径本身。例如,如果我们想在2D Canvas或WebGL中为粒子制作动画,就像下面这个动画一样。

codepen地址

001.gif

⚠️ 接下来的内容希望你知道如何设置和运行一个three.js场景。

以下是这个动画的关键概念。

  1. 获取路径及其总长度
  2. 沿着路径循环,直到达到其长度
  3. 获取存在于索引距离上的点
  4. 在每次迭代时,在点的坐标处创建一个Vector3
  5. 将矢量推送到一个顶点数组
  6. 从顶点创建一个几何体
  7. 创建一个点网格,并将其添加到你的场景中。

// Get the Path in the DOM 绑定svg的dom!
const path = document.querySelector("path");

// Store the total length of the path 获取长度!
const length = path.getTotalLength();

// Empty array to store all vertices 粒子数组!
const vertices = [];

// Loop along the path
for (let i = 0; i < length; i += 0.2) { 按长度进行遍历

  // Get the coordinates of a point based on the index value 确定位置
  const point = path.getPointAtLength(i);
  
  // Create a new vector at the coordinates 根据二维位置创建粒子
  const vector = new THREE.Vector3(point.x, -point.y, 0);
  
  // Randomize a little bit the point to make the heart fluffier 粒子位置随机化!
  vector.x += (Math.random() - 0.5) * 30;
  vector.y += (Math.random() - 0.5) * 30;
  vector.z += (Math.random() - 0.5) * 70;
  
  // Push the vector into the array 加入粒子数组
  vertices.push(vector);
}
// Create a new geometry from the vertices 粒子的几何体!
const geometry = new THREE.BufferGeometry().setFromPoints(vertices);

// Define a pink material 粒子的材料!
const material = new THREE.PointsMaterial( { color: 0xee5282, blending: 
THREE.AdditiveBlending, size: 3 } );

// Create a Points mesh based on the geometry and material 粒子实体化了!
const particles = new THREE.Points(geometry, material);

// Offset the particles in the scene based on the viewbox values 设置整体位置
particles.position.x -= 600 / 2;
particles.position.y += 552 / 2;

// Add the particles in to the scene 添加进场景就能看到了!!!
scene.add(particles);

codepen地址

001.gif

注:心形是根据svg的路径创建的,在路径上,产生随机的粒子!然后放进scene中!

创建一个心形粒子是很有意思的,但为这些粒子制作动画就更有趣了🥳

首先,我们设置一个全局时间线,这样所有的tweens将被分组,一旦最后一个动画完成,我们就可以重复所有的tweens。

// Create a global gsap timeline that contains all tweens 动画时间线出来了!
const tl = gsap.timeline({
  repeat: -1,
  yoyo: true
});

在创建矢量的时候,我们给它附加一个gsap tween,使它从心形的中心开始动画。我们可以根据SVG的viewBox属性viewBox="0 0 600 552 "来计算起点的x和y坐标。 由于在SVG中y轴是在另一个方向,我们应用一个负值。(在WebGL中y是向上的,而在CSS和SVG中是向下的)。

每个矢量的动画将有一个延迟,**这个延迟是根据它自己沿路径的距离计算出来的,**所以这将创造一个漂亮的粒子流转。


for (let i = 0; i < length; i += 0.1) {
  [...]
  
  // Create a tween for that vector
  tl.from(vector, {
  
      x: 600 / 2, // Center X of the heart
      y: -552 / 2, // Center Y of the heart
      z: 0, // Center of the scene
      ease: "power2.inOut",
      duration: "random(2, 5)" // Random duration
      
    },
    
    i * 0.002 // Delay calculated from the distance along the path 整个i既是时间也是距离!!!
  );
}

最后,我们需要在每一帧更新Points网格的几何体,因为Vector3对象正在被动画化,但几何体并不知道这一点。

function render() {
  requestAnimationFrame(render);
  
  // Update the geometry from the animated vertices 重新设置点!
  geometry.setFromPoints(vertices);
}

然后就可以了 💖

codepen地址

001.gif

What’s next?

你来告诉我!现在你可以沿着SVG路径提取点的坐标,试着在其他地方应用这些数据🚀。 用线条代替粒子做动画怎么样?或者根据你沿着另一条路径挑选的随机点创建一条新的路径

探索这种技术,并在Twitter上与我分享你的成果,我迫不及待地想看看你会想出什么办法💙。

作者其他文章

非常棒!值得学习!

01

tympanus.net/codrops/202…

粒子效果应用于模型表面!

image.png

02

tympanus.net/codrops/201…

image.png

03

tympanus.net/codrops/201…

image.png

总结

对于SVG动画有了一点认识,感谢作者详细写出了思考的每一个细节。SVG结合GASP真的很强大,SVG不仅可以用于二维,也可以用于三维。粒子化效果看起来可真棒!

写一写收获

  • 发现新的学习网站 - CSS-Tricks
  • 动画的产生是因为移动,粒子的产生是因为在该位置上创建的。
  • 作者写的步骤123很容易理解!!
  • 接下来可以收集更多的SVG案例,二维的三维的,多看看总能会的。。
  • 下次参加活动可以试试SVG~

当整个宇宙,它为你而闪烁,请别走,因为是只为你闪烁 - 《整个宇宙将为你闪烁》