reactjs - Three.js 触摸事件在移动设备上无法正常工作
问题描述
所以我有一个交互式 3D 地球仪,用 three.js 和一个模拟数据库构建。
当您单击一个国家/地区时,它会调用模拟数据库并在弹出窗口中返回信息。它在台式机上完美运行,但我在移动设备上一直遇到问题。
通过添加触摸事件,我现在可以在触摸移动设备时获取国家/地区,但是它似乎没有使用 UsersManager 函数调用数据库。
我很欣赏这有点冗长,但我也无法将其弹出到代码沙箱中,因为它目前大约有 50 个文件。
如果有人能在“测试”功能中发现我需要做的任何事情,我将不胜感激。
import { PerspectiveCamera, Raycaster, WebGLRenderer } from "three";
import CountriesManager from "./CountriesManager";
import HitDetector from "./HitDetector";
import Loader from "./loader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import SceneDetection from "./scenes/SceneDetection";
import SceneMain from "./scenes/SceneMain";
import UsersManager from "./UsersManager";
import { getMouse } from "./utils";
import { onCountryClick } from "./AppSignals";
export default class SceneApp {
init() {
this.loadAssets(); // FIRST THING -> load assets
}
loadAssets() {
const loader = new Loader();
loader.load(
["assets/earthtemplate.jpg", "assets/circle.png", "assets/worldmap.gif"],
this.onLoadingComplete
);
}
// only initialise scene when the assets are all loaded
onLoadingComplete = () => {
this.countryText = document.getElementById("countryName");
this.renderer = new WebGLRenderer({
antialias: true
});
document.body.appendChild(this.renderer.domElement);
this.countryText = document.getElementById("countryName");
// lazy, to know which country we hovering we will update this html element
// TODO: as we're using React, may be best to signal to the parent component to update its state.countryName ?
UsersManager.init(); // ideally we won't have to do that when it's linked to a proper db
CountriesManager.init(); // will link the countries colors to their name / id
this.currentState = STATES.explore;
this.easing = 0.2; // is camera animating around the globe?
this.animating = false; // is camera animating around the globe?
this._down = false; // is mouse down
this._downAndMove = false; // is mouse down and moving at the same time?
this._mouse = { x: 0, y: 0 }; // could probably be in a class specific for Interaction
this._target = { x: 0, y: 0, z: 0 }; // the position for the camera to rotate around the globe
this.scene = new SceneMain(); // main Scene with all the visual things in it
this.scene.init();
this.sceneDetection = new SceneDetection(); // this scene will only be used as a render texture to know which country we're hovering
this.sceneDetection.init(); // it will only contain the colored globe
const ratio = window.innerWidth / window.innerHeight;
this.camera = new PerspectiveCamera(45, ratio, 1, 3000);
this.camera.position.set(0, 20, CAMERA_DIST);
this.camera.lookAt(this.scene.position);
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.enableDamping = true;
this.controls.enablePan = false;
this.hitDetector = new HitDetector(
this.sceneDetection,
this.renderer,
this.camera
); // hit detector will check what color we're hovering
this.raycaster = new Raycaster();
this.addEvents();
this.update(); // starts the RAF loop
};
addEvents() {
// resize
window.addEventListener("resize", this.resize);
window.addEventListener("mousedown", this.onDown);
window.addEventListener("mouseup", this.onUp);
window.addEventListener("mousemove", this.onMove);
window.addEventListener("click", this.onClick);
window.addEventListener("touchstart", this.test);
window.addEventListener("touchmove", this.onMove);
window.addEventListener("touchend", this.onDown);
this.resize();
}
removeEvents() {
window.removeEventListener("resize", this.resize);
window.removeEventListener("mousedown", this.onDown);
window.removeEventListener("mouseup", this.onUp);
window.removeEventListener("mousemove", this.onMove);
window.removeEventListener("click", this.onClick);
window.removeEventListener("touchstart", this.test);
window.removeEventListener("touchmove", this.test);
window.removeEventListener("touchend", this.onDown);
}
test = e => {
getMouse(e, this._mouse); // get the mouse position
if (this.currentState !== STATES.explore) {
// if we are focusing on a country, no white overlays on other countries
if (this._down && this.currentState === STATES.select)
this.currentState = STATES.animateOut; // if we're moving while having the mousepressing down, get out of the zoom
return;
}
if (this._down) this._downAndMove = true; // we're moving while pressing down
const color = this.hitDetector.update(this._mouse.x, this._mouse.y); // get the color from the hit detector
const country = CountriesManager.getCountry(color); // get the country depending of the color
if (country && country !== this.lastCountry) {
// if that's a different country that we're hovering
this.lastCountry = country;
this.countryText.innerText = country.name;
this.scene.onHoverCountry(country.id); // this will call a change of uniform in the shaders
console.log(this.lastCountry);
} else if (color === "ffffff".toUpperCase()) {
this.lastCountry = null;
this.scene.onHoverCountry(999999); // this will call a change of uniform in the shaders
}
console.log("this is working on mobile as a click", country);
};
onUp = e => {
this._down = false;
// if we were moving, means we're exploring so don't continue further more
if (this._downAndMove) {
this._downAndMove = false;
return;
}
if (this.currentState === STATES.explore && this.lastCountry) {
const int = this.getIntersection(this._mouse);
if (int) {
// zoom on the country
const { x, y, z } = int.point;
let scale =
(GLOBE_RADIUS + (CAMERA_DIST - GLOBE_RADIUS) / 1.5) / GLOBE_RADIUS;
this.animating = true;
this._target.x = x * scale;
this._target.y = y * scale;
this._target.z = z * scale;
this.currentState = STATES.animateIn; // see update function
}
// dispatch country / users
const users = UsersManager.getUsersPerCountry(this.lastCountry.name);
onCountryClick.dispatch(this.lastCountry.name, users);
}
};
onDown = e => {
this._down = true;
};
onMove = e => {
getMouse(e, this._mouse); // get the mouse position
// move particles up when moving the mouse on the sphere
const int = this.getIntersection(this._mouse);
if (int) this.scene.pushParticlesUp(int);
if (this.currentState !== STATES.explore) {
// if we are focusing on a country, no white overlays on other countries
if (this._down && this.currentState === STATES.select)
this.currentState = STATES.animateOut; // if we're moving while having the mousepressing down, get out of the zoom
return;
}
if (this._down) this._downAndMove = true; // we're moving while pressing down
const color = this.hitDetector.update(this._mouse.x, this._mouse.y); // get the color from the hit detector
const country = CountriesManager.getCountry(color); // get the country depending of the color
if (country && country !== this.lastCountry) {
// if that's a different country that we're hovering
this.lastCountry = country;
this.countryText.innerText = country.name;
this.scene.onHoverCountry(country.id); // this will call a change of uniform in the shaders
} else if (color === "ffffff".toUpperCase()) {
this.lastCountry = null;
this.countryText.innerText = "";
this.scene.onHoverCountry(999999); // this will call a change of uniform in the shaders
}
};
getIntersection(pos) {
const x = (pos.x / window.innerWidth) * 2 - 1; // get the mouse coordinates between -1 and 1 (clipspace coordinates)
const y = -(pos.y / window.innerHeight) * 2 + 1;
this.raycaster.setFromCamera({ x, y }, this.camera); // raycast
let intersects = this.raycaster.intersectObjects(
this.sceneDetection.children
); // should only be one sphere
return intersects[0]; // get first intersection
}
update() {
this.renderer.render(this.scene, this.camera);
this.controls.update();
// depending of the state of the globe (see consts file) we need to zoom in / out, etc.
if (this.currentState === STATES.animateOut) {
// in animate out of the selected view, need the camera to zoom back
const dist = this.camera.position.length();
const diff = CAMERA_DIST - dist;
if (diff < 1) {
// change the state if the camera is far enough
this.currentState = STATES.explore;
} else {
const scale = CAMERA_DIST / dist;
const newX = this.camera.position.x * scale;
const newY = this.camera.position.y * scale;
const newZ = this.camera.position.z * scale;
this.camera.position.x += (newX - this.camera.position.x) * this.easing;
this.camera.position.y += (newY - this.camera.position.y) * this.easing;
this.camera.position.z += (newZ - this.camera.position.z) * this.easing;
}
} else if (this.currentState === STATES.animateIn) {
// if animateIn, ease to the camera!
// TODO to aboid jerkyness in the movement, you can clamp diffX, diffY, diffZ so it doesn't add too much in one frame
const diffX = (this._target.x - this.camera.position.x) * this.easing;
const diffY = (this._target.y - this.camera.position.y) * this.easing;
const diffZ = (this._target.z - this.camera.position.z) * this.easing;
this.camera.position.x += diffX;
this.camera.position.y += diffY;
this.camera.position.z += diffZ;
// if we're close enough from the target, change state
if (Math.abs(diffX * diffY * diffZ) < 0.01)
this.currentState = STATES.select;
} else if (this.currentState === STATES.explore && !this._down) {
this.sceneDetection.update();
this.scene.update();
}
this.raf = requestAnimationFrame(this.update.bind(this));
}
resize = () => {
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
};
unmount() {
this.removeEvents();
this.renderer.domElement.remove();
cancelAnimationFrame(this.raf);
}
}
解决方案
添加我的评论作为完整答案,因此可以将问题标记为已解决。
您的touchend
事件当前指向与您的事件相同的处理程序mousedown
。
window.addEventListener("touchend", this.onDown);
在大多数情况下(尽管可能不在您的情况下),touchend
等同于mouseup
事件(这是您进行数据库调用的地方)。
window.addEventListener("touchend", this.onUp);
如果这是正确的,您也应该更新您的removeEventListener
.
推荐阅读
- javascript - 如何将 `cv.Mat()` 的大小设置为等于视频的大小?
- node.js - 无法更新 mongodb 中的单个数据
- php - Visual Studio Code 中的 PHP Intelelephene
- javascript - 如何在带有填充颜色的折线图上添加带中断的区域
- python-3.x - 如何在不使用循环的情况下打印列表中的特定项目
- java - 当我删除字段时,Firestore EventListener 导致 Null 异常
- java - 如何从不同的位置导入包?
- javascript - React - 条件渲染(来自多个数组的迭代)
- c - 在不同环境下运行C程序时出现段错误
- python - 无法使用 Pyads 建立路由(Python 和 TwinCAT 3)