# 一、应用示例

# 1、大波浪特效

three.js 官方实例:waves (opens new window)

  iview 官方界面就采用了类似的特效:

  下面我们可以自己实现一下:

普通效果
  • login.vue
<template>
  <WavesBg :top="250"></WavesBg>

  <div class="login-container">登录card</div>
</template>

<script setup lang="ts">
import WavesBg from '@/components/waves.vue'
</script>

<style scoped>
/* 卡片居中 */
.login-container {
  width: 100%;
  height: 100vh;
  background-color: cadetblue;

  display: flex;
  align-items: center;
  justify-content: center;
}
</style>
  • waves.vue
<template>
  <div id="iviewBg"></div>
</template>

<script setup>
import * as THREE from 'three'
// 显示右上角fps框
import { onMounted } from 'vue'

const props = defineProps({
  //控制x轴波浪的长度
  amountX: {
    type: Number,
    default: 50
  },
  //控制y轴波浪的长度
  amountY: {
    type: Number,
    default: 50
  },
  //控制点颜色
  color: {
    type: String,
    default: '#097bdb'
  },
  //控制波浪的位置
  top: {
    type: Number,
    default: 350
  }
})

const SEPARATION = 100

// let stats;
let container, camera, scene, renderer

let particles,
  count = 0

let mouseX = 0

let windowHalfX = window.innerWidth / 2

function init() {
  container = document.createElement('div')
  document.getElementById('iviewBg').appendChild(container)

  //创建透视相机
  camera = new THREE.PerspectiveCamera(
    75, //摄像机视锥体垂直视野角度
    window.innerWidth / window.innerHeight, //摄像机视锥体长宽比
    1, //摄像机视锥体近端面
    10000 //摄像机视锥体远端面
  )

  //设置相机z轴视野
  camera.position.z = 1000

  //创建场景
  scene = new THREE.Scene()

  const numParticles = props.amountX * props.amountY

  const positions = new Float32Array(numParticles * 3)
  const scales = new Float32Array(numParticles)

  let i = 0,
    j = 0

  // 初始化粒子位置和大小
  for (let ix = 0; ix < props.amountX; ix++) {
    for (let iy = 0; iy < props.amountY; iy++) {
      positions[i] = ix * SEPARATION - (props.amountX * SEPARATION) / 2 // x
      positions[i + 1] = 0 // y
      positions[i + 2] = iy * SEPARATION - (props.amountY * SEPARATION) / 2 // z
      scales[j] = 1
      i += 3
      j++
    }
  }

  //是面片、线或点几何体的有效表述。包括顶点位置,面片索引、法相量、颜色值、UV 坐标和自定义缓存属性值。使用 BufferGeometry 可以有效减少向 GPU 传输上述数据所需的开销
  const geometry = new THREE.BufferGeometry()
  geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
  geometry.setAttribute('scale', new THREE.BufferAttribute(scales, 1))

  //着色器材质(ShaderMaterial),设置球的大小,颜色,等
  const material = new THREE.ShaderMaterial({
    uniforms: {
      //设置球的颜色
      color: { value: new THREE.Color(props.color) }
    },
    //控制球的大小
    vertexShader:
      'attribute float scale; void main() {vec4 mvPosition = modelViewMatrix * vec4( position, 1.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)

  //alpha - canvas是否包含alpha (透明度)。默认为 false。
  //渲染器的背景色默认为黑色,设置渲染器的背景色为透明
  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
  renderer.setPixelRatio(window.devicePixelRatio)
  renderer.setClearAlpha(0)
  renderer.setSize(window.innerWidth, window.innerHeight)
  container.appendChild(renderer.domElement)

  //显示右上角fps框
  // stats = new Stats();
  // container.appendChild(stats.dom);

  container.style.touchAction = 'none'
  //监听鼠标移动事件
  container.addEventListener('pointermove', onPointerMove)

  //调整波浪的位置
  container.style.position = 'relative'
  container.style.top = `${props.top}px`

  window.addEventListener('resize', onWindowResize)
}

function 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,
    j = 0
  for (let ix = 0; ix < props.amountX; ix++) {
    for (let iy = 0; iy < props.amountY; iy++) {
      positions[i + 1] = Math.sin((ix + count) * 0.3) * 50 + Math.sin((iy + count) * 0.5) * 50

      scales[j] = (Math.sin((ix + count) * 0.3) + 1) * 10 + (Math.sin((iy + count) * 0.5) + 1) * 10

      i += 3
      j++
    }
  }

  particles.geometry.attributes.position.needsUpdate = true
  particles.geometry.attributes.scale.needsUpdate = true

  renderer.render(scene, camera)

  count += 0.1
}

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

//监听鼠标移动事件
function onPointerMove(event) {
  // console.log(event)
  if (event.isPrimary === false) return
  mouseX = event.clientX - windowHalfX
}

function animate() {
  requestAnimationFrame(animate)
  render()
  //fps 实时更新
  // stats.update();
}

onMounted(() => {
  init()
  animate()
})
</script>

<style>
#iviewBg {
  width: 100%;
  height: 100vh;

  position: absolute;
  top: 0;
  left: 0;

  background: url('../assets/wavesBg.png') no-repeat;
  background-repeat: no-repeat;
  background-size: cover;
  overflow: hidden;
}
</style>
  • types / three.d.ts
