Vue3 实现 Three.js粒子特效

效果

在这里插入图片描述

<template>
  <div id="waves" />
</template>

<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import * as THREE from "three";

const amountX = ref(50);
const amountY = ref(50);
const color = ref("#e1b284");
const topOffset = ref(350);

let count = 0;
let mouseX = 0;
let windowHalfX = null;
let camera = null;
let scene = null;
let particles = null;
let renderer = null;

const init = () => {
  const SEPARATION = 100;
  const SCREEN_WIDTH = window.innerWidth;
  const SCREEN_HEIGHT = window.innerHeight;
  const container = document.createElement("div");
  windowHalfX = window.innerWidth / 2;
  container.style.position = "relative";
  container.style.top = `${topOffset.value}px`;
  container.style.height = `${SCREEN_HEIGHT - topOffset.value}px`;

  const waves = document.getElementById("waves");
  waves.appendChild(container);

  camera = new THREE.PerspectiveCamera(
    75,
    SCREEN_WIDTH / SCREEN_HEIGHT,
    1,
    10000,
  );
  camera.position.z = 1000;

  scene = new THREE.Scene();

  const numParticles = amountX.value * amountY.value;
  const positions = new Float32Array(numParticles * 3);
  const scales = new Float32Array(numParticles);

  let i = 0;
  let j = 0;
  for (let ix = 0; ix < amountX.value; ix++) {
    for (let iy = 0; iy < amountY.value; iy++) {
      positions[i] = ix * SEPARATION - (amountX.value * SEPARATION) / 2;
      positions[i + 1] = 0;
      positions[i + 2] = iy * SEPARATION - (amountY.value * SEPARATION) / 2;
      scales[j] = 1;
      i += 3;
      j++;
    }
  }

  const geometry = new THREE.BufferGeometry();
  geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
  geometry.setAttribute("scale", new THREE.BufferAttribute(scales, 1));

  const material = new THREE.ShaderMaterial({
    uniforms: {
      color: { value: new THREE.Color(color.value) },
    },
    vertexShader: `
      attribute float scale;
      void main() {
        vec4 mvPosition = modelViewMatrix * vec4( position, 2.0 );
        gl_PointSize = scale * ( 300.0 / - mvPosition.z );
        gl_Position = projectionMatrix * mvPosition;
      }
    `,
    fragmentShader: `
      uniform vec3 color;
      void main() {
        if ( length( gl_PointCoord - vec2( 0.5, 0.5 ) ) > 0.475 ) discard;
        gl_FragColor = vec4( color, 1.0 );
      }
    `,
  });

  particles = new THREE.Points(geometry, material);
  scene.add(particles);

  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.setSize(container.clientWidth, container.clientHeight);
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setClearAlpha(0);
  container.appendChild(renderer.domElement);

  window.addEventListener("resize", onWindowResize, { passive: false });
  document.addEventListener("mousemove", onDocumentMouseMove, {
    passive: false,
  });
  document.addEventListener("touchstart", onDocumentTouchStart, {
    passive: false,
  });
  document.addEventListener("touchmove", onDocumentTouchMove, {
    passive: false,
  });
};

const render = () => {
  camera.position.x += (mouseX - camera.position.x) * 0.05;
  camera.position.y = 400;
  camera.lookAt(scene.position);

  const positions = particles.geometry.attributes.position.array;
  const scales = particles.geometry.attributes.scale.array;

  let i = 0;
  let j = 0;
  for (let ix = 0; ix < amountX.value; ix++) {
    for (let iy = 0; iy < amountY.value; iy++) {
      positions[i + 1] =
        Math.sin((ix + count) * 0.3) * 100 + Math.sin((iy + count) * 0.5) * 100;
      scales[j] =
        (Math.sin((ix + count) * 0.3) + 1) * 8 +
        (Math.sin((iy + count) * 0.5) + 1) * 8;
      i += 3;
      j++;
    }
  }

  particles.geometry.attributes.position.needsUpdate = true;
  particles.geometry.attributes.scale.needsUpdate = true;
  renderer.render(scene, camera);
  count += 0.1;
};

const animate = () => {
  requestAnimationFrame(animate);
  render();
};

const onDocumentMouseMove = (event) => {
  mouseX = event.clientX - windowHalfX;
};

const onDocumentTouchStart = (event) => {
  if (event.touches.length === 1) {
    mouseX = event.touches[0].pageX - windowHalfX;
  }
};

const onDocumentTouchMove = (event) => {
  if (event.touches.length === 1) {
    event.preventDefault();
    mouseX = event.touches[0].pageX - windowHalfX;
  }
};

const onWindowResize = () => {
  windowHalfX = window.innerWidth / 2;
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
};

onMounted(() => {
  init();
  animate();
});

onUnmounted(() => {
  window.removeEventListener("resize", onWindowResize);
  document.removeEventListener("mousemove", onDocumentMouseMove);
  document.removeEventListener("touchstart", onDocumentTouchStart);
  document.removeEventListener("touchmove", onDocumentTouchMove);
});
</script>

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值