Three.js在vue中的使用(二)-加载、控制

该文章已生成可运行项目,

在 Vue 中使用 Three.js 加载模型、控制视角、添加点击事件是构建 3D 场景的常见需求。下面是一个完整的示例,演示如何在 Vue 单文件组件中实现以下功能:

  • 使用 GLTFLoader 加载 .glb/.gltf 模型
  • 添加 OrbitControls 控制视角(旋转、缩放、平移)
  • 给模型添加点击事件

使用的技术栈

  • Vue 3 + Composition API(或 Vue 2)
  • Three.js 核心库
  • three/examples/js/loaders/GLTFLoader
  • three/examples/js/controls/OrbitControls

📦 安装依赖(如未安装)

npm install three
npm install three-gltf-loader  # 或直接引入 GLTFLoader

示例代码:Vue 单文件组件

<template>
  <div class="model-viewer-container" ref="viewerContainer"></div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'

const viewerContainer = ref(null)

let scene, camera, renderer, controls, model

function init() {
  // 创建场景
  scene = new THREE.Scene()
  scene.background = new THREE.Color(0xeeeeee)

  // 创建相机
  const width = viewerContainer.value.clientWidth
  const height = viewerContainer.value.clientHeight
  camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000)
  camera.position.set(0, 2, 5)

  // 创建渲染器
  renderer = new THREE.WebGLRenderer({ antialias: true })
  renderer.setSize(width, height)
  renderer.setPixelRatio(window.devicePixelRatio)
  viewerContainer.value.appendChild(renderer.domElement)

  // 添加光源
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.6)
  scene.add(ambientLight)

  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)
  directionalLight.position.set(5, 10, 7.5)
  scene.add(directionalLight)

  // 添加控制器
  controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true

  // 加载模型
  const loader = new GLTFLoader()
  loader.load(
    '/models/test.glb', // 替换为你的模型路径
    (gltf) => {
      model = gltf.scene
      scene.add(model)

      // 添加点击事件监听
      window.addEventListener('click', onClick)
    },
    undefined,
    (error) => {
      console.error('An error occurred while loading the model:', error)
    }
  )

  // 渲染循环
  function animate() {
    requestAnimationFrame(animate)
    controls.update()
    renderer.render(scene, camera)
  }

  animate()
}

// 点击事件处理函数
function onClick(event) {
  if (!model) return

  // 计算鼠标归一化坐标
  const mouse = new THREE.Vector2()
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1

  // 创建射线
  const raycaster = new THREE.Raycaster()
  raycaster.setFromCamera(mouse, camera)

  // 获取模型中的所有可交互对象
  const intersects = raycaster.intersectObject(model, true)

  if (intersects.length > 0) {
    console.log('点击了模型!', intersects[0].object)
    alert('你点击了模型上的一个部件')
  }
}

// 响应窗口变化
window.addEventListener('resize', () => {
  if (!camera || !renderer) return
  camera.aspect = viewerContainer.value.clientWidth / viewerContainer.value.clientHeight
  camera.updateProjectionMatrix()
  renderer.setSize(viewerContainer.value.clientWidth, viewerContainer.value.clientHeight)
})

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

<style scoped>
.model-viewer-container {
  width: 100%;
  height: 100vh;
}
</style>

文件结构建议

your-project/
├── public/
│   └── models/
│       └── test.glb   <-- 放置你的模型文件
├── src/
│   └── components/
│       └── ModelViewer.vue

注意:模型放在 public/models/ 目录下,通过 /models/test.glb 路径访问。


🔧 功能说明

功能实现方式
加载模型使用 GLTFLoader 加载 .glb.gltf 模型
控制视角使用 OrbitControls 实现自由旋转、缩放、平移
点击事件使用 Raycaster 进行射线检测,判断是否点击到模型
响应式布局监听 resize 事件并更新相机和渲染器尺寸

扩展建议

