前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >那个前端写的页面好酷——大量的粒子(元素)的动效实现

那个前端写的页面好酷——大量的粒子(元素)的动效实现

作者头像
lhyt
发布2019-12-01 13:38:25
2.1K0
发布2019-12-01 13:38:25
举报
文章被收录于专栏:lhyt前端之路lhyt前端之路

如何构建粒子世界

基于THREE.CSS3dObject

CSS3dObject这个对象,可以让我们像操作threejs对象那样来操作div,使用threejs丰富的api来实现css+div的3d效果。实际上最终效果就是把threejs的参数转化为css的matrix。我们看一段简单的代码,这是创建一个div元素,然后使用three的api控制它的位置:

代码语言:javascript
复制
      const element = document.createElement("div");
      element.className = "element";
      element.style.width = "4px";
      element.style.height = "4px";
      element.style.borderRadius = "50%";
      const object = new THREE.CSS3DObject(element);

      // 使用threejs的api
      object.position.x = x;
      object.position.y = y;
      object.position.z = z || 0;
复制代码

当然,这些代码还不足以渲染出来,因为three还是得使用three那套流程:场景、相机、渲染器,将物体加入场景,渲染器render。这样子,才能让3d世界展示眼前

代码语言:javascript
复制
// 场景
const scene = new THREE.Scene();
// 相机
const camera = new THREE.PerspectiveCamera(
  40,
  window.innerWidth / window.innerHeight,
  1,
  10000
);
camera.position.z = 2000;
// 渲染器
const renderer = new THREE.CSS3DRenderer();
// 把对象加入场景
scene.add(object);
// 渲染
renderer.render(scene, camera);
document.body.appendChild(renderer.domElement);
复制代码

结果是一个个div:

最终效果

适用场景:量级为几十,炫酷的、具有交互的页面元素。

基于THREE的粒子系统

使用THREE.Points粒子系统实现

代码语言:javascript
复制
// 球坐标下的标准球方程
// 这里是球坐标和直角坐标的转换
// size相当于球坐标的r
const getSphere = (i, count, size) => {
  const phi = Math.acos(-1 + (2 * i) / count);
  const theta = Math.sqrt(count * Math.PI) * phi;
  return {
    x: size * Math.cos(theta) * Math.sin(phi),
    y: size * Math.sin(theta) * Math.sin(phi),
    z: -size * Math.cos(phi)
  };
};

const color = new THREE.Color();
// 1000 个点
for (let i = 0; i < 1000; i++) {
  // 获取点的坐标
  const { x, y, z } = getSphere(i, 1000, 500);
  positions.push(x, y, z);
  // 设置颜色
  color.setRGB(2 * Math.random(), 2 * Math.random(), 2 * Math.random());
  colors.push(color.r, color.g, color.b);
}
// 创建缓冲几何体
const geometry = new THREE.BufferGeometry();
// 给几何体加上属性,一些版本的设置属性函数的名称为setAttribute
// positions: [x1, y1, z1, x2, y2, z2, ...]
geometry.addAttribute(
  "position",
  new THREE.Float32BufferAttribute(positions, 3)
);
// colors: [r1, g1, b1, r2, g2, b2, ...]
geometry.addAttribute("color", new THREE.Float32BufferAttribute(colors, 3));

// 给粒子系统加入PointsMaterial材料
const points = new THREE.Points(
  geometry,
  new THREE.PointsMaterial({
    size: 20, // 粒子大小
    vertexColors: THREE.VertexColors // 使用顶点颜色
  })
);
scene.add(points);

// 动起来,体验一下立体感
function animate() {
  requestAnimationFrame(animate);
  render();
}
function render() {
  points.rotation.x += 0.0025;
  points.rotation.y += 0.005;
  renderer.render(scene, camera);
}
复制代码

此时,可以看见由一个个点构成的几何体展示出来了。demo代码在codesandbox的vec.html

