Struggling to integrate a basic Three.js
scene with the Vuetify v-app
component? You're not alone.
By using vue
, vue-router
, and three-js
without Vuetify, I encountered no issues loading a simple 'Hello World' scene. If you're curious, you can find the code for this successful attempt here: https://github.com/TjalleWired/vue-three-test
I've experimented with a route detector to bypass the v-app
component when loading /viewer
(employing v-if
in the components). However, the outcomes were inconsistent, ranging from overlapping UI elements with the 3D viewer to the 3D viewer disappearing altogether with each reload.
My current project setup involves a Navbar.vue
component, App.vue
, and a Viewer.vue
view. Additionally, I've activated vue-router
which is functioning as expected.
If you're keen on seeing my latest efforts to make Vuetify play nice with Three.js, check out this minimal project here: https://github.com/TjalleWired/three-vuetify/
Viewer.vue
<template>
<div id="container"></div>
</template>
<script>
import * as THREE from "three";
export default {
name: "ThreeTest",
data() {
return {
cube: null,
renderer: null,
scene: null,
camera: null,
};
},
methods: {
init: function () {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(this.renderer.domElement);
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
this.cube = new THREE.Mesh(geometry, material);
this.scene.add(this.cube);
this.camera.position.z = 5;
},
animate: function () {
requestAnimationFrame(this.animate);
this.cube.rotation.x += 0.01;
this.cube.rotation.y += 0.01;
this.renderer.render(this.scene, this.camera);
},
},
mounted() {
this.init();
this.animate();
},
};
</script>
Navbar.vue
<template>
<div>
<v-app-bar app clipped-left flat dark>
<v-toolbar-title>
<span class="first-word font uppercase">stp</span>
<span class="second-word font uppercase">upload</span>
</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-navigation-drawer app clipped flat dark expand-on-hover>
<v-list>
<v-list-item class="px-2">
<v-list-item-avatar>
<v-img src="https://randomuser.me/api/portraits/men/11.jpg"></v-img>
</v-list-item-avatar>
<v-list-item-title>
<span class="username">{{ username }}</span>
</v-list-item-title>
</v-list-item>
<v-list-item v-for="item in navbarlist" :key="item.route" :to="item.route">
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>{{ item.text }}</v-list-item-content>
</v-list-item>
</v-list>
<template v-slot:append>
<v-list>
<v-list-item @click="logout()">
<v-list-item-icon>
<v-icon color="red">mdi-logout</v-icon>
</v-list-item-icon>
<v-list-item-content>Logout</v-list-item-content>
</v-list-item>
</v-list>
</template>
</v-navigation-drawer>
</div>
</template>
<script>
export default {
data: () => ({
drawer: true,
navbarlist: [
{ icon: "mdi-view-dashboard", text: "Dashboard", route: "/" },
{ icon: "mdi-upload", text: "Upload", route: "/upload" },
{ icon: "mdi-video-3d", text: "Viewer", route: "/viewer" },
],
username: "",
}),
created: function () {
this.username = this.$store.state.userProfile.name;
},
methods: {
logout() {
this.$store.dispatch("logout", {});
},
},
};
</script>
<style>
.font {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
}
.uppercase {
text-transform: uppercase;
}
.first-word {
font-weight: 400;
}
.second-word {
font-weight: 200;
color: grey;
}
.item-tile-icon {
color: black;
}
.username {
color: whitesmoke;
font-family: "Franklin Gothic Medium", "Arial Narrow", Arial, sans-serif;
font-weight: lighter;
letter-spacing: 0.001em;
}
</style>
App.vue
<template>
<div>
<v-app>
<Navbar v-if="showNavBar"/>
<v-main>
<router-view></router-view>
</v-main>
</v-app>
</div>
</template>
<script>
import Navbar from "./components/Navbar";
export default {
name: "App",
components: {
Navbar,
},
data: () => ({
showNavBar: true,
app: true
}),
mounted() {
}
};
</script>