i am developing an Aframe WebVR app, trying to write a function that will split a this.id into two parts and giving it to a variable then .setattribute
this is my code
AFRAME.registerComponent('remove-yellow', {
init: function () {
this.el.addEventListener('click', function (evt) {
console.log(this.id);
var boxid = this.id.split("-")[0];
console.log(boxid);
boxid.setAttribute("animation__scale", "property: scale; from: 1 1 0.01; to: 0.001 0.001 0.001; dur: 150");
});
}
});
with var boxid, console will give uncaught TypeError: boxid.setAttribute is not a function.This is the box that I am trying to animate:
<a-box id="box1" position="0 2 -1.5" rotation="0 0 0" depth="0" width="1" height="1" color="#39dbd8" scale="0.001 0.001 0">
<a-entity id="info" width="1" position="0 0 0.6" text="value: Hello people what is going on AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa; color:#000;"></a-entity>
<a-box id="box1-close" class="clickable" remove-yellow id="box2" position="0.4 0.4 0.6" rotation="0 0 0" depth="0" width="0.15" height="0.15" color="#f00" scale="1 1 0">
</a-box>
</a-box>
<a-box id="box1-show" class="clickable" add-yellow id="box3" position="0 2 -2" rotation="0 0 0" depth="0" width="0.5" height="0.5" color="#008000" scale="1 1 0"></a-box>
when box1-show is clicked, its id will be split"-", box1 will then receive the animation attribute. If I write:
box1.setAttribute("animation__scale", "property: scale; from: 1 1 0.01; to: 0.001 0.001 0.001; dur: 150");
it will work fine.But with var boxid, console will give uncaught TypeError: boxid.setAttribute is not a function.
I have tried the codes below from other solutions I found:
$(boxid).attr('animation__scale', 'property: scale; from: 1 1 0.1; to: 0.001 0.001 0.001; dur: 150');
the error will disappear but it will not animate.
I am thinking it might be a syntax error, anyone have any ideas?
At first the id is box1-close, then its redeclared as box2. Its best to use the id only for identification purposes, not to smuggle data :)
Normally You could use the global data attribute:
<div data-id="box1"></div>
But considering it's best to fully utilize the a-frame component system,
You need to use the component's schema.
Should the component manipulate any other element on the scene, just do
<a-entity my-component="param: value">
and access it in the component by the reference this.data.param.
Moreover the id, split or not, is just a string, you can't set any attributes to it, hence the error. If you want to set an attribute to the element it represents, you can grab it using:
document.querySelector("#" + value).setAttribute(), or more properly with document.getElementById(value).setAttribute().
You can check it out in my fiddle.
Related
I am making an A-Frame site and I need to create a button to move a ball with the id:"test" to it's starting position: 0 8 0. I've tried with the setAttribute script and it doesn't work. This is the javascript code I'm currently working with:
AFRAME.registerComponent('ballreset', {
events: {
click: function(evt)
{
document.getElementById('test').setAttribute('position', {x:0, y:8, z:0});
}
}
});
Edit: I found a typo in the code. But didn't solve problem
Without a full example its hard to tell what exactly is the problem but,
make sure you do have a raycaster - based cursor component. Mouse clicks won't work with webGL renders like they do with HTML elements.
make sure the click listener is working, ie. by logging each click
make sure the element with the given id exists at the time of the click
Below is a working version of something similar to what you want to achieve (click any object):
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent('foo', {
events: {
click: function(evt) {
// grab the current position
let pos = this.el.getAttribute("position");
// move upwards
this.el.setAttribute('position', { x: pos.x, y: pos.y + 0.25, z: pos.z });
}
}
});
</script>
<!-- attach a cursor component -->
<a-scene cursor="rayOrigin: mouse">
<a-box position="-1 0.5 -3" rotation="0 45 0" color="#4CC3D9" foo></a-box>
<a-sphere position="0 1.25 -5" radius="1.25" color="#EF2D5E" foo></a-sphere>
<a-cylinder position="1 0.75 -3" radius="0.5" height="1.5" color="#FFC65D" foo></a-cylinder>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" foo></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
I'm trying to make a component that checks the current position of a sphere in an AFrame scene and when it hits a specific coordinate and when it does it fires an event (In example below it resets it to its default position):
AFRAME.registerComponent("trackball", {
update: function() {
let pos = this.el.getAttribute("position");
if (pos == {x:-21.821,y: 1,z: 0})
{
this.el.setAttribute("position", "-21.821 5 0");
}
}
});
I'm not sure what format is returned when .getAttribute("position") is called so that may be why it's not working. I am running AFrame 1.1.0.
First of all update is called when attributes are changed via setAttribute(). If you want a function that is called on each render frame, then use tick().
Secondly, try using a range, instead of a fixed point, it's very likely that the object will move past the point between two ticks.
Something like this:
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/n5ro/aframe-physics-system#v4.0.1/dist/aframe-physics-system.min.js"></script>
<script>
AFRAME.registerComponent("trackball", {
tick: function() {
let pos = this.el.getAttribute("position");
if (pos.y < 0.5) {
// reset position
this.el.setAttribute("position", "0 3 -4")
// sync
this.el.components["dynamic-body"].syncToPhysics();
}
}
});
</script>
<a-scene physics cursor="rayOrigin: mouse">
<a-sphere position="0 1.25 -5" radius="0.25" color="#EF2D5E" dynamic-body trackball></a-sphere>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" static-body></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
Also try using the object3D properties instead setAttribute() and getAttribute() when dealing with frequently called functions (which certainly applies to tick()):
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/n5ro/aframe-physics-system#v4.0.1/dist/aframe-physics-system.min.js"></script>
<script>
AFRAME.registerComponent("trackball", {
// iife to initialize the "origin point" once
tick: (function() {
const origin = new THREE.Vector3(0, 3, -4);
const y_range = 0.5;
return function() {
// check if the ball is out of range
const pos = this.el.object3D.position
if (pos.y < y_range) {
// reset position
pos.copy(origin);
// sync
this.el.components["dynamic-body"].syncToPhysics();
}
}
})()
});
</script>
<a-scene physics cursor="rayOrigin: mouse">
<a-sphere position="0 1.25 -4" radius="0.25" color="#EF2D5E" dynamic-body trackball></a-sphere>
<a-plane position="0 0 -4" rotation="-90 0 0" width="4" height="4" color="#7BC8A4" static-body></a-plane>
<a-sky color="#ECECEC"></a-sky>
</a-scene>
Keep in mind, updating the position in such manner is more performant, but will cause getAttribute("position") to return the last position set via setAttribute("position", new_position)
I'm trying to reparent an element (entity), keeping its position, rotation (and if possible, size, eg, scale) in the scene. Ideally, I would like to have a component (say "reparent") that when set on an entity, "moves" it to the specified parent, keeping the aspect of the entity in the scene. For example, for the next HTML code, the green entity would become a child of the red one, but would keep its position and rotation in the scene:
<a-scene id="scene" background="color: grey">
<a-entity id="red"
position="-1 3.5 -4" rotation="30 0 0" material="color: red"
geometry="primitive: cylinder"></a-entity>
<a-entity id="green" reparent="parent: #red"
position="-3 2 -4" rotation="0 45 0" material="color: green"
geometry="primitive: box"></a-entity>
</a-scene>
Apparently, this can be done at the three level using attach, but when I try to write a simple component using it, it doesn't work, apparently due to what the reparenting at the HTML does (I assume, because of AFrame).
I've written his test component to show the problem:
AFRAME.registerComponent('reparent', {
schema: {
parent: {type: 'selector', default: null},
},
init: function () {
const el = this.el;
const parent = this.data.parent;
move = function(evt) {
parent.object3D.attach(el.object3D);
console.log(el.object3D.parent.el.id, el.parentElement.id);
// parent.appendChild(el);
// console.log(el.object3D.parent.el.id, el.parentElement.id);
};
el.sceneEl.addEventListener('loaded', move);
}
});
When run, the result is red scene: the parent of object3D changed, but at the HTML level the parent of the element remains the scene. The green box appears as intended (in "-3 2 -4", world coordinates, and with the right rotation).
If I uncomment the two commented lines, I get a second line red red, but the green box disappears. In other words, now at the HTML the element got reparented, as intended, but somehow its object3D is not working anymore.
Any idea about why this fails, or some other idea for such a component?
All of this with AFrame 1.1.0.
After getting the advice that reusing the same element (entity) for reparenting is not a good idea (see Change parent of component and keep position), I explored the idea of copying the entity to be reparented in a new element under the new parent, and then remove the "old" one (instead of directly reparenting the entity). Granted, it is not the most clean hack in the world, but I hope it helps in my use case. So, I wrote a component:
AFRAME.registerComponent('reparent', {
schema: {
parent: {type: 'selector', default: null},
},
update: function () {
const el = this.el;
const parent = this.data.parent;
if (el.parentElement == parent) {
// We're already a child of the intended parent, do nothing
return;
};
// Reparent, once object3D is ready
reparent = function() {
// Attach the object3D to the new parent, to get position, rotation, scale
parent.object3D.attach(el.object3D);
let position = el.object3D.position;
let rotation = el.object3D.rotation;
let scale = el.object3D.scale;
// Create new element, copy the current one on it
let newEl = document.createElement(el.tagName);
if (el.hasAttributes()) {
let attrs = el.attributes;
for(var i = attrs.length - 1; i >= 0; i--) {
let attrName = attrs[i].name;
let attrVal = el.getAttribute(attrName);
newEl.setAttribute(attrName, attrVal);
};
};
// Listener for location, rotation,... when the new el is laded
relocate = function() {
newEl.object3D.location = location;
newEl.object3D.rotation = rotation;
newEl.object3D.scale = scale;
};
newEl.addEventListener('loaded', relocate, {'once': true});
// Attach the new element, and remove this one
parent.appendChild(newEl);
el.parentElement.removeChild(el);
};
if (el.getObject3D('mesh')) {
reparent();
} else {
el.sceneEl.addEventListener('object3dset', reparent, {'once': true});
};
}
});
You can check it works with the following HTML file:
<!DOCTYPE html>
<html>
<head>
<script src="https://aframe.io/releases/1.1.0/aframe.min.js"></script>
<script src="reparent.js"></script>
</head>
<body>
<a-scene id="scene" background="color: grey">
<a-entity id="red"
position="1 2 -4" rotation="30 0 0" material="color: red"
geometry="primitive: cylinder"></a-entity>
<a-entity id="green" reparent="parent: #red"
position="-1 0 -4" rotation="0 45 0" material="color: green"
geometry="primitive: box"></a-entity>
<a-entity id="wf"
position="-1 0 -4" rotation="0 45 0" material="wireframe: true"
geometry="primitive: box"></a-entity>
</a-scene>
</body>
</html>
The wireframe entity is just to show how the green box stays exactly in the same position, although it was "reparented" under the red entity (explore the DOM to check that). As explained above, the green box is really a new one, cloned from the original one, which was then deleted by the component.
The component should work when its parent property is changed, allowing for dynamic reparenting when needed.
I am trying to build a simple scene with A-Frame in JS to excercise the basics(codepen). So far so good I got the geometry like I want it, not perfect but it's ok.
<a-cylinder position="-4.8 1 0" radius="0.2" height="2" color="slategray">
<a-cylinder position="0 1.5 0" radius="0.15" height="1.5" color="slategray">
<a-box position="-1 0.5 0" width="2" height="0.5" depth="0.3" color="slategray">
<a-circle cursor-listener id="green" position="-0.5 -0.03 0.17" color="#196f3d " radius="0.2"></a-circle></a-circle>
<a-circle cursor-listener id="yellow" position="0 -0.03 0.17" color="#7d6608 " radius="0.2"></a-circle>
<a-circle cursor-listener id="red" position="+0.5 -0.03 0.17" color="#641e16" radius="0.2"></a-circle>
</a-box>
</a-cylinder>
</a-cylinder>
<a-ring position="0 0 0" rotation="-90 0 0 " color="gray" radius-inner="5" radius-outer="7">
<a-sphere id="car" position="-0 -1 0.4" radius="0.01" animation="property:rotation;to:0 0 -360; easing:linear; dur:5000;loop:false;autoplay:false">
<a-box position="-5.5 0 0" width="0.4" height="0.4" depth="0.4" ></a-box>
</a-sphere>
</a-ring>
I can make the rectangle orbit around the center of the scene, but what i want to do is make the animation a click event based, so I though of switching the loop attribute from false to true.
i went ahead and tried it in the console but nothing changed.
var s=document.getElementById("car")
s.components.animation.animation.loop=true
Note: I've also changed the autoplay to false and tried switching it back from the console by executing
var s=document.getElementById("car")
s.components.animation.animation.autoplay=true
but again nothing changed.
I think I misunderstood some in the documentation, I'm not sure exactly.
Can somebody please help me.
---------------------------------EDIT------------------------
I've added these events setting to the animation attribute of the the parent of component of my box
startEvents: rotation-start; pauseEvents: rotation-pause; resumeEvents: rotation-resume;
e.g :
<a-sphere id="car" position="-0 -1 0.4" radius="0.01" animation="property:rotation;startEvents: rotation-start; pauseEvents: rotation-pause; resumeEvents: rotation-resume;to:0 0 -360; easing:linear; dur:5000;loop:true;">
<a-box position="-5.5 0 0" width="0.4" height="0.4" depth="0.4" ></a-box>
</a-sphere>
so now I can play/pause the animation in the browser console using :
var e=document.getElementById("car")
e.components.animation.animation.play()
e.components.animation.animation.pause()
but trying to do it inside script tag so that i can utilize the cursor-listener event doesn't work :
<script>
AFRAME.registerComponent('cursor-listener', {
update: function () {
this.el.addEventListener('click', function (evt) {
var car=document.getElementById("car");
if(this.id=="red"){
car.components.animation.animation.pause()
}
if(this.id=="green"){
console.log(this.data);
this.components.animation.animation.play()
}
});
}
});
</script>
throws error:
TypeError: this.components.animation is undefinedtraffic_light.html:23:21
---------------------------------------------------EDIT N°2-------------------------
I fixed it , here is the new js :
<script>
AFRAME.registerComponent('cursor-listener', {
update: function () {
this.el.addEventListener('click', function (evt) {
var car=document.getElementById("car_origin");
if(this.id=="red"){
car.components.animation.animation.pause()
}
if(this.id=="green"){
car.components.animation.animation.play()
}
});
}
});
</script>
I was using this exact code in the previous version of Aframe 0.8.0 And the clicking was working normally.
However, when I tried using the 0.8.2 or the master version it showed no signs of clicking and without showing any errors
This is my registered component which I would like to detect clicks on the entities it is attached to
AFRAME.registerComponent('change-color-on-hover', {
init: function () {
var data = this.data;
var el = this.el; // <a-box>
var defaultColor = el.getAttribute('material').color;
/**
* Attach 'click' event
*/
el.addEventListener('click', function () {
alert('clicked');
});
}});
The Element where I want to detect the click (I am setting its position later and they it's appearing where they should be)
<a-image position="" src="#blue_target_rendered" height="30" width="40" depth="1" shadow event-set__click="_event: click; color:black" change-color-on-hover look-at="[camera]" id="hotspot-{{$hotspot->id}}" data-link-to="{{$hotspot->link_to}}" data-link-from="{{$hotspot->link_from}}">
With a setup like this:
<a-entity id="cameraParent" position="0 0 0" >
<a-entity id="cam" camera="zoom:1;" look-controls collider-check position="0 0 0" >
<a-entity cursor=" rayOrigin: mouse" geometry="primitive: ring; radiusInner: 0; radiusOuter: 0" material="color: black; shader: flat"></a-entity>
<a-entity raycaster="showLine: true; far: 1000"></a-entity>
</a-entity>
</a-entity>
The line component created by the raycaster is interfering with the rays emitted by the cursor = rayOrigin: mouse. In other words, you're clicking on the element with the line on each click.
If you reposition the raycaster a bit:
<a-entity position="0 -0.1 0" raycaster="showLine:true; far: 1000"></a-entity>
The mouse cursor should be working fine.