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

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 的示例代码
相关推荐
前端小白۞12 分钟前
vue2 md文件预览和下载
前端·javascript·vue.js
十里-19 分钟前
为什么创建1x1的gif图片,和png 或者jpg图片有什么区别
前端
u***u68527 分钟前
Vue云原生
前端·vue.js·云原生
OpenTiny社区43 分钟前
TinyEngine 低代码实时协作揭秘:原理 +实操,看完直接用!
前端·vue.js·低代码
5***79001 小时前
Vue项目性能优化
前端·javascript·vue.js
丫丫7237341 小时前
Three.js 模型树结构与节点查询学习笔记
javascript·webgl
车传新2 小时前
Javascript
javascript
天若有情6732 小时前
【c++】手撸C++ Promise:从零实现通用异步回调组件,支持链式调用+异常安全
开发语言·前端·javascript·c++·promise
抱琴_2 小时前
【Vue3】大屏性能优化黑科技:Vue 3 中实现请求合并,让你的大屏飞起来!
前端·vue.js