Proxy Interference with Three.js Rendering
When using Vue 3's reactive() to wrap Three.js objects like scene, camera, renderer, and controls, a runtime error occurs during rendering:
three.module.js:24471 Uncaught TypeError:
'get' on proxy: property 'modelViewMatrix' is a read-only and
non-configurable data property on the proxy target but the proxy did not
return its actual value (expected '#<Matrix4>' but got '[object Object]')
at renderObject (three.module.js:24471)
at renderObjects (three.module.js:24458)
at Proxy.WebGLRenderer.render (three.module.js:24258)
at animate (Component.vue:192)
Root Cause: Vue 3 Proxy vs. Three.js Internal Properties
Vue 3's reactivity system relies on JavaScript Proxies to intercept property access. Three.js internally defines numerous read-only properties (e.g., modelViewMatrix, normalMatrix) using Object.defineProperty with configurable: false. When a Proxy intercepts access to such non-configurable properties and returns a value different from the original, JavaScript throws a TypeError.
This behavior is demonstrated by the following example:
const handler = {
get(target, prop) {
return 'intercepted value';
}
};
const originalObj = {};
Object.defineProperty(originalObj, 'readOnlyProp', {
configurable: false,
value: 'original value'
});
const proxyObj = new Proxy(originalObj, handler);
console.log(proxyObj.readOnlyProp); // Throws TypeError
Recommended Implementation Patterns
-
Avoid Reactivity for Core Three.js Objects: Declare fundamental Three.js objects (scene, camera, renderer) using standard variablse instead of Vue's reactive wrappers.
// Use standard variables for core objects let scene, camera, renderer; function initThree() { scene = new THREE.Scene(); camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); renderer = new THREE.WebGLRenderer(); } -
Selective Reactivity with Raw Conversion: For Vue-controlled objects that need to be added to the scene, use
ref()and convert to raw objects when interacting with Three.js.import { ref, toRaw } from 'vue'; const meshRef = ref(new THREE.Mesh(geometry, material)); // Add to scene using raw object scene.add(toRaw(meshRef.value)); // Render using raw objects renderer.render(toRaw(scene), camera);
This approach maintains Vue's reactivity for application state while preventing proxy interference with Three.js's internal rendering logic.