declare module 'three'
declare module '@/components/waves.vue'
3d 加强

https://mybj123.com/8316.html

波浪 plus

https://juejin.cn/post/6844904015671590919

# 2、粒子特效

  常见的粒子特效有:雪花、星星、烟花等。这里我不得不提一下一个非常优秀的库Particles.js (opens new window)

Particles.js 库基于 HTML5 Canvas 技术开发,通过自定义粒子的大小、颜色、速度、方向、形状等参数,实现各种炫酷的粒子效果

基本使用

  以前有一个 vue-particles (opens new window) 的 Vue 组件库,可以在在网页中创建粒子效果

使用示例

https://www.cnblogs.com/IwishIcould/p/11568563.html

vue-particles 是基于 Particles.js 开发,使用 Canvas 渲染粒子效果,可以在 vue2 中使用。

  但在现在的 vue、react 框架开发下,我更加推荐使用tsParticles (opens new window)

tsparticles 是一个基于 Typescript 和 Particles.js 的 JavaScript 库。并且与 vue2、vue3 (opens new window)、react、angular、jQuery 等兼容。

使用示例

  上图的目标效果:https://codepen.io/lencamo/pen/poxammE?editors=0010

  • login.vue
<template>
  <Particle></Particle>

  <div class="login-container">登录card</div>
</template>

<script setup lang="ts">
import Particle from '@/components/particle.vue'
</script>

<style scoped>
/* 卡片居中 */
.login-container {
  width: 100%;
  height: 100vh;
  background-color: cadetblue;

  display: flex;
  align-items: center;
  justify-content: center;
}
</style>
  • particle.vue
<script setup>
import { loadFull } from 'tsparticles'

const particlesInit = async (engine) => {
  await loadFull(engine)
}

const particlesLoaded = async (container) => {
  console.log('Particles container loaded', container)
}
</script>

<template>
  <div id="app">
    <Particles
      id="tsparticles"
      :particlesInit="particlesInit"
      :particlesLoaded="particlesLoaded"
      url="/particles.json"
    />
  </div>
</template>
  • particles.json

在线配置 (opens new window)

{
  "background": {
    "color": {
      "value": "#0d47a1"
    }
  },
  "fpsLimit": 120,
  "interactivity": {
    "events": {
      "onClick": {
        "enable": true,
        "mode": "push"
      },
      "onHover": {
        "enable": true,
        "mode": "attract",
        "parallax": { "enable": false, "force": 60, "smooth": 10 }
      },
      "resize": true
    },
    "modes": {
      "push": {
        "quantity": 4
      },
      "attract": { "distance": 200, "duration": 0.4, "factor": 5 }
    }
  },
  "particles": {
    "color": {
      "value": "#ffffff"
    },
    "links": {
      "color": "#ffffff",
      "distance": 150,
      "enable": true,
      "opacity": 0.4,
      "width": 1
    },
    "move": {
      "attract": { "enable": false, "rotateX": 600, "rotateY": 1200 },
      "bounce": false,
      "direction": "none",
      "enable": true,
      "out_mode": "out",
      "random": false,
      "speed": 2,
      "straight": false
    },
    "number": {
      "density": {
        "enable": true,
        "area": 800
      },
      "value": 80
    },
    "opacity": {
      "value": 0.5
    },
    "shape": {
      "character": {
        "fill": false,
        "font": "Verdana",
        "style": "",
        "value": "*",
        "weight": "400"
      },
      "polygon": { "nb_sides": 5 },
      "stroke": { "color": "#000000", "width": 0 },
      "type": "circle"
    },
    "size": {
      "anim": { "enable": false, "size_min": 0.1, "speed": 40, "sync": false },
      "random": true,
      "value": 5
    }
  },
  "polygon": {
    "draw": { "enable": false, "lineColor": "#ffffff", "lineWidth": 0.5 },
    "move": { "radius": 10 },
    "scale": 1,
    "type": "none",
    "url": ""
  },
  "detectRetina": true
}
更新于 : 8/7/2024, 2:16:31 PM