适用场景:量级大,无细微交互、丰富的粒子变换场景

基于paint api

这个不多说,之前我另一篇文章已经介绍过

适用场景:chrome浏览器,支持复杂的动画,但只能简单的交互且没有参数输出

tweenjs

tweenjs是一个数据缓动的库,里面有一些内置的缓动函数,通常用于动画。使用方法:

代码语言:javascript
复制
const o = { v: 0 }
new TWEEN.Tween(o)
  .delay(10000) // 延迟10s
  .to({ v: 1 }, 5000) // 5s内把v从0变成1
  .start(); // 执行

复制代码

tween自带缓动效果预览

与tween结合

THREE.CSS3dObject与tween结合

基于CSS3dObject实现的,如何动起来?举个例子,一开始,先把全部点放在原点。然后,把这些点缓动成一个球。缓动成球的方法:生成球的坐标点集合,遍历全部在原点的点集,一个个地添加tween,缓动到最终球的坐标点坐标上:

代码语言:javascript
复制
        const count = 60;
// 先放在原点
        Object.assign(object.position, {
          x: 0,
          y: 0,
          z: 0
        });

// 缓动到球的每一个点的坐标上
        const phi = Math.acos(-1 + (2 * i) / count);
        const theta = Math.sqrt(count * Math.PI) * phi;

        const SIZE = 800;

        new TWEEN.Tween(object.position)
          .delay(1500)
          .to({
            x: SIZE * Math.cos(theta) * Math.sin(phi),
            y: SIZE * Math.sin(theta) * Math.sin(phi),
            z: -SIZE * Math.cos(phi)
          })
          .start();
复制代码

此时,效果也可以想象出来,就是像爆炸一样

demo地址:gjtyz.csb.app/sphere.html

粒子系统与tween结合

粒子系统使用的是缓冲几何体,我们可以自己给缓冲几何体加上一些自定义属性,然后通过顶点着色器来读取,达到控制顶点属性的效果。

着色器

webgl的着色器的是gpu执行的,所以性能很好,大量的粒子动态渲染都可以不卡。接下来,我们实现一个位置、大小、颜色同时缓动的粒子特效。参考官方文档,我们修改一下代码,得到满足我们需求的顶点着色器代码:

代码语言:javascript
复制
        attribute float size;
        attribute vec3 position2;
        uniform float lamda; // 自己传入
        uniform float size1; // 自己传入
        void main() {
            vec3 temp;
            temp.x = position.x * lamda + position2.x * (1. - lamda);
            temp.y = position.y * lamda + position2.y * (1. - lamda);
            temp.z = position.z * lamda + position2.z * (1. - lamda);
            vec4 mvPosition = modelViewMatrix * vec4( temp, 1.0 );
            gl_PointSize = size1;
            gl_Position = projectionMatrix * mvPosition;
        }
复制代码

改变颜色的是片元着色器:

代码语言:javascript
复制
// 传入color变量
        uniform vec3 color;
        void main() {
            gl_FragColor = vec4(color, 1.0);
        }
复制代码

canvas文字转粒子坐标

大概的思路:创建一个canvas,获取ctx,使用ctx写红色字。再遍历getImageData的点,发现是红点,则把红点坐标加入。最终返回结果是Float32Array类型化数组(x1, y1, z1, x2, y2, z2......),因为THREE的几何体addAttribute的时候需要Float32Array对象

代码语言:javascript
复制
    function getTxtData(str) {
      const c = document.createElement("canvas");
      c.width = innerHeight;
      c.height = innerWidth;
      const t = c.getContext("2d");
      t.fillStyle = "#f00";
      t.font = "150px Georgia";
      t.fillText(str, 100, 100);
      const { data, width, height } = t.getImageData(
        0,
        0,
        innerHeight,
        innerWidth
      );
      const temp = [];
      const gap = 3;
      for (let w = 0; w < width; w += gap) {
        for (let h = 0; h < height; h += gap) {
          const index = (h * width + w) * 4;
          if (data[index] == 255) {
            temp.push(w / 10, -h / 10, 0);
          }
        }
      }
      return new Float32Array(temp);
    }