需求推荐做法
多个模型加载使用 Promise.all() 异步加载多个模型
模型动画播放使用 AnimationMixerClock 控制动画
加载进度条使用 LoadingManager 显示加载百分比
自定义材质遍历模型子对象并修改材质颜色、透明度等属性
高亮选中部分修改点击对象的材质颜色或使用 OutlinePass 后期高亮
本文章已经生成可运行项目
chunk-vendors.js:13463 [HMR] Waiting for update signal from WDS... main.js:26 [Violation] Added non-passive event listener to a scroll-blocking 'mousewheel' event. Consider marking event handler as 'passive' to make the page more responsive. See https://www.chromestatus.com/feature/5745543795965952 addEventListener @ event.js:201 mountSingleDOMEventListener @ HandlerProxy.js:412 eval @ HandlerProxy.js:359 each @ util.js:300 mountLocalDOMEventListeners @ HandlerProxy.js:358 HandlerDomProxy @ HandlerProxy.js:486 ZRender @ zrender.js:138 init @ zrender.js:49 ECharts @ echarts.js:206 init @ echarts.js:1928 mounted @ cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/components/areaChart.vue?vue&type=script&lang=js:199 invokeWithErrorHandling @ vue.runtime.esm.js:3072 callHook$1 @ vue.runtime.esm.js:4087 insert @ vue.runtime.esm.js:4480 invokeInsertHook @ vue.runtime.esm.js:6999 patch @ vue.runtime.esm.js:7213 Vue._update @ vue.runtime.esm.js:3824 updateComponent @ vue.runtime.esm.js:3930 Watcher.get @ vue.runtime.esm.js:3501 Watcher.run @ vue.runtime.esm.js:3577 flushSchedulerQueue @ vue.runtime.esm.js:4180 eval @ vue.runtime.esm.js:3198 flushCallbacks @ vue.runtime.esm.js:3120 Promise.then timerFunc @ vue.runtime.esm.js:3145 nextTick @ vue.runtime.esm.js:3210 queueWatcher @ vue.runtime.esm.js:4266 Watcher.update @ vue.runtime.esm.js:3568 Vue.$forceUpdate @ vue.runtime.esm.js:3849 forceRender_1 @ vue.runtime.esm.js:2816 eval @ vue.runtime.esm.js:2836 eval @ vue.runtime.esm.js:370 Promise.then resolveAsyncComponent @ vue.runtime.esm.js:2856 createComponent @ vue.runtime.esm.js:4531 _createElement @ vue.runtime.esm.js:2971 createElement$1 @ vue.runtime.esm.js:2921 vm._c @ vue.runtime.esm.js:2694 render @ cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"3bd8c2c6-vue-loader-template"}!./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/mainPage.vue?vue&type=template&id=0be33bfc&scoped=true:28 Vue._render @ vue.runtime.esm.js:2739 updateComponent @ vue.runtime.esm.js:3930 Watcher.get @ vue.runtime.esm.js:3501 Watcher @ vue.runtime.esm.js:3491 mountComponent @ vue.runtime.esm.js:3947 Vue.$mount @ vue.runtime.esm.js:8830 init @ vue.runtime.esm.js:4464 merged @ vue.runtime.esm.js:4618 createComponent @ vue.runtime.esm.js:6624 createElm @ vue.runtime.esm.js:6578 updateChildren @ vue.runtime.esm.js:6873 patchVnode @ vue.runtime.esm.js:6966 updateChildren @ vue.runtime.esm.js:6840 patchVnode @ vue.runtime.esm.js:6966 patch @ vue.runtime.esm.js:7134 Vue._update @ vue.runtime.esm.js:3824 updateComponent @ vue.runtime.esm.js:3930 Watcher.get @ vue.runtime.esm.js:3501 Watcher.run @ vue.runtime.esm.js:3577 flushSchedulerQueue @ vue.runtime.esm.js:4180 eval @ vue.runtime.esm.js:3198 flushCallbacks @ vue.runtime.esm.js:3120 Promise.then timerFunc @ vue.runtime.esm.js:3145 nextTick @ vue.runtime.esm.js:3210 queueWatcher @ vue.runtime.esm.js:4266 Watcher.update @ vue.runtime.esm.js:3568 Dep.notify @ vue.runtime.esm.js:790 reactiveSetter @ vue.runtime.esm.js:1021 eval @ vue-router.esm.js:3013 eval @ vue-router.esm.js:3012 updateRoute @ vue-router.esm.js:2422 eval @ vue-router.esm.js:2271 eval @ vue-router.esm.js:2410 step @ vue-router.esm.js:2092 step @ vue-router.esm.js:2099 step @ vue-router.esm.js:2099 runQueue @ vue-router.esm.js:2103 eval @ vue-router.esm.js:2405 step @ vue-router.esm.js:2092 eval @ vue-router.esm.js:2096 eval @ vue-router.esm.js:2392 eval @ vue-router.esm.js:2135 eval @ vue-router.esm.js:2211 Promise.then eval @ vue-router.esm.js:2158 eval @ vue-router.esm.js:2179 eval @ vue-router.esm.js:2179 flatMapComponents @ vue-router.esm.js:2178 eval @ vue-router.esm.js:2114 iterator @ vue-router.esm.js:2370 step @ vue-router.esm.js:2095 step @ vue-router.esm.js:2099 step @ vue-router.esm.js:2099 runQueue @ vue-router.esm.js:2103 confirmTransition @ vue-router.esm.js:2400 transitionTo @ vue-router.esm.js:2268 init @ vue-router.esm.js:3004 beforeCreate @ vue-router.esm.js:1306 invokeWithErrorHandling @ vue.runtime.esm.js:3072 callHook$1 @ vue.runtime.esm.js:4087 Vue._init @ vue.runtime.esm.js:5745 Vue @ vue.runtime.esm.js:5818 eval @ main.js:26 ./src/main.js @ app.js:1304 __webpack_require__ @ app.js:854 fn @ app.js:151 1 @ app.js:1377 __webpack_require__ @ app.js:854 checkDeferredModules @ app.js:46 (anonymous) @ app.js:994 (anonymous) @ app.js:997 main.js:26 [Violation] Added non-passive event listener to a scroll-blocking 'mousewheel' event. Consider marking event handler as 'passive' to make the page more responsive. See https://www.chromestatus.com/feature/5745543795965952 addEventListener @ event.js:201 mountSingleDOMEventListener @ HandlerProxy.js:412 eval @ HandlerProxy.js:359 each @ util.js:300 mountLocalDOMEventListeners @ HandlerProxy.js:358 HandlerDomProxy @ HandlerProxy.js:486 ZRender @ zrender.js:138 init @ zrender.js:49 ECharts @ echarts.js:206 init @ echarts.js:1928 mounted @ cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/components/page2/barChart.vue?vue&type=script&lang=js:189 invokeWithErrorHandling @ vue.runtime.esm.js:3072 callHook$1 @ vue.runtime.esm.js:4087 insert @ vue.runtime.esm.js:4480 invokeInsertHook @ vue.runtime.esm.js:6999 patch @ vue.runtime.esm.js:7213 Vue._update @ vue.runtime.esm.js:3824 updateComponent @ vue.runtime.esm.js:3930 Watcher.get @ vue.runtime.esm.js:3501 Watcher.run @ vue.runtime.esm.js:3577 flushSchedulerQueue @ vue.runtime.esm.js:4180 eval @ vue.runtime.esm.js:3198 flushCallbacks @ vue.runtime.esm.js:3120 Promise.then timerFunc @ vue.runtime.esm.js:3145 nextTick @ vue.runtime.esm.js:3210 queueWatcher @ vue.runtime.esm.js:4266 Watcher.update @ vue.runtime.esm.js:3568 Vue.$forceUpdate @ vue.runtime.esm.js:3849 forceRender_1 @ vue.runtime.esm.js:2816 eval @ vue.runtime.esm.js:2836 eval @ vue.runtime.esm.js:370 Promise.then resolveAsyncComponent @ vue.runtime.esm.js:2856 createComponent @ vue.runtime.esm.js:4531 _createElement @ vue.runtime.esm.js:2971 createElement$1 @ vue.runtime.esm.js:2921 vm._c @ vue.runtime.esm.js:2694 render @ cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"3bd8c2c6-vue-loader-template"}!./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/mainPage.vue?vue&type=template&id=0be33bfc&scoped=true:48 Vue._render @ vue.runtime.esm.js:2739 updateComponent @ vue.runtime.esm.js:3930 Watcher.get @ vue.runtime.esm.js:3501 Watcher @ vue.runtime.esm.js:3491 mountComponent @ vue.runtime.esm.js:3947 Vue.$mount @ vue.runtime.esm.js:8830 init @ vue.runtime.esm.js:4464 merged @ vue.runtime.esm.js:4618 createComponent @ vue.runtime.esm.js:6624 createElm @ vue.runtime.esm.js:6578 updateChildren @ vue.runtime.esm.js:6873 patchVnode @ vue.runtime.esm.js:6966 updateChildren @ vue.runtime.esm.js:6840 patchVnode @ vue.runtime.esm.js:6966 patch @ vue.runtime.esm.js:7134 Vue._update @ vue.runtime.esm.js:3824 updateComponent @ vue.runtime.esm.js:3930 Watcher.get @ vue.runtime.esm.js:3501 Watcher.run @ vue.runtime.esm.js:3577 flushSchedulerQueue @ vue.runtime.esm.js:4180 eval @ vue.runtime.esm.js:3198 flushCallbacks @ vue.runtime.esm.js:3120 Promise.then timerFunc @ vue.runtime.esm.js:3145 nextTick @ vue.runtime.esm.js:3210 queueWatcher @ vue.runtime.esm.js:4266 Watcher.update @ vue.runtime.esm.js:3568 Dep.notify @ vue.runtime.esm.js:790 reactiveSetter @ vue.runtime.esm.js:1021 eval @ vue-router.esm.js:3013 eval @ vue-router.esm.js:3012 updateRoute @ vue-router.esm.js:2422 eval @ vue-router.esm.js:2271 eval @ vue-router.esm.js:2410 step @ vue-router.esm.js:2092 step @ vue-router.esm.js:2099 step @ vue-router.esm.js:2099 runQueue @ vue-router.esm.js:2103 eval @ vue-router.esm.js:2405 step @ vue-router.esm.js:2092 eval @ vue-router.esm.js:2096 eval @ vue-router.esm.js:2392 eval @ vue-router.esm.js:2135 eval @ vue-router.esm.js:2211 Promise.then eval @ vue-router.esm.js:2158 eval @ vue-router.esm.js:2179 eval @ vue-router.esm.js:2179 flatMapComponents @ vue-router.esm.js:2178 eval @ vue-router.esm.js:2114 iterator @ vue-router.esm.js:2370 step @ vue-router.esm.js:2095 step @ vue-router.esm.js:2099 step @ vue-router.esm.js:2099 runQueue @ vue-router.esm.js:2103 confirmTransition @ vue-router.esm.js:2400 transitionTo @ vue-router.esm.js:2268 init @ vue-router.esm.js:3004 beforeCreate @ vue-router.esm.js:1306 invokeWithErrorHandling @ vue.runtime.esm.js:3072 callHook$1 @ vue.runtime.esm.js:4087 Vue._init @ vue.runtime.esm.js:5745 Vue @ vue.runtime.esm.js:5818 eval @ main.js:26 ./src/main.js @ app.js:1304 __webpack_require__ @ app.js:854 fn @ app.js:151 1 @ app.js:1377 __webpack_require__ @ app.js:854 checkDeferredModules @ app.js:46 (anonymous) @ app.js:994 (anonymous) @ app.js:997 main.js:26 [Violation] Added non-passive event listener to a scroll-blocking 'mousewheel' event. Consider marking event handler as 'passive' to make the page more responsive. See https://www.chromestatus.com/feature/5745543795965952 addEventListener @ event.js:201 mountSingleDOMEventListener @ HandlerProxy.js:412 eval @ HandlerProxy.js:359 each @ util.js:300 mountLocalDOMEventListeners @ HandlerProxy.js:358 HandlerDomProxy @ HandlerProxy.js:486 ZRender @ zrender.js:138 init @ zrender.js:49 ECharts @ echarts.js:206 init @ echarts.js:1928 mounted @ cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/components/page2/pieChart03.vue?vue&type=script&lang=js:21 invokeWithErrorHandling @ vue.runtime.esm.js:3072 callHook$1 @ vue.runtime.esm.js:4087 insert @ vue.runtime.esm.js:4480 invokeInsertHook @ vue.runtime.esm.js:6999 patch @ vue.runtime.esm.js:7213 Vue._update @ vue.runtime.esm.js:3824 updateComponent @ vue.runtime.esm.js:3930 Watcher.get @ vue.runtime.esm.js:3501 Watcher.run @ vue.runtime.esm.js:3577 flushSchedulerQueue @ vue.runtime.esm.js:4180 eval @ vue.runtime.esm.js:3198 flushCallbacks @ vue.runtime.esm.js:3120 Promise.then timerFunc @ vue.runtime.esm.js:3145 nextTick @ vue.runtime.esm.js:3210 queueWatcher @ vue.runtime.esm.js:4266 Watcher.update @ vue.runtime.esm.js:3568 Vue.$forceUpdate @ vue.runtime.esm.js:3849 forceRender_1 @ vue.runtime.esm.js:2816 eval @ vue.runtime.esm.js:2836 eval @ vue.runtime.esm.js:370 Promise.then resolveAsyncComponent @ vue.runtime.esm.js:2856 createComponent @ vue.runtime.esm.js:4531 _createElement @ vue.runtime.esm.js:2971 createElement$1 @ vue.runtime.esm.js:2921 vm._c @ vue.runtime.esm.js:2694 render @ cjs.js?{"cacheDirectory":"node_modules/.cache/vue-loader","cacheIdentifier":"3bd8c2c6-vue-loader-template"}!./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/mainPage.vue?vue&type=template&id=0be33bfc&scoped=true:109 Vue._render @ vue.runtime.esm.js:2739 updateComponent @ vue.runtime.esm.js:3930 Watcher.get @ vue.runtime.esm.js:3501 Watcher @ vue.runtime.esm.js:3491 mountComponent @ vue.runtime.esm.js:3947 Vue.$mount @ vue.runtime.esm.js:8830 init @ vue.runtime.esm.js:4464 merged @ vue.runtime.esm.js:4618 createComponent @ vue.runtime.esm.js:6624 createElm @ vue.runtime.esm.js:6578 updateChildren @ vue.runtime.esm.js:6873 patchVnode @ vue.runtime.esm.js:6966 updateChildren @ vue.runtime.esm.js:6840 patchVnode @ vue.runtime.esm.js:6966 patch @ vue.runtime.esm.js:7134 Vue._update @ vue.runtime.esm.js:3824 updateComponent @ vue.runtime.esm.js:3930 Watcher.get @ vue.runtime.esm.js:3501 Watcher.run @ vue.runtime.esm.js:3577 flushSchedulerQueue @ vue.runtime.esm.js:4180 eval @ vue.runtime.esm.js:3198 flushCallbacks @ vue.runtime.esm.js:3120 Promise.then timerFunc @ vue.runtime.esm.js:3145 nextTick @ vue.runtime.esm.js:3210 queueWatcher @ vue.runtime.esm.js:4266 Watcher.update @ vue.runtime.esm.js:3568 Dep.notify @ vue.runtime.esm.js:790 reactiveSetter @ vue.runtime.esm.js:1021 eval @ vue-router.esm.js:3013 eval @ vue-router.esm.js:3012 updateRoute @ vue-router.esm.js:2422 eval @ vue-router.esm.js:2271 eval @ vue-router.esm.js:2410 step @ vue-router.esm.js:2092 step @ vue-router.esm.js:2099 step @ vue-router.esm.js:2099 runQueue @ vue-router.esm.js:2103 eval @ vue-router.esm.js:2405 step @ vue-router.esm.js:2092 eval @ vue-router.esm.js:2096 eval @ vue-router.esm.js:2392 eval @ vue-router.esm.js:2135 eval @ vue-router.esm.js:2211 Promise.then eval @ vue-router.esm.js:2158 eval @ vue-router.esm.js:2179 eval @ vue-router.esm.js:2179 flatMapComponents @ vue-router.esm.js:2178 eval @ vue-router.esm.js:2114 iterator @ vue-router.esm.js:2370 step @ vue-router.esm.js:2095 step @ vue-router.esm.js:2099 step @ vue-router.esm.js:2099 runQueue @ vue-router.esm.js:2103 confirmTransition @ vue-router.esm.js:2400 transitionTo @ vue-router.esm.js:2268 init @ vue-router.esm.js:3004 beforeCreate @ vue-router.esm.js:1306 invokeWithErrorHandling @ vue.runtime.esm.js:3072 callHook$1 @ vue.runtime.esm.js:4087 Vue._init @ vue.runtime.esm.js:5745 Vue @ vue.runtime.esm.js:5818 eval @ main.js:26 ./src/main.js @ app.js:1304 __webpack_require__ @ app.js:854 fn @ app.js:151 1 @ app.js:1377 __webpack_require__ @ app.js:854 checkDeferredModules @ app.js:46 (anonymous) @ app.js:994 (anonymous) @ app.js:997 cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/components/areaChart.vue?vue&type=script&lang=js:22 报警信息此时: {__ob__: Observer} cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/components/page2/barChart.vue?vue&type=script&lang=js:22 报警信息此时: {__ob__: Observer} main.js:26 加载 3D 模型失败: SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON at JSON.parse (<anonymous>) at GLTFLoader.parse (GLTFLoader.js:387:17) at Object.eval [as onLoad] (GLTFLoader.js:249:11) at eval (three.core.js:44796:38) eval @ cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/mainPage.vue?vue&type=script&lang=js:132 _onError @ GLTFLoader.js:225 eval @ GLTFLoader.js:259 eval @ three.core.js:44796 Promise.then load @ three.core.js:44784 load @ GLTFLoader.js:245 initThreeModel @ cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/mainPage.vue?vue&type=script&lang=js:121 eval @ cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/views/mainPage.vue?vue&type=script&lang=js:153 eval @ vue.runtime.esm.js:3198 flushCallbacks @ vue.runtime.esm.js:3120 Promise.then timerFunc @ vue.runtime.esm.js:3145 nextTick @ vue.runtime.esm.js:3210 Vue.$nextTick @ vue.runtime.esm.js:2718 mounted @ iview.js:20290 invokeWithErrorHandling @ vue.runtime.esm.js:3072 callHook$1 @ vue.runtime.esm.js:4087 insert @ vue.runtime.esm.js:4480 invokeInsertHook @ vue.runtime.esm.js:6999 patch @ vue.runtime.esm.js:7213 Vue._update @ vue.runtime.esm.js:3824 updateComponent @ vue.runtime.esm.js:3930 Watcher.get @ vue.runtime.esm.js:3501 Watcher.run @ vue.runtime.esm.js:3577 flushSchedulerQueue @ vue.runtime.esm.js:4180 eval @ vue.runtime.esm.js:3198 flushCallbacks @ vue.runtime.esm.js:3120 Promise.then timerFunc @ vue.runtime.esm.js:3145 nextTick @ vue.runtime.esm.js:3210 queueWatcher @ vue.runtime.esm.js:4266 Watcher.update @ vue.runtime.esm.js:3568 Dep.notify @ vue.runtime.esm.js:790 reactiveSetter @ vue.runtime.esm.js:1021 eval @ vue-router.esm.js:3013 eval @ vue-router.esm.js:3012 updateRoute @ vue-router.esm.js:2422 eval @ vue-router.esm.js:2271 eval @ vue-router.esm.js:2410 step @ vue-router.esm.js:2092 step @ vue-router.esm.js:2099 step @ vue-router.esm.js:2099 runQueue @ vue-router.esm.js:2103 eval @ vue-router.esm.js:2405 step @ vue-router.esm.js:2092 eval @ vue-router.esm.js:2096 eval @ vue-router.esm.js:2392 eval @ vue-router.esm.js:2135 eval @ vue-router.esm.js:2211 Promise.then eval @ vue-router.esm.js:2158 eval @ vue-router.esm.js:2179 eval @ vue-router.esm.js:2179 flatMapComponents @ vue-router.esm.js:2178 eval @ vue-router.esm.js:2114 iterator @ vue-router.esm.js:2370 step @ vue-router.esm.js:2095 step @ vue-router.esm.js:2099 step @ vue-router.esm.js:2099 runQueue @ vue-router.esm.js:2103 confirmTransition @ vue-router.esm.js:2400 transitionTo @ vue-router.esm.js:2268 init @ vue-router.esm.js:3004 beforeCreate @ vue-router.esm.js:1306 invokeWithErrorHandling @ vue.runtime.esm.js:3072 callHook$1 @ vue.runtime.esm.js:4087 Vue._init @ vue.runtime.esm.js:5745 Vue @ vue.runtime.esm.js:5818 eval @ main.js:26 ./src/main.js @ app.js:1304 __webpack_require__ @ app.js:854 fn @ app.js:151 1 @ app.js:1377 __webpack_require__ @ app.js:854 checkDeferredModules @ app.js:46 (anonymous) @ app.js:994 (anonymous) @ app.js:997
11-11
<think>我们面临三个问题: 1. 'HMR等待WDS更新信号'的日志输出(属于信息性日志,通常可以忽略,但用户希望关闭) 2. 添加非被动事件监听器到滚动阻塞事件(控制台警告) 3. 加载3D模型失败,错误为:SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON 下面逐一解决: ### 一、关闭[HMR]日志(参考之前回答和引用[1][2][3]) 问题:控制台输出`[HMR] Waiting for update signal from WDS...`,这是webpack热更新的日志,虽然不影响功能,但用户可能觉得干扰。 解决方案: 1. **修改vue.config.js配置(推荐)**:通过配置devServer.client.logging来减少日志级别。 ```javascript // vue.config.js module.exports = { devServer: { client: { logging: 'error', // 只输出错误日志 overlay: false, // 可选:关闭浏览器全屏覆盖错误 } } } ``` 这样设置后,只有错误才会在控制台显示,信息级别的HMR日志将不再显示。 2. **修改webpack的log.js(临时,不推荐)**:如之前所述,修改node_modules/webpack/hot/log.js,注释掉console.log。但这种方法在重新安装node_modules后会失效。 3. **使用webpack插件(高级)**:可以编写一个webpack插件来过滤日志,但相对复杂,不推荐。 ### 、非被动事件监听器警告(滚动阻塞事件) 问题:在控制台看到类似警告: ``` [Violation] Added non-passive event listener to a scroll-blocking 'touchstart' event. Consider marking event handler as 'passive' to make the page more responsive. ``` 原因:事件监听器阻塞了滚动,导致页面响应延迟。现代浏览器推荐将事件监听器标记为`passive`以提升滚动性能。 解决方案: 1. **在事件监听器中添加passive选项**: 例如,使用addEventListener时: ```javascript element.addEventListener('touchstart', handler, { passive: true }); ``` 但注意:如果事件处理函数中调用了`event.preventDefault()`,则不能使用passive,因为passive会忽略preventDefault。 2. **使用第三方库修复**: 如果是第三方库(如Vue的某些UI组件)引起的,可以尝试使用`default-passive-events`库。 安装: ```bash npm install default-passive-events --save ``` 在main.js中引入: ```javascript import 'default-passive-events'; ``` 3. **框架层面的修复(Vue)**: 在Vue中,我们可以通过全局配置来为特定的事件添加passive选项(Vue2.4+支持): ```javascript Vue.config.productionTip = false; Vue.config.performance = true; // 为特定事件添加passive Vue.config.optionMergeStrategies.domEvent = (parent, child, vm) => { if (!child) return parent if (!parent) return child return function mergedHandler(...args) { const [event] = args if (event.type === 'touchstart' || event.type === 'touchmove') { event.passive = true } parent.call(this, ...args) child.call(this, ...args) } } ``` 但这种方法较为复杂,且可能不适用于所有情况。更推荐使用种方法。 ### 三、加载3D模型失败(SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON) 问题描述:在加载3D模型(如GLB、GLTF)时,控制台报错:`Unexpected token '<'`,并且错误内容显示接收到了HTML文档(以`<!DOCTYPE html>`开头)。这通常意味着请求的模型文件路径错误,服务器返回了一个HTML页面(通常是404页面)而不是模型文件。 分析:根据引用[2][3][4][5]可知,这种错误通常与资源路径不正确有关,特别是在Vue项目使用了history路由模式时,相对路径和绝对路径的配置差异可能导致此类问题。 解决方案: 1. **检查模型文件路径**: - 确保在代码中引用的模型路径是正确的。如果是放在public目录下,则使用绝对路径: ```javascript // 正确:public目录下的文件使用绝对路径(以/开头) loader.load('/models/your-model.glb', ...) ``` - 如果是放在src/assets目录下,则使用require或import相对路径: ```javascript // 使用require loader.load(require('@/assets/models/your-model.glb'), ...) ``` 或者(如果loader支持URL对象): ```javascript import modelUrl from '@/assets/models/your-model.glb'; loader.load(modelUrl, ...) ``` 2. **配置publicPath(重要)**: 在vue.config.js中,设置正确的publicPath。根据引用[2][5],如果使用history模式,publicPath必须为绝对路径`'/'`而不能是相对路径`'./'`。 ```javascript // vue.config.js module.exports = { publicPath: '/', // 生产环境构建时使用的publicPath // ...其他配置 } ``` 3. **服务器配置**: - 确保服务器(如Nginx, Apache, IIS)正确配置了静态资源目录,并将模型文件的MIME类型设置为正确的类型(如.glb为model/gltf-binary)。 - 对于使用history模式的路由,需要配置服务器支持(因为直接访问深层路径可能会返回index.html,导致模型请求被路由拦截而返回HTML)。引用[5]提到可能需要URL重写。 以Nginx为例,配置一个重写规则: ```nginx location / { try_files $uri $uri/ /index.html; } ``` 但是注意,这个规则会导致所有不存在的路径都返回index.html,因此我们需要确保模型文件的路径是真实存在的。或者,我们可以将模型文件放在不会被路由拦截的路径下(如static目录)。 4. **检查请求路径(Network面板)**: 在浏览器开发者工具的Network面板中,查看模型文件的请求: - 请求URL是否正确? - 响应状态码是200还是404/其他? - 响应内容是否为HTML(说明路径错误)? 5. **使用正确的Loader和文件类型**: 确保你使用加载器(如GLTFLoader)支持该文件类型。同时,文件本身应该是有效的。 6. **处理跨域问题**: 如果模型文件在另一个域上,确保服务器设置了CORS头。 7. **避免路由拦截**: 如果模型文件请求被路由拦截(比如在Vue Router中定义了类似`'/model/:id'`的路由),则应该调整路由,或者将模型文件放在public目录以避免被路由拦截。 总结错误原因:请求模型文件时,由于路径错误或服务器配置问题,返回了HTML页面(比如404页面),而加载器期望的是进制或JSON格式的模型文件,解析HTML时遇到`<`符号就报错了。 ### 补充:三个问题的关联性 这三个问题都是开发中常见的,但彼此独立。可以分别按照上述方法解决。 §§相关问题§§ 1. Vue项目中如何正确配置静态资源路径(publicPath)? 2. 如何处理浏览器中非被动事件监听器的警告? 3. 服务器部署Vue应用(使用history模式)后出现资源加载错误如何解决? 4.Vue项目中如何正确引入和加载Three.js的3D模型? 5. 如何使用Nginx配置Vue项目的history模式? </think>以下是针对 Vue 项目中三个关键问题的完整解决方案: ### 一、解决 HMR 等待 WDS 更新信号日志 #### 问题本质 Webpack 热更新机制的正常提示信息,可通过配置关闭: ```javascript // vue.config.js module.exports = { devServer: { client: { logging: "error", // 仅显示错误日志 overlay: false // 关闭全屏错误覆盖 } } } ``` > **效果**:控制台将只显示关键错误信息,HMR 状态提示被过滤[^1][^2] #### 替代方案(临时) ```javascript // main.js if (process.env.NODE_ENV === "development") { const originalLog = console.log; console.log = (...args) => { if (!args[0].includes("[HMR]")) originalLog(...args); }; } ``` --- ### 、修复滚动阻塞事件警告 #### 问题原因 触发滚动的事件监听器(如 `touchstart`)未标记为被动模式: ``` [Violation] Added non-passive event listener to a scroll-blocking 'touchstart' event... ``` #### 解决方案 1. **原生事件监听修复**: ```javascript // 添加 passive 选项 element.addEventListener("touchstart", handler, { passive: true }); ``` 2. **Vue 事件指令修复**: ```html <!-- 模板中使用 --> <div @touchstart.passive="handleTouch">...</div> ``` 3. **全局修复(推荐)**: ```bash npm install default-passive-events ``` ```javascript // main.js import "default-passive-events"; ``` > **原理**:自动为滚动相关事件添加 `passive: true` 属性[^3] --- ### 三、解决 3D 模型加载失败(SyntaxError: Unexpected token '<') #### 错误本质 ```json 加载 3D 模型失败: SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON ``` 表明模型加载请求返回了 HTML 文档(通常是 404 页面)而非模型文件 #### 🔍 根本原因排查(参考引用[2][3][5]) 1. **路径配置错误**: - 开发环境有效但生产环境失效 - 错误使用相对路径 `./model.glb`(应使用绝对路径 `/model.glb`) 2. **路由模式冲突**: ```javascript // router/index.js export default new Router({ mode: "history", // 导致路径解析错误 // 改为: mode: "hash", // 修复方案[^4] }) ``` 3. **服务器配置问题**: - 未配置 MIME 类型 - Nginx/Apache 未正确处理静态资源请求 #### ✅ 完整解决方案 1. **路径修正**: ```javascript // 错误 ❌ loader.load("./assets/model.glb") // 正确 ✅(public目录) loader.load("/models/model.glb") // 正确 ✅(src/assets目录) loader.load(require("@/assets/model.glb")) ``` 2. **vue.config.js 配置**: ```javascript module.exports = { publicPath: "/", // 必须为根路径[^2][^5] chainWebpack: config => { config.module .rule("glb") .test(/\.glb$/) .use("file-loader") .loader("file-loader") .options({ name: "static/models/[name].[hash:8].[ext]" }) } } ``` 3. **服务器配置示例(Nginx)**: ```nginx location / { try_files $uri $uri/ /index.html; # history模式必需 } location /static/models { add_header Access-Control-Allow-Origin *; types { model/gltf-binary glb; application/octet-stream glb; } } ``` 4. **模型加载最佳实践**: ```javascript new GLTFLoader().load( process.env.NODE_ENV === "production" ? "/static/models/scene.glb" : require("@/dev_models/scene.glb"), gltf => scene.add(gltf.scene), null, error => console.error("模型加载失败:", error) // 捕获具体错误 ); ``` --- ### 问题关联分析 | 问题类型 | 根本原因 | 解决方案 | |-----------------------|----------------------------------|----------------------------------| | HMR 日志 | Webpack 开发服务器配置 | 调整 logging 级别 | | 非被动事件监听器 | 滚动事件未标记 passive | 全局注入 passive 事件处理 | | 模型加载失败 | 路径解析错误/路由模式冲突 | 绝对路径 + hash路由 + 正确配置 | > **关键检查点**:浏览器开发者工具的 Network 选项卡查看模型请求: > 1. 状态码是否 200(非 404/304) > 2. 响应内容是否为模型进制数据(非 HTML) > 3. Content-Type 是否为 `model/gltf-binary`
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

香蕉可乐荷包蛋

努力写有用的code

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值