Skip to content

闪耀正方形

鼠标移动正方形会散开并且发光点击下载案例

js
import "./style.scss";

import { MeshMatcapMaterial, Group, Mesh, Color, Vector3, Clock } from "three";
import { scene, camera, renderer, controls } from "./base.js";

import { RoundedBoxGeometry } from "three/examples/jsm/geometries/RoundedBoxGeometry.js";
import { EffectComposer, RenderPass, EffectPass, BloomEffect } from "postprocessing";

camera.position.set(10, 10, 10);
controls.enableDamping = true;
renderer.setAnimationLoop(animate);

// 后处理渲染器
const composer = new EffectComposer(renderer);
composer.addPass(new RenderPass(scene, camera));

// 辉光
const bloom = new BloomEffect({
  mipmapBlur: true, // 启用mipmap
  luminanceThreshold: 0.3, // 降低阈值,更容易触发辉光
  intensity: 0, // 初始强度为0
  levels: 7, // 辉光等级
});

const bloomPass = new EffectPass(camera, bloom);
composer.addPass(bloomPass);

const cubesGroup = new Group(); // 创建一个组,用于存储所有box
scene.add(cubesGroup); // 添加组

const stride = 4; // 创建一个4x4x4的立方体
const gap = 0.1; // 创建的间隔
const displacement = 3; // 创建的位移程度
const intensity = 1; // 辉光强度

const positions = []; // 创建一个positions数组,用于存储每个box的位置
const center = stride / 2 - stride * gap + gap; // 创建的立方体中心点

for (let x = 0; x < stride; x++) {
  for (let y = 0; y < stride; y++) {
    for (let z = 0; z < stride; z++) {
      const pos = [x + x * gap - center, y + y * gap - center, z + z * gap - center];
      positions.push(pos);

      const geom = new RoundedBoxGeometry(1, 1, 1, 2, 0.15);
      const mat = new MeshMatcapMaterial({ color: "white" });
      const cube = new Mesh(geom, mat);
      cube.position.set(...pos);
      cubesGroup.add(cube); // 添加box
    }
  }
}

const clock = new Clock();
const cursor = new Vector3(); // 创建一个向量,用于存储鼠标位置
const oPos = new Vector3(); //  创建一个向量,用于存储box的原始位置
const vec = new Vector3(); // 创建一个向量,用于存储box的位置
const dir = new Vector3(); // 创建一个向量,用于存储box与鼠标之间的方向

function animate() {
  const delta = clock.getDelta();
  const elapsed = clock.elapsedTime;

  cursor.set(pointer.x, pointer.y, 0.5).unproject(camera); // 计算鼠标位置
  dir.copy(cursor).sub(camera.position).normalize(); // 创建一个向量,用于存储box与鼠标之间的方向
  cursor.add(dir.multiplyScalar(camera.position.length())); // 计算box的位置

  let count = 0; // 创建一个计数器,用于记录当前box的索引
  let totalDisplacement = 0; // 记录所有box的总位移程度
  const totalBoxes = cubesGroup.children.length; // 获取box的总数

  for (let child of cubesGroup.children) {
    oPos.set(...positions[count++]);
    dir.copy(oPos).sub(cursor).normalize(); // 计算box与鼠标之间的方向
    const dist = oPos.distanceTo(cursor); // 计算box与鼠标之间的距离
    const distInv = displacement - dist; // 计算box与鼠标之间的距离与 displacement 的差
    const col = Math.max(0.5, distInv) / 1.5; // 计算box与鼠标之间的颜色
    const mov = 1 + Math.sin(elapsed * 2 + 1000 * count); // 创建一个移动效果

    const targetColor = dist > displacement * 1.1 ? new Color("white") : new Color(col / 2, col * 2, col * 4); // 计算box与鼠标之间的颜色
    child.material.color.lerp(targetColor, 0.1); // 改变box的颜色

    const targetPos = dist > displacement ? oPos : vec.copy(oPos).add(dir.multiplyScalar(distInv * intensity + mov / 4)); // 创建一个目标位置
    // 创建一个缓动效果
    child.position.set(damp(child.position.x, targetPos.x, 5, delta), damp(child.position.y, targetPos.y, 5, delta), damp(child.position.z, targetPos.z, 5, delta));

    // 计算当前box与原始位置的距离(散开程度)
    const currentDisplacement = child.position.distanceTo(oPos); // 计算当前box与原始位置的距离
    totalDisplacement += currentDisplacement; // 累加所有box的总位移程度
  }

  const averageDisplacement = totalDisplacement / totalBoxes; // 计算所有box的总位移平均值
  const maxExpectedDisplacement = 2; // 设置最大期望位移
  const bloomIntensity = Math.min(averageDisplacement / maxExpectedDisplacement, 1) * 2; // 计算辉光强度
  bloom.intensity = damp(bloom.intensity, bloomIntensity, 5, delta); // 改变辉光强度

  controls.update();
  renderer.render(scene, camera);
  composer.render();
}

function damp(current, target, lambda, delta) {
  return current + (target - current) * (1 - Math.exp(-lambda * delta));
}

const pointer = { x: 0, y: 0 };
// 鼠标移动
window.addEventListener("pointermove", (e) => {
  pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
  pointer.y = -(e.clientY / window.innerHeight) * 2 + 1;
});

window.addEventListener("resize", () => {
  composer.setSize(window.innerWidth, window.innerHeight);
});

应用场景

借鉴这个思路,可以做一个官网的特效,感觉很酷。