vue3+vite+ts+tailwindcss:threejs的编译环境(gsap+dat.ui)

vue3+vite+ts+tailwindcss:threejs的编译环境

安装

npm install --save three
npm i --save-dev @types/three

插件

安装gsap

GSAP (GreenSock Animation Platform) 是一个用于创建高性能动画的JavaScript库。它是一个功能强大、灵活且易于使用的动画引擎,被广泛应用于网页设计和开发中。

GSAP 提供了一套丰富的动画工具和特效,可以实现各种动画效果,包括缓动动画、时间轴动画、滚动动画等等。它支持对元素属性(如位置、大小、颜色等)进行动态变化,并且能够精确控制动画的持续时间、延迟和回调函数。

GSAP 具有很高的性能和流畅的动画效果,它通过优化动画的渲染方式,实现在各种设备和浏览器上的平滑动画表现。此外,GSAP 还提供了一套直观的 API 风格,使得动画的创建和管理变得简单和可靠。

npm install --save gsap
npm i --save-dev @types/gsap

发现官方说

gsap provides its own type definitions, so you don’t need @types/gsap installed!

就不用安装@types/gsap了

使用

import { gsap } from 'gsap'

测试

安装dat.ui(debug插件)

dat.ui提供了内联式的debug,是threejs进行debug的轻量级控制器库

npm install --save dat.gui
npm i --save-dev @types/dat.gui

然后就可以在相应的vue文件中引入

import * as dat from 'dat.gui';
//debug
const gui = new dat.GUI();

如果出现编译器报错找不到的,采用以下方法:

在根目录添加dat.gui.d.ts:(这步只是在告诉编译器已经引入),找到node_modules/@types/dat.gui里的index.d.ts文件,复制内容到dat.gui.d.ts里面

export as namespace dat;

export interface GUIParams {
    /**
     * Handles GUI's element placement for you.
     * @default true
     */
    autoPlace?: boolean | undefined;
    /**
     * If true, starts closed.
     * @default false
     */
    closed?: boolean | undefined;
    /**
     * If true, close/open button shows on top of the GUI.
     * @default false
     */
    closeOnTop?: boolean | undefined;
    /**
     * If true, GUI is closed by the "h" keypress.
     * @default false
     */
    hideable?: boolean | undefined;
    /**
     * JSON object representing the saved state of this GUI.
     */
    load?: any;
    /**
     * The name of this GUI.
     */
    name?: string | undefined;
    /**
     * The identifier for a set of saved values.
     */
    preset?: string | undefined;
    /**
     * The width of GUI element.
     */
    width?: number | undefined;
}

export class GUI {
    static CLASS_AUTO_PLACE: string;
    static CLASS_AUTO_PLACE_CONTAINER: string;
    static CLASS_MAIN: string;
    static CLASS_CONTROLLER_ROW: string;
    static CLASS_TOO_TALL: string;
    static CLASS_CLOSED: string;
    static CLASS_CLOSE_BUTTON: string;
    static CLASS_CLOSE_TOP: string;
    static CLASS_CLOSE_BOTTOM: string;
    static CLASS_DRAG: string;
    static DEFAULT_WIDTH: number;
    static TEXT_CLOSED: string;
    static TEXT_OPEN: string;

    constructor(option?: GUIParams);

    __controllers: GUIController[];
    __folders: { [folderName: string]: GUI };
    domElement: HTMLElement;

    add<T extends object>(
        target: T,
        propName: keyof T,
        min?: number,
        max?: number,
        step?: number,
    ): GUIController;
    add<T extends object>(target: T, propName: keyof T, status: boolean): GUIController;
    add<T extends object>(target: T, propName: keyof T, items: string[]): GUIController;
    add<T extends object>(target: T, propName: keyof T, items: number[]): GUIController;
    add<T extends object>(target: T, propName: keyof T, items: Object): GUIController;

    addColor(target: Object, propName: string): GUIController;

    remove(controller: GUIController): void;
    destroy(): void;

    addFolder(propName: string): GUI;
    removeFolder(subFolder: GUI): void;

    open(): void;
    close(): void;
    hide(): void;
    show(): void;

    remember(target: Object, ...additionalTargets: Object[]): void;
    getRoot(): GUI;

    getSaveObject(): Object;
    save(): void;
    saveAs(presetName: string): void;
    revert(gui: GUI): void;

    listen(controller: GUIController): void;
    updateDisplay(): void;

    // gui properties in dat/gui/GUI.js
    readonly parent: GUI;
    readonly scrollable: boolean;
    readonly autoPlace: boolean;
    preset: string;
    width: number;
    name: string;
    closed: boolean;
    readonly load: Object;
    useLocalStorage: boolean;
}

export class GUIController<T extends object = object> {
    domElement: HTMLElement;
    object: Object;
    property: string;

    constructor(object: T, property: keyof T);

