代码

<script setup lang="ts">
import router from '@/router';
import * as THREE from 'three';
import { ref, type Ref, nextTick } from 'vue';
import { onMounted } from 'vue';
const props = defineProps({
    title: String,
    OnThreeLoaded: Function,
    Tick: Function,
})

const handleClick = () => {
    router.back();
}

const threeContainer: Ref<HTMLElement | null> = ref(null);

let renderer: THREE.WebGLRenderer | null = null;

let camera: THREE.PerspectiveCamera | null = null;

let scene: THREE.Scene | null = null;

let cube: THREE.Mesh | null = null;

onMounted(() => {

    nextTick(() => {
        if (!threeContainer.value) return

        // 初始化场景
        scene = new THREE.Scene()

        // 初始化相机
        camera = new THREE.PerspectiveCamera(75, threeContainer.value.offsetWidth / threeContainer.value.offsetHeight, 0.1, 1000)
        camera.position.z = 5
        //添加摄像机
        scene.add(camera);

        // 添加物体
        const geometry = new THREE.BoxGeometry()
        const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
        cube = new THREE.Mesh(geometry, material)
        scene.add(cube)

        // 初始化渲染器
        renderer = new THREE.WebGLRenderer({ antialias: true })
        renderer.setSize(threeContainer.value.offsetWidth, threeContainer.value.offsetHeight)
        //新写法
        renderer.setAnimationLoop(tick)
        threeContainer.value.appendChild(renderer.domElement)

        // 通知父组件
        props.OnThreeLoaded?.(scene, camera, renderer)

        // 启动渲染循环(旧写法)
        // tick(0)
    })


    let previousTime = 0

    const tick = (time: number) => {
        if (renderer&&scene&&camera) {
            const deltaTime = (time - previousTime)

            if (cube)
                cube.rotation.y += 0.001*deltaTime
            //Tick
            props.Tick?.(time, deltaTime);
            renderer.render(scene!, camera!);
            previousTime = time
            //渲染旧写法
            // window.requestAnimationFrame(tick)
        }
    }
})
</script>

模板

<template>
    <div class="layout">
        <div class="iconfont icon-guanbi" @click="handleClick"></div>
        <div class="title">{{ props.title }}</div>
        <div class="three-container" ref="threeContainer">

        </div>
        <slot>

        </slot>
    </div>
</template>

样式

<style scoped>
.three-container {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.layout {
    position: relative;
    width: 100vw;
    height: 100vh;
}

.icon-guanbi {
    font-size: 30px;
    position: absolute;
    top: 10px;
    left: 10px;
    z-index: 1;
}

.icon-guanbi:hover,
.icon-guanbi:focus {
    color: #ff0000;
    font-size: 32px;
}

.icon-guanbi:active {
    color: #2494da;
    font-size: 30px;
}

.title {
    position: absolute;
    top: 10px;
    left: 50px;
    font-size: 20px;
    font-weight: bold;
    color: white;
    z-index: 1;
}
</style>