在 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
事件导致了问题。我建议您将其替换为 mouseup
和 mousedown
事件。
还要确保您确实可以触发事件 - 您需要将光标连接到相机
工作示例
运行 全屏(运行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>
我创建了一个 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
事件导致了问题。我建议您将其替换为 mouseup
和 mousedown
事件。
还要确保您确实可以触发事件 - 您需要将光标连接到相机
工作示例
运行 全屏(运行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>