    options(option: any): GUIController;
    name(name: string): GUIController;

    listen(): GUIController;
    remove(): GUIController;

    onChange(fnc: (value?: any) => void): GUIController;
    onFinishChange(fnc: (value?: any) => void): GUIController;

    setValue(value: any): GUIController;
    getValue(): any;

    updateDisplay(): GUIController;
    isModified(): boolean;

    // NumberController
    min(n: number): GUIController;
    max(n: number): GUIController;
    step(n: number): GUIController;

    // FunctionController
    fire(): GUIController;
}

使用

gui.add(…)添加元素
给个例子
src/utils/object/BoxUtil.ts

import * as THREE from "three";

export interface dubBoxConfig {
    color: number | undefined,
    wireframe: boolean | undefined,
    width: number,
    height: number,
    depth: number,
    widthSegments: number | undefined,
    heightSegments: number | undefined,
    depthSegments: number | undefined
}
export function getBox(config: dubBoxConfig): Promise<THREE.Mesh<THREE.BoxGeometry, THREE.MeshBasicMaterial>> {
    return new Promise<THREE.Mesh<THREE.BoxGeometry, THREE.MeshBasicMaterial>>(async (resolve, reject) => {
        const geometry = new THREE.BoxGeometry(config.width, config.height, config.width, config.widthSegments, config.heightSegments, config.depthSegments);
        const material = new THREE.MeshBasicMaterial({ color: config.color, wireframe: config.wireframe });
        const cube = new THREE.Mesh(geometry, material);
        resolve(cube)
    })
}

src/components/DubDebug.vue
注意我用的async setup( ) ,需要Suspense包裹

    <Suspense>
        <RouterView></RouterView>
    </Suspense>
<template>
    <div class="w-full h-screen">
        <div ref="debug"></div>
    </div>
    <button @click="hideDebug()">debug</button>
</template>
<script lang="ts">
import { ref, onMounted, watch, reactive, onBeforeUnmount } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"
import { onBeforeRouteLeave } from "vue-router";
import { getBox, dubBoxConfig } from '@/utils/object/BoxUtil'
import { gsap } from 'gsap'
import * as dat from 'dat.gui';
const debug = ref<HTMLDivElement | null>(null);

