在更改窗口的ThreeJS VueJS应用程序中获得正确的鼠标悬停交互



我为自己所做的一切感到骄傲:我有一个由4个形状组成的菜单。当你悬停一个形状时,它会改变颜色,增长并将其他形状推到顶部,而旋转速度会变慢。

我阅读了ThreeJS文档,并遵循了StackOverflow成员的建议。

我很难处理鼠标交互和窗口大小调整:当我第一次打开浏览器时,鼠标悬停似乎并没有在鼠标悬停时被调用。当我调整窗口大小时,它显然是一团糟。如果有人知道我做错了什么,请提前感谢:(

这是我的组件:

<template>
<v-container>
<div @click="onClick" @mousemove="onMouseMove" id="menu3D" style="background-color: transparent; position: fixed; left: 20px; width:15%; height:100%;"></div>
<v-row class="text-center">
<v-col
class="mb-5"
cols="12"
>
<h2 class="headline font-weight-bold mb-3">
Accueil
</h2>
<v-row justify="center">
<p>
THIS IS ONLY A TEST
</p>
</v-row>
</v-col>
</v-row>
</v-container>
</template>
<script>
import * as Three from 'three'
export default {
name: 'Home',
mounted() {
this.init();
},
methods: {
init: function() {
this.createScene();
this.createCamera();
this.userData.formes.forEach(x=>this.createShape(x))
this.addSpotlight(16777215/*'#fdffab'*/);
this.addAmbientLight();
this.animate();
window.addEventListener('resize', this.onResize())
},
onResize: function() {
let container = document.getElementById('menu3D');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.camera.aspect = container.clientWidth / container.clientHeight;
this.camera.updateProjectionMatrix();
},
createScene: function() {
this.renderer = new Three.WebGLRenderer({
antialias: true,
alpha: true
});
let container = document.getElementById('menu3D');
this.renderer.setSize(container.clientWidth, container.clientHeight);
this.renderer.setPixelRatio(window.devicePixelRatio);
this.renderer.setClearColor(0xffffff,0);
container.appendChild(this.renderer.domElement);
},
createCamera: function() {
//let container = document.getElementById('container');
this.camera = new Three.PerspectiveCamera(50, 1.686275 /*container.clientWidth/container.clientHeight*/, 0.01, 1000);
this.camera.position.set(0, 5, 20);
this.camera.zoom = 1;
},
createShape: function(shape) {
let material = new Three.MeshStandardMaterial({
"color": '#0000ff'/*16777215*/,
"roughness": 1,
"metalness": 0.5,
"emissive": 0,
"depthFunc": 3,
"depthTest": true,
"depthWrite": true,
"stencilWrite": false,
"stencilWriteMask": 255,
"stencilFunc": 519,
"stencilRef": 0,
"stencilFuncMask": 255,
"stencilFail": 7680,
"stencilZFail": 7680,
"stencilZPass": 7680
})
switch (shape.nom) {
case "Box": {
this.geometry = new Three.BoxBufferGeometry(1.8,1.8,1.8)
break;
}
case "Sphere": {
this.geometry = new Three.SphereBufferGeometry(1,8,6,0,6.283185,0, 3.141593)
break;
}
case "Dodecahedron": {
this.geometry = new Three.DodecahedronBufferGeometry(1.2,0)
break;
}
case "Icosahedron": {
this.geometry = new Three.IcosahedronBufferGeometry(1.5,0)
break;
}
}
this.mesh = new Three.Mesh(this.geometry, material)
this.mesh.name = shape.nom
this.mesh.userData = shape.userData
this.mesh.receiveShadow = true
this.mesh.castShadow = true
this.mesh.position.set(0, shape.userData.position.y, 0)
this.scene.add(this.mesh)
},
addSpotlight: function(color) {
const light = new Three.SpotLight(color, 2, 1000)
light.position.set(0, 0, 30)
this.scene.add(light)
},
addAmbientLight: function() {
const light = new Three.AmbientLight('#fff', 0.5)
this.scene.add(light)
},
verifForme: function(e) {
let t = this
let elt = t.scene.getObjectByName(e);
t.intersects = t.raycaster.intersectObject(elt);
if (t.intersects.length !== 0) {
// if it's not in the array, we put it at the beginning
if (t.userData.souris.indexOf(e)<0) {
t.userData.souris.unshift(e);
console.log(t.userData.souris[0] + " survolé!");
}
if (t.userData.souris[0] == e) {
let obj = t.intersects[0].object;
obj.material.color.set('#'+elt.userData.couleurs[1]);
obj.scale.set(obj.scale.x<1.4?obj.scale.x+t.VITESSE_ZOOM:obj.scale.x,obj.scale.y<1.4?obj.scale.y+t.VITESSE_ZOOM:obj.scale.y,obj.scale.z<1.4?obj.scale.z+t.VITESSE_ZOOM:obj.scale.z);
obj.rotation.y += t.VITESSE_ROTATION/t.RALENTISSEMENT
t.replacer(obj,obj.userData.position.y+obj.userData.decalage)
}
else {
t.retrecir(e,elt);
}
}
else {
if (t.userData.souris.indexOf(e)>=0) {
t.userData.souris = t.userData.souris.filter(forme => forme != e);
}
t.retrecir(e,elt);
}
},
onClick: function ( event ) {
event.preventDefault();
if (this.userData.souris.length >0 ) { console.log(this.userData.souris[0] + " clicked!"); }
else {
console.log("click outside!")
}
},
onMouseMove: function(event){
let container = document.getElementById('menu3D');
this.mouse.x = ( event.clientX / container.clientWidth ) * 2 - 1;
this.mouse.y = - ( event.clientY / container.clientHeight ) * 2 + 1;
//console.log(JSON.stringify(this.mouse))
},
replacer: function(e,py) {
// next line to prevent shaking
if (Math.abs(e.position.y - py) < 0.05) { return true }
let rhesus = 10*this.VITESSE_ZOOM
if (this.userData.souris[0] != e.name) { rhesus *= 3 }
//console.log(e.name+': '+this.userData.souris[0]+' - '+rhesus)
if (e.position.y > py) { rhesus = -1 }
e.position.set(0,Math.trunc(10*e.position.y+rhesus)/10,0)
},
retrecir: function (n,e) {
// checking if the clicked element is on top
let dec = 0
let elt = this
if ((elt.userData.souris.length > 0) && (elt.userData.formes.map(x=>x.nom).indexOf(n)<elt.userData.formes.map(x=>x.nom).indexOf(elt.userData.souris[0]))) {
dec = Math.trunc(10*e.parent.getObjectByName(elt.userData.souris[0]).userData.decalage*2.1)/10;
}
e.material.color.set('#'+e.userData.couleurs[0]);
e.rotation.y += elt.VITESSE_ROTATION
e.scale.set(e.scale.x>1?e.scale.x-elt.VITESSE_ZOOM:e.scale.x,e.scale.y>1?e.scale.y-elt.VITESSE_ZOOM:e.scale.y,e.scale.z>1?e.scale.z-elt.VITESSE_ZOOM:e.scale.z);
let newY = e.userData.position.y+dec
if (e.position.y != newY) {
elt.replacer(e,newY)
}
},
animate: function() {
let elt = this
requestAnimationFrame(this.animate);
this.raycaster.setFromCamera(this.mouse, this.camera);
this.userData.formes.map(x=>x.nom).forEach(x=>elt.verifForme(x))
if (this.userData.souris.length >0 ) { document.body.style.cursor = "pointer"; }
else { document.body.style.cursor = "default"; }
this.camera.updateProjectionMatrix();
this.renderer.render(this.scene, this.camera);
}
},
data: () => ({
scene: new Three.Scene(),
camera: null,
renderer: Three.WebGLRenderer,
mesh: new Three.Mesh,
factor:0,
mouse : new Three.Vector2(1, 1),
raycaster : new Three.Raycaster(),
intersects : [],
VITESSE_ROTATION: 0.05,
VITESSE_ZOOM: 0.1,
RALENTISSEMENT: 3,
userData: {
"souris": [],
"formes": [
{
"nom": "Box",
"userData": {
"position": {
"x": 0,
"y": 7.8,
"z": 0
},
"couleurs": [
"aaaaaa",
"095256"
],
"decalage": 0.5
}
},
{
"nom": "Icosahedron",
"userData": {
"position": {
"x": 0,
"y": 5.5,
"z": 0
},
"couleurs": [
"aaaaaa",
"087F8C"
],
"decalage": 0.5
}
},
{
"nom": "Dodecahedron",
"userData": {
"position": {
"x": 0,
"y": 3.1,
"z": 0
},
"couleurs": [
"aaaaaa",
"5AAA95"
],
"decalage": 0.4
}
},
{
"nom": "Sphere",
"userData": {
"position": {
"x": 0,
"y": 1,
"z": 0
},
"couleurs": [
"aaaaaa",
"86A873"
],
"decalage": 0.2
}
}
]
}
}),
}
</script>

我使用您提供的代码调查了这个问题,并在本地修复了它,所以我希望它也适用于您。问题如下:

  1. 在调整大小事件的附件中有一个很难找到的拼写错误:您需要使用window.addEventListener('resize', this.onResize);来删除(),而不是window.addEventListener('resize', this.onResize()),因为您不想在附件时调用该函数,而是希望每次触发事件时都调用它
  2. 由于第一个问题,由于调整大小函数没有在您预期的时候被调用,我想这就是为什么您在相机实例化中使用硬编码值(1.686275(,而不是推荐的公式container.clientWidth / container.clientHeight,所以您需要将其改回

createCamera: function () {
let container = document.getElementById('menu3D');
this.camera = new Three.PerspectiveCamera(50, container.clientWidth / container.clientHeight, 0.01, 1000);
...

  1. 由于VueJS的要求,3D容器div不在HTMLbody的根级别,因此在onMouseMove()中,您需要使用偏移坐标而不是客户端坐标,如下所示:

onMouseMove: function (event) {
let container = document.getElementById('menu3D');
this.mouse.x = (event.offsetX / container.clientWidth) * 2 - 1;
this.mouse.y = - (event.offsetY / container.clientHeight) * 2 + 1;
...

恐怕问题源于您的相机的创建:

this.camera = new Three.PerspectiveCamera(50, 1.686275 /*container.clientWidth/container.clientHeight*/, 0.01, 1000);

为什么要使用神奇的宽高比1.686275,而不是像调整大小时那样使用实际的width/height?这将在调整大小前后为您提供不同的行为。

乍一看,这是我的最佳猜测,尽管我认为你的应用程序中还有其他硬编码的"神奇数字"需要根据屏幕的宽度和高度重新计算。我不可能通读你发布的300行代码。您应该考虑隔离问题以创建一个最小的工作示例,并通过代码片段将其添加到您的问题中,这样我们就可以看到您的代码在运行。

最新更新