【问题标题】:Three.js touch events not working properly on mobileThree.js 触摸事件在移动设备上无法正常工作
【发布时间】:2020-01-28 16:49:10
【问题描述】:

所以我有一个交互式 3D 地球仪,由 three.js 和一个模拟数据库构建。

当您单击一个国家/地区时,它会调用模拟数据库并在弹出窗口中返回信息。它在桌面上完美运行,但我在移动设备上一直遇到问题。

通过添加触摸事件,我现在可以在移动设备上触摸时获取国家/地区,但是它似乎没有使用 UsersManager 函数调用数据库。

我很欣赏这有点冗长,但我也无法将其弹出到代码沙箱中,因为它目前大约有 50 个文件。

如果有人能在“测试”功能中发现我需要做的任何事情,我将不胜感激。

Desktop PopUp Working

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);
  }
}

【问题讨论】:

  • 您是否隔离了根本问题?你能用调试器单步调试你的代码,看看问题是不是服务调用发送了错误的坐标(可能是事件位置转换逻辑不正确)?现在很难帮助您编写代码,因为您当前的示例不是minimal reproducible example
  • 您将touchend 事件分配给与mousedown 相同的功能。 (window.addEventListener("touchend", this.onDown);) 这是故意的吗?似乎他们(在概念上)做了不同的事情。我本来希望 touchendmouseup 保持一致,但我对您的用例并不完全清楚。
  • @TheJim01 已经整理好了,非常感谢!我意识到我的例子并不多,所以非常感谢您的意见!
  • @zero298 我已经通过它并进行了调试,但事实证明它有点难以捉摸,谢天谢地,它现在正在像上面那样进行微小的调整。我将来会尝试做一些事情,但这个很棘手!

标签: reactjs three.js


【解决方案1】:

将我的评论添加为完整答案,以便将问题标记为已解决。

您的touchend 事件当前指向与您的mousedown 事件相同的处理程序。

window.addEventListener("touchend", this.onDown);

在大多数情况下(尽管可能不在您的情况下),touchend 等同于 mouseup 事件(这是您调用数据库的地方)。

window.addEventListener("touchend", this.onUp);

如果这是正确的,你也应该更新你的removeEventListener

【讨论】:

    猜你喜欢
    • 2015-01-04
    • 1970-01-01
    • 2021-01-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-04
    • 2017-07-29
    • 2023-03-18
    相关资源
    最近更新 更多