export default {
    async setup() {
    	const gui = new dat.GUI({ closed: true, width: 400 });
        onBeforeRouteLeave(() => {
            window.removeEventListener('resize', updateSize)
            window.removeEventListener('dblclick', HandleDblclick)
        })
        onMounted(() => {
            if (debug.value) {
                if (!renderer.capabilities.isWebGL2) {
                    console.warn("WebGL is not available:", false);
                    return;
                }
                renderer.setSize(window.innerWidth, window.innerHeight);
                renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
                debug.value.appendChild(renderer.domElement);
                window.addEventListener('resize', updateSize)
                window.addEventListener('dblclick', HandleDblclick)
                scene.add(cube)
                scene.add(cube)
                camera.position.z = 5;
                animate();
            }
        });
        onBeforeUnmount(() => {
            window.removeEventListener('resize', updateSize)
            window.removeEventListener('dblclick', HandleDblclick)
        });

        //size
        const size = reactive({
            width: window.innerWidth,
            height: window.innerHeight
        })
        function updateSize() {
            size.width = window.innerWidth
            size.height = window.innerHeight
        }
        watch(size, async (newSize, oldSize) => {
            //使缩放无视角偏移
            camera.aspect = size.width / size.height
            camera.updateProjectionMatrix()
            //使宽度自适应
            renderer.setSize(window.innerWidth, window.innerHeight);
        })

        //camera
        const camera = new THREE.PerspectiveCamera(
            75,
            size.width / size.height,
            0.1,
            1000
        );

        //scene
        const scene = new THREE.Scene();

        //renderer
        const renderer = new THREE.WebGLRenderer();

        //object
        const boxConfig = reactive({
            color: 0x00ff00,
            wireframe: false,
            width: 1,
            height: 1,
            depth: 1,
            widthSegments: 2,
            heightSegments: 2,
            depthSegments: 2
        }) as dubBoxConfig
        watch(() => boxConfig.color, (newColor) => {
            if (newColor !== undefined) {
                cube.material.color.set(newColor)
            }
        });
        watch(() => boxConfig.wireframe, (newWireframe) => {
            if (newWireframe != undefined) {
                cube.material.wireframe = newWireframe;
            }
        });
        const cube = await getBox(boxConfig);
        //controls
        const controls = new OrbitControls(camera as any, renderer.domElement)

        //animate
        const animate_action = function () {
            cube.rotation.x += 0.01;
            cube.rotation.y += 0.01;
        }
        const animate_action_spin = function () {
            gsap.to(cube.rotation, { duration: 1, y: cube.rotation.y + 10 })
        }
        const animate = function () {
            requestAnimationFrame(animate);
            animate_action()
            renderer.render(scene, camera);
        };

        //dblclick
        const HandleDblclick = function () {
            //@ts-ignore
            const fullscreenElement = document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement;
            if (!fullscreenElement) {
                if (debug.value) {
                    fullscreen(debug.value)
                }
            } else {
                exitFullscreen()
            }
        }
        function fullscreen(element: HTMLDivElement) {
            if (element.requestFullscreen) {
                element.requestFullscreen();
                //@ts-ignore
            } else if (element.mozRequestFullScreen) { // 兼容 Firefox
                //@ts-ignore
                element.mozRequestFullScreen();
                //@ts-ignore
            } else if (element.webkitRequestFullscreen) { // 兼容 Chrome, Safari 和 Opera
                //@ts-ignore
                element.webkitRequestFullscreen();
                //@ts-ignore
            } else if (element.msRequestFullscreen) { // 兼容 IE/Edge
                //@ts-ignore
                element.msRequestFullscreen();
            }
        }
        function exitFullscreen() {
            if (document.exitFullscreen) {
                document.exitFullscreen();
                //@ts-ignore
            } else if (document.mozCancelFullScreen) { // 兼容 Firefox
                //@ts-ignore
                document.mozCancelFullScreen();
                //@ts-ignore
            } else if (document.webkitExitFullscreen) { // 兼容 Chrome, Safari 和 Opera(已弃用)
                //@ts-ignore
                document.webkitExitFullscreen();
                //@ts-ignore
            } else if (document.msExitFullscreen) { // 兼容 IE/Edge
                //@ts-ignore
                document.msExitFullscreen();
            }
        }
        //debug
        let cubeFolder = gui.addFolder('正方体');
        cubeFolder.add(cube.position, 'x', -3, 3, 0.01)
        cubeFolder.add(cube.position, 'y').min(-3).max(3).step(0.01)
        cubeFolder.add(cube.position, 'z')
            .min(-3)
            .max(3)
            .step(0.01)
            .name('正方体z坐标')
        cubeFolder
            .add(cube, 'visible')
            .name('显示元素')
        cubeFolder
            .add({ wireframe: boxConfig.wireframe }, 'wireframe')
            .name('显示骨架')
            .onChange((wireframe) => {
                boxConfig.wireframe = wireframe
            })
        cubeFolder
            .addColor({ color: boxConfig.color }, "color")
            .onChange((color) => {
                boxConfig.color = color;
            });
        cubeFolder
            .add({ spin: animate_action_spin }, 'spin')
            .name("旋转")
        const hideDebug = function () {
            if (gui.domElement.style.display !== 'none') {
                gui.domElement.style.display = 'none'; // 隐藏 GUI 元素
            } else {
                gui.domElement.style.display = 'block'; // 显示 GUI 元素
            }
        }
        return { debug, boxConfig, hideDebug };
    }
}
</script>
<style scoped></style>

最终效果
页面效果

最后在页面debug可以点击H键隐藏下划栏
dat.ui参考:https://jsfiddle.net/ikatyang/182ztwao

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
你好!对于使用Vue 3、TypeScript和Vite来实现一个看房自由的应用,可以结合Three.js这个D图形库来实现。下面是一个简单的步骤指南: 1. 首先,确保你已经安装了Node.js和npm。 2. 创建一个新的Vue项目,可以使用Vue CLI来快速搭建一个基本的项目结构。 ```bash npm install -g @vue/cli vue create my-project ``` 3. 在Vue项目中安装Vite作为开发服务器。 ```bash cd my-project npm install -D create-vite npx create-vite ``` 4. 安装Three.js库和相关依赖。 ```bash npm install three ``` 5. 在Vue组件中引入Three.js库,并开始编写代码来实现看房自由功能。 ```typescript <template> <div ref="container"></div> </template> <script lang="ts"> import { ref, onMounted } from 'vue'; import * as THREE from 'three'; export default { setup() { const container = ref(null); onMounted(() => { // 创建场景、相机和渲染器 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer(); // 设置渲染器的大小并将其添加到DOM中 renderer.setSize(window.innerWidth, window.innerHeight); container.value.appendChild(renderer.domElement); // 创建一个立方体并将其添加到场景中 const geometry = new THREE.BoxGeometry(); const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube); // 设置相机的位置 camera.position.z = 5; // 动画循环 const animate = function () { requestAnimationFrame(animate); // 使立方体旋转起来 cube.rotation.x += 0.01; cube.rotation.y += 0.01; // 渲染场景和相机 renderer.render(scene, camera); }; animate(); }); return { container, }; }, }; </script> <style> #container { width: 100%; height: 100%; } </style> ``` 这只是一个简单的示例,你可以根据自己的需求来构建更复杂的场景和交互逻辑。希望对你有所帮助!如有任何疑问,欢迎继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

duringbug

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值