这是一个很酷的金属球,点击它会产生涟漪……

1. 分享

最近看到一个很酷的金属球效果:

点击它的时候会产生涟漪,效果如下:

体验地址:gnufault.github.io/ripple-sphe...

移动端也可以,但因为部署在 Github,需要你科学上网。

2. 实现

该效果使用 three.js 实现,整体代码并不复杂,核心的 JS 代码也就 130 多行。

这是源码地址:github.com/GNUfault/ri...

javascript 复制代码
import * as THREE from "https://unpkg.com/three@0.150.0/build/three.module.js";

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, innerWidth / innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(devicePixelRatio);
document.body.appendChild(renderer.domElement);
document.body.style.margin = "0";
document.body.style.overflow = "hidden";
document.body.style.touchAction = "none";

const envMap = new THREE.CubeTextureLoader().load([
  "https://threejs.org/examples/textures/cube/Bridge2/posx.jpg",
  "https://threejs.org/examples/textures/cube/Bridge2/negx.jpg",
  "https://threejs.org/examples/textures/cube/Bridge2/posy.jpg",
  "https://threejs.org/examples/textures/cube/Bridge2/negy.jpg",
  "https://threejs.org/examples/textures/cube/Bridge2/posz.jpg",
  "https://threejs.org/examples/textures/cube/Bridge2/negz.jpg",
]);
scene.background = envMap;

const material = new THREE.MeshStandardMaterial({
  metalness: 1,
  roughness: 0,
  envMap: envMap,
});

const geometry = new THREE.SphereGeometry(1, 128, 128);
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);

const light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(5, 5, 5);
scene.add(light);
scene.add(new THREE.AmbientLight(0xffffff, 0.5));

camera.position.z = 3;

const originalPositions = geometry.attributes.position.array.slice();

let ripples = [];
const maxRipples = 6;

function addRipple(point) {
  if (ripples.length >= maxRipples) ripples.shift();
  ripples.push({ point, start: performance.now() });
}

function updateRipples() {
  const positions = geometry.attributes.position.array;
  const now = performance.now();
  const vertex = new THREE.Vector3();
  const temp = new THREE.Vector3();

  for (let i = 0; i < positions.length; i += 3) {
    vertex.set(originalPositions[i], originalPositions[i + 1], originalPositions[i + 2]);

    let offset = 0;
    for (const ripple of ripples) {
      const age = (now - ripple.start) / 1000;
      if (age > 2.5) continue;
      const dist = vertex.distanceTo(ripple.point);

      const fadeIn = Math.min(age * 8.0, 1.0);
      const fadeOut = Math.exp(-age * 3.0);
      const wave = Math.sin(dist * 60 - age * 25) * Math.exp(-dist * 5);

      offset += wave * 0.01 * fadeIn * fadeOut;
    }

    temp.copy(vertex).normalize().multiplyScalar(offset);
    positions[i] = originalPositions[i] + temp.x;
    positions[i + 1] = originalPositions[i + 1] + temp.y;
    positions[i + 2] = originalPositions[i + 2] + temp.z;
  }

  geometry.attributes.position.needsUpdate = true;
  geometry.computeVertexNormals();
}

function animate() {
  requestAnimationFrame(animate);
  updateRipples();
  renderer.render(scene, camera);
}
animate();

const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();

function handleInteraction(clientX, clientY) {
  mouse.x = (clientX / innerWidth) * 2 - 1;
  mouse.y = -(clientY / innerHeight) * 2 + 1;

  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObject(sphere);
  if (intersects.length > 0) {
    const worldPoint = intersects[0].point.clone();
    const localPoint = sphere.worldToLocal(worldPoint);
    const normalizedPoint = localPoint.normalize();
    addRipple(normalizedPoint);
  }
}

window.addEventListener("click", (event) => {
  handleInteraction(event.clientX, event.clientY);
});

window.addEventListener("touchstart", (event) => {
  event.preventDefault();
  for (let i = 0; i < event.touches.length; i++) {
    const touch = event.touches[i];
    handleInteraction(touch.clientX, touch.clientY);
  }
});

window.addEventListener("touchmove", (event) => {
  event.preventDefault();
});

window.addEventListener("resize", () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});

你可以:

  • fork 一份,添加其他效果,作为自己的作品集
  • 作为一个学习 three.js 的示例代码
相关推荐
烛阴2 小时前
为什么 `Promise.then` 总比 `setTimeout(..., 0)` 快?微任务的秘密
前端·javascript·typescript
XiaoSong2 小时前
基于 React Native/Expo 项目的持续集成(CI)最佳实践配置指南
前端·react native·react.js
white-persist2 小时前
汇编代码详细解释:汇编语言如何转化为对应的C语言,怎么转化为对应的C代码?
java·c语言·前端·网络·汇编·安全·网络安全
张愚歌2 小时前
轻松打造个性化Leaflet地图标记
前端·javascript
华仔啊2 小时前
CSS实现高级流光按钮动画,这几行代码堪称神来之笔
前端·css
用户3777967210962 小时前
新值依赖旧值?并发更新的“坑”
javascript
歪歪1002 小时前
详细介绍一下“集中同步+分布式入库”方案的具体实现步骤
开发语言·前端·分布式·后端·信息可视化
林太白2 小时前
rust17-部门管理模块
前端·后端·rust
_处女座程序员的日常2 小时前
如何预览常见格式word、excel、ppt、图片等格式的文档
前端·javascript·word·excel·开源软件