在 A-Frame 中动态创建的事件监听器

Add event listener on dynamically created in A-Frame

我创建了一个 class 来帮助我使用 A-Frame 显示 3D 模型。在此 class 中,有一些在运行时创建的球体广告插入到场景中。我正在尝试添加一个事件侦听器(我必须在单击这些球体时显示一条消息)

代码如下:

export class AFrameObjViewNavMarkProvider implements Provider {
  // Class Variables...

  // Constructor...

  // ----- Method ----- \
  public setPointerService(pointersService: PointersService) {
    // method code...
  }

  public setPointerTrigger(value: boolean) {
    // method code...
  }

  // ----- Handlers ----- \

  // click Handler
  clickHandler(event, model: Model){
    // code for saving into backend...

    // save pointer into the back-end
    this.pointersService.loadPointer(newPointer).subscribe((pointer) => {
      this.showPointer(pointer);
    });
  }

  showPointer(pointer: Pointer){
    // create a string containing the position
    let pointString = pointer.position[0].toFixed(3) + " "
      + pointer.position[1].toFixed(3) + " "
      + pointer.position[2].toFixed(3);

    // compute the box that contains the model
    let modelRef = <any>document.getElementById("model");
    const box = new THREE.Box3().setFromObject(modelRef.object3D);
    const boxSizes = box.getSize(new THREE.Vector3());

    // compute the min size of the box (x, y, z)
    // it will be used to set pointer radius
    let minBoxSize = Math.min(boxSizes.x, boxSizes.y, boxSizes.z);
    let radius = minBoxSize / 30;

    let scene = document.getElementById("scene");
    let marker = document.createElement("a-sphere");
    scene.appendChild(marker);

    marker.setAttribute("class", "pointer");
    marker.setAttribute("radius", `${radius}`);
    marker.setAttribute("color", "#CC0000");
    marker.setAttribute("position", pointString);
  }

  // ----- Visual Methods ----- \
  renderModel(model: Model) {
    // position-setter is used to set the model position according to its size
    AFrameUtils.registerPositionSetter();

    // reference to the provider itself
    let caller: any = this;

    function clickHandler(event) {
      caller.clickHandler(event, model);
    }

    // sets the behaviour in response to a click event
    AFRAME.registerComponent('click-handler', {
      // init also calls update
      init: function () {
        let mouseDownTime: number = null;
        let mouseDownPoint: any = null;

        this.el.addEventListener('mousedown', event => {
          mouseDownTime = new Date().getTime();
          mouseDownPoint = event.detail.intersection.point;
        });

        this.el.addEventListener('mouseup', event => {
          if(!event.detail.intersection) return;

          let mouseUpTime = new Date().getTime();
          let mouseUpPoint = event.detail.intersection.point;

          // compute the differences (time and position) between press and release
          let timeDiff = mouseUpTime - mouseDownTime;

          // if press and release occur within 185 ms
          //  we consider the event as a click
          if (timeDiff <= 185 && JSON.stringify(mouseDownPoint) === JSON.stringify(mouseUpPoint)) {
            clickHandler(event);
          }
        });
      }
    });

    let renderingArea = document.getElementById('rendering-area');
    renderingArea.innerHTML = `
      <a-scene embedded id="scene" cursor="rayOrigin: mouse" raycaster="objects: .clickable">
        <!-- Assets definition -->
        <a-assets>
            <a-asset-item id="object-ref" src="${model.sources[0]}"></a-asset-item>
            <a-asset-item id="material-ref" src="${model.sources[1]}"></a-asset-item>
        </a-assets>

        <!-- Using the asset management system. -->
        <a-obj-model id="model" class="clickable" src="#object-ref" mtl="#material-ref" position-setter click-handler>
        </a-obj-model>

        <!-- Camera -->
        <a-camera id="camera" wasd-controls="fly:true"></a-camera>

        <!-- Environment elements-->
        <a-sky id="sky" color="#000000"></a-sky>
      </a-scene>
    `;

    setTimeout(() => {
      this.pointersService.getPointersByModelId(model.id).subscribe(pointers => {
        for(let pointer of pointers){
          this.showPointer(pointer);
        }

        let markers = Array.from(document.getElementsByClassName('pointer'));
        for(let marker of markers){
          marker.addEventListener('click', () => {
            console.log('click on pointer');
          })
        }
      });
    }, 125);
  }
}

和截图:

第一次尝试:我尝试注册另一个组件,像这样:

AFRAME.registerComponent('pointer-handler', {
      init: function(){
        this.el.addEventListener('click', () => {
          console.log('click on pointer');
        });
      }
    });

并在 showPointer 方法中使用 setAttribute("pointer-handler", "")

第二次尝试:我尝试使用marker.addEventListener直接将事件侦听器添加到showPointer方法中。

Third Try:第三次尝试可以看代码,进入setTimeout.

的回调

每一次尝试都不奏效;如果我尝试单击一个球体,则什么也不会发生。但是,如果我打开 A-Frame 检查器,导航到其中一个球体并单击它,消息就会被记录下来。我怀疑添加了处理程序,但某些原因导致未检测到点击。

我们将不胜感激。

谢谢!

编辑:您可以找到存储库 here

使用 setAttribute("pointer-handler", "") 方法是绝对有效的,也是实现目标的正确方法。

我认为可能是 click 事件导致了问题。我建议您将其替换为 mouseupmousedown 事件。

还要确保您确实可以触发事件 - 您需要将光标连接到相机

工作示例

运行 全屏(运行ning 片段后的右上角可以看到整个场景 - 关闭也在右上角)。

您可以通过按左上角的按钮添加新元素。查看如何有时触发点击,有时根本不触发点击。

let boxCounter = 0;

function createNewElement() {
  let box = document.createElement("a-box");
  boxCounter++;
  box.setAttribute("position", "" + boxCounter + " 0 -2");
  box.setAttribute("scale", "0.5");
  box.setAttribute("color", "red");
  box.setAttribute("click-handler", "");
  document.getElementById("scene").appendChild(box);
}
<!DOCTYPE html>
<html>
    <head>
        <script src="https://aframe.io/releases/1.0.0/aframe.min.js"></script>
    </head>
    <body>
      <script>
        AFRAME.registerComponent('click-handler', {
          init: function() {
            this.el.addEventListener('mousedown', (event) => {
              console.log("I was mousedowned!");
              this.el.setAttribute('material', 'color', 'green');
            });

            this.el.addEventListener('mouseup', (event) => {
              console.log("I was mouseuped!");
              this.el.setAttribute('material', 'color', 'red');
            });
            
            this.el.addEventListener('click', (event) => {
              console.log("I was clicked!");
              this.el.setAttribute('scale', '0.5 0.5 0.5');
            });
          }
        });
      </script>
        <a-scene id="scene">
          <a-entity camera look-controls>
            <a-entity cursor="fuse: true; fuseTimeout: 500;"
                      position="0 0 -1"
                      geometry="primitive: ring; radiusInner: 0.02; radiusOuter: 0.03"
                      material="color: black; shader: flat">
            </a-entity>
          </a-entity>
          <a-sphere color="red" position="0 0 -2" scale="0.5 0.5 0.5" click-handler></a-sphere>
        </a-scene>
        <button onclick="createNewElement()" style="width: 100px; height: 100px; position: absolute; top: 0; left: 0;" value="toggle"></button>
  </body>
    </body>
</html>