You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Three.js鼠标悬停物体时无法实现相机缩放效果求助

问题描述

我是Three.js新手,这是我的第一个项目。我想要实现鼠标悬停在frame1物体上时,相机能平滑缩放聚焦该物体的效果(类似将目标元素放大至屏幕中心的交互)。目前我能将frame1移到屏幕中央,但无法实现相机缩放,尝试过Tween但无效,怀疑代码基础逻辑存在问题。

项目代码

<!DOCTYPE html>
<html>

<head>
  <title>Three.js JPG Background with Custom Position</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r121/three.min.js"></script>
  <script src="./js/tween.js/dist/tween.umd.js"></script>

  <!-- Css -->
<style>
  body {
    overflow: hidden;
}

canvas {
    width: 100%;
    height: 100%;
    position: fixed;
    top: 0;
    left: 0;
}
</style>
</head>

<body>
  <script>
    // Initialize Three.js scene
    var scene = new THREE.Scene();
    var camera = new THREE.OrthographicCamera(window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 0.1,);
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);


    // Load the Main BG texture
    var mainBg = new THREE.TextureLoader().load("http://127.0.0.1:5500/img/main-bg.jpg");

    // Create a full-screen plane to display the texture
    var geometry1 = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight);
    var material1 = new THREE.MeshBasicMaterial({ map: mainBg });
    var plane1 = new THREE.Mesh(geometry1, material1);
    plane1.position.set(0, 0, 0)
    scene.add(plane1);

    // Create a full-screen plane to display the texture
    var frame1 = new THREE.TextureLoader().load("http://127.0.0.1:5500/img/frame1.png");
    var geometry2 = new THREE.PlaneGeometry(90, 110);
    var material2 = new THREE.MeshBasicMaterial({ map: frame1 });
    var plane2 = new THREE.Mesh(geometry2, material2);
    plane2.position.set(-190, 130, .1);
    scene.add(plane2);

    // Check if mouse is over the child asset
    var raycaster = new THREE.Raycaster();
    var mouse = new THREE.Vector2();
    var zoomed = false;

    // Mouse move function
    function onMouseMove(event) {
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      raycaster.setFromCamera(mouse, camera);
      var intersects = raycaster.intersectObjects([plane2]);
      if (intersects.length > 0 && !zoomed) {
        zoomed = true;
        camera.position.set(plane2.position.x, plane2.position.y, plane2.position.z);
        camera.lookAt(plane2.position.x, plane2.position.y, plane2.position.z);

      } else if (intersects.length === 0 && zoomed) {
        zoomed = false;
        camera.position.set(0, 0, 5);
        camera.lookAt(new THREE.Vector3(0, 0, 0));
      }
    }
    // Mouse move function calling on moving mouse
    document.addEventListener('mousemove', onMouseMove, false);
    window.addEventListener('mousedown', function () {
      gsap.to(camera.position, {
        z: 15,
        duration: 15
      });
    })

    // Render loop
    function render() {
      camera.position.z = 5;
      requestAnimationFrame(render);
      renderer.render(scene, camera);
    }

    render();


  </script>
</body>

</html>

项目资源

  • frame1(透明PNG格式图片)
  • main-bg(背景图)

解决方案

你的代码存在三个核心问题导致缩放失效,逐个修正即可实现需求:

1. 正交相机缩放逻辑错误

你使用的是OrthographicCamera(正交相机),这类相机无透视效果,修改position.z不会产生缩放效果。正交相机的缩放需调整zoom属性,或者修改left/right/top/bottom参数,同时每次修改后必须调用camera.updateProjectionMatrix()更新投影矩阵。

2. 渲染循环强制覆盖相机状态

render函数中的camera.position.z = 5;会在每一帧强制重置相机z轴位置,直接覆盖所有动画修改,这是缩放失效的直接原因,必须删除该行代码。

3. 动画逻辑混乱

你同时引入了Tween.js和GSAP但未正确使用,且悬停逻辑直接硬改相机位置,无平滑过渡效果。

以下是修正后的完整代码,实现鼠标悬停frame1时平滑缩放聚焦、移出时恢复原状的效果:

<!DOCTYPE html>
<html>

<head>
  <title>Three.js JPG Background with Custom Position</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r121/three.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>

  <!-- Css -->
<style>
  body {
    overflow: hidden;
    margin: 0;
}

canvas {
    width: 100%;
    height: 100%;
    position: fixed;
    top: 0;
    left: 0;
}
</style>
</head>