复制代码

给缓冲几何体加上属性&加入tween

代码语言:javascript
复制
      const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
      const geometry = new THREE.BufferGeometry();
// 字多的、复杂的,粒子多
      const m = getTxtData("I am here");
      geometry.addAttribute("position", new THREE.BufferAttribute(m, 3));
// 字少的、简单的,粒子少
      const newPositions = getTxtData("lhyt");
      const newLen = newPositions.length;
      const positionArr = geometry.attributes.position.array;

      const positionLen = positionArr.length;
      const position2 = new Float32Array(positionLen);
      position2.set(newPositions);
      // 顶点较少的模型顶点坐标,后半部分从头开始赋值
      // 其实也可以隐藏的,不过重复赋值效果好一些
      for (let i = newLen, j = 0; i < positionLen; i++, j++) {
        position2[i] = newPositions[j];
        position2[i + 1] = newPositions[j + 1];
        position2[i + 2] = newPositions[j + 2];
      }

// position2是第二个状态,粒子多的那个状态
      geometry.addAttribute(
        "position2",
        new THREE.BufferAttribute(position2, 3)
      );
// 给tween用的缓动操作对象
      const o = {
        v: 0,
        s: 1,
        c: 0x00ffff
      };
      let uniforms = {
        // 顶点颜色
        color: {
          type: "v3",
          value: new THREE.Color(o.c)
        },
        // 传递lamda、size1值,用于shader计算顶点位置
        lamda: {
          value: o.v
        },
        size1: {
          value: o.s
        }
      };
      // 着色器材料
      const shaderMaterial = new THREE.ShaderMaterial({
        uniforms,
        vertexShader: `
        attribute float size;
        attribute vec3 position2;
        uniform float lamda; // 自己传入
        uniform float size1; // 自己传入
        void main() {
            vec3 temp;
// lamda为0,取position;lamda为0,取position2,达到状态切换的效果
            temp.x = position.x * lamda + position2.x * (1. - lamda);
            temp.y = position.y * lamda + position2.y * (1. - lamda);
            temp.z = position.z * lamda + position2.z * (1. - lamda);
// emm,因为需要4维矩阵来运算
            vec4 mvPosition = modelViewMatrix * vec4( temp, 1.0 );
            gl_PointSize = size1;
            gl_Position = projectionMatrix * mvPosition;
        }
        `,
        fragmentShader: `
        uniform vec3 color;
        void main() {
            gl_FragColor = vec4(color, 1.0);
        }
        `,
        blending: THREE.AdditiveBlending,
        transparent: true
      });
      // 创建粒子系统
      const particleSystem = new THREE.Points(geometry, shaderMaterial);

      scene.add(particleSystem);

      new TWEEN.Tween(o)
        .delay(1000)
        .to({ v: 1, s: 10, c: 0x0000ff }, 2000) // 切换的参数最终状态
        .onUpdate(() => {
// tween更新的时候,把粒子系统的uniforms里面参数也更新,着色器也会根据参数更新
          particleSystem.material.uniforms.lamda.value = o.v;
          particleSystem.material.uniforms.size1.value = o.s;
          particleSystem.material.uniforms.color.value = new THREE.Color(o.c);
        })
        .start();
复制代码

后面就是一些渲染、raf方法,具体代码参考codesandbox的textParticle.html部分的代码

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019年12月01日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 如何构建粒子世界
    • 基于THREE.CSS3dObject
      • 基于THREE的粒子系统
        • 基于paint api
        • tweenjs
        • 与tween结合
          • THREE.CSS3dObject与tween结合
            • 粒子系统与tween结合
              • 着色器
              • canvas文字转粒子坐标
              • 给缓冲几何体加上属性&加入tween
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档