Vue 集成
@tripo3d/engine/vue 提供两个薄适配:
Canvas:接管 Engine Canvas 的 DOM、start/stop 与最终 dispose;createPluginVue():在构造期 markRaw Engine 初始公共 identity。
它们不把 Core 改造成 Vue runtime,也不提供内置 provide/inject。
Canvas
<script setup lang="ts">
import { createEngine } from '@tripo3d/engine';
import { Canvas, createPluginVue } from '@tripo3d/engine/vue';
import { markRaw, onMounted, shallowRef } from 'vue';
import type { Engine } from '@tripo3d/engine';
const engine = shallowRef<Engine | null>(null);
onMounted(() => {
engine.value = markRaw(createEngine({
plugins: [createPluginVue()],
}));
});
</script>
<template>
<Canvas v-if="engine" :engine="engine">
<div class="hud">普通 Vue overlay</div>
</Canvas>
</template>
<style scoped>
.hud {
pointer-events: auto;
}
</style>Canvas 只接受一个必填 engine prop:
- mounted 时确认该 Engine Canvas 没有其他 DOM parent;
- 把 Canvas 插入内部 root 并调用
engine.start(); - prop identity 更换时先 stop/dispose/detach 旧 Engine,再接纳新 Engine;
- unmount 时自动 stop、dispose 并分离 Canvas。
Canvas 接纳 Engine 后取得最终 dispose authority。不要在父组件的 onBeforeUnmount 再重复销毁。
默认 slot 位于覆盖 Canvas 的 overlay 中;overlay 默认 pointer-events: none,交互控件需局部设置 pointer-events: auto。这样既保留 HUD,又不会让空白 overlay 阻断 Engine Pointer。
createPluginVue
const engine = createEngine({
plugins: [createPluginVue()],
});Plugin markRaw 的初始 identity 包括 Engine、Canvas、renderer、默认 Camera/World/root/Scene、Pipeline、默认 View、Pointer 与 Raycaster。
它不处理之后动态创建的 Entity、Object3D、Camera、Pass 或业务状态。原因是 Plugin 静态且 Core 不提供实例拦截器。
响应式边界
推荐只让 Vue 跟踪 UI 真正需要的少量标量:
const engine = shallowRef(markRaw(createEngine()));
const selected = shallowRef<Entity | null>(null);
const ui = reactive({
loading: false,
selectedName: '',
});
function select(entity: Entity | null) {
selected.value = entity === null ? null : markRaw(entity);
ui.selectedName = entity?.object.name ?? '';
}- Engine/Entity/World/Camera/Material 等外部 identity 用
shallowRef; - 对即将进入 Vue state 的动态 Three.js/Engine 对象显式
markRaw; - 不要用
reactive(engine)或普通ref(threeObject); - Component/System/Script setup 返回值是普通业务 identity,若需要模板响应性,直接在业务 setup 中返回 Vue
ref/reactive,或在组合层维护 UI state。
取消 Vue Plugin 也可以,只要业务始终用 shallowRef/markRaw 管理边界;Plugin 是便利性保护,不是 Core 正确性的前提。
页面结构建议
Page.vue # 只组合 Canvas 与 controls
├── useEngineScene.ts # 创建 Engine、场景与生命周期
├── engine/scene.ts # 纯 Engine/Three Definition
└── SceneControls.vue # props down / emits up,不持有 Engine路由页保持组合层,副作用放 composable,Engine 领域定义保持纯 TypeScript。控制组件只接收可序列化状态并发出用户意图,避免 UI 树到处持有 renderer/scene。
SSR / Nuxt
Engine 模块导入 SSR-safe,但 createEngine() 需要 browser document/WebGL。VitePress/Nuxt 中在 mounted 后构造,并用 ClientOnly 避免服务端渲染 Canvas:
<ClientOnly>
<Canvas v-if="engine" :engine="engine" />
</ClientOnly>若 Canvas 还未接纳前构造流程失败,创建者负责 dispose candidate;一旦 Canvas 接纳,最终销毁由 Canvas 负责。