<body>
  <script>
    // Initialize Three.js scene
    var scene = new THREE.Scene();
    // 补全正交相机远裁剪面参数,避免渲染异常
    var camera = new THREE.OrthographicCamera(
      window.innerWidth / -2, 
      window.innerWidth / 2, 
      window.innerHeight / 2, 
      window.innerHeight / -2, 
      0.1, 
      1000
    );
    camera.position.set(0, 0, 5);
    camera.lookAt(scene.position);
    var renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);


    // Load the Main BG texture(替换为你的本地背景图路径)
    var mainBg = new THREE.TextureLoader().load("./img/main-bg.jpg");
    mainBg.minFilter = THREE.LinearFilter;

    // Create a full-screen plane to display the texture
    var geometry1 = new THREE.PlaneGeometry(window.innerWidth, window.innerHeight);
    var material1 = new THREE.MeshBasicMaterial({ map: mainBg });
    var plane1 = new THREE.Mesh(geometry1, material1);
    plane1.position.set(0, 0, 0)
    scene.add(plane1);

    // Create frame1 plane(替换为你的本地frame1图片路径)
    var frame1 = new THREE.TextureLoader().load("./img/frame1.png");
    frame1.minFilter = THREE.LinearFilter;
    var geometry2 = new THREE.PlaneGeometry(90, 110);
    // 开启透明,适配PNG图片的透明区域
    var material2 = new THREE.MeshBasicMaterial({ map: frame1, transparent: true });
    var plane2 = new THREE.Mesh(geometry2, material2);
    plane2.position.set(-190, 130, 0.1);
    scene.add(plane2);

    // Check if mouse is over the child asset
    var raycaster = new THREE.Raycaster();
    var mouse = new THREE.Vector2();
    var zoomed = false;

    // 保存相机初始状态,用于移出时恢复
    const initialCamera = {
      pos: new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z),
      zoom: camera.zoom
    };
    // 聚焦frame1时的相机状态
    const targetCamera = {
      pos: new THREE.Vector3(plane2.position.x, plane2.position.y, camera.position.z),
      zoom: 3 // 缩放倍数,数值越大放大效果越明显
    };

    // Mouse move function
    function onMouseMove(event) {
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      raycaster.setFromCamera(mouse, camera);
      var intersects = raycaster.intersectObjects([plane2]);
      
      if (intersects.length > 0 && !zoomed) {
        zoomed = true;
        // 平滑过渡到聚焦状态
        gsap.to(camera.position, {
          x: targetCamera.pos.x,
          y: targetCamera.pos.y,
          duration: 0.5,
          onUpdate: () => camera.updateProjectionMatrix()
        });
        gsap.to(camera, {
          zoom: targetCamera.zoom,
          duration: 0.5,
          onUpdate: () => camera.updateProjectionMatrix()
        });
      } else if (intersects.length === 0 && zoomed) {
        zoomed = false;
        // 平滑恢复初始状态
        gsap.to(camera.position, {
          x: initialCamera.pos.x,
          y: initialCamera.pos.y,
          duration: 0.5,
          onUpdate: () => camera.updateProjectionMatrix()
        });
        gsap.to(camera, {
          zoom: initialCamera.zoom,
          duration: 0.5,
          onUpdate: () => camera.updateProjectionMatrix()
        });
      }
    }
    document.addEventListener('mousemove', onMouseMove, false);

    // 窗口大小变化时更新相机参数,保证适配屏幕
    window.addEventListener('resize', () => {
      camera.left = window.innerWidth / -2;
      camera.right = window.innerWidth / 2;
      camera.top = window.innerHeight / 2;
      camera.bottom = window.innerHeight / -2;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    });

    // Render loop
    function render() {
      requestAnimationFrame(render);
      renderer.render(scene, camera);
    }

    render();
  </script>
</body>
</html>

关键修改说明

  • 删除了渲染循环中强制重置相机z轴的代码,避免覆盖动画效果
  • 补全正交相机的远裁剪面参数,修复潜在渲染问题
  • 使用正交相机的zoom属性实现缩放,配合updateProjectionMatrix()更新投影
  • 采用GSAP实现相机位置和缩放的平滑过渡,提升交互体验
  • 为frame1材质添加transparent: true,确保PNG透明区域正常显示
  • 添加窗口大小监听,保证屏幕尺寸变化时相机参数自动适配

内容的提问来源于stack exchange,提问作者Zohaib Mateen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 11:55:45