A-Frame: How to define a mixin dynamically at runtime? - aframe

A-Frame Mixins go into the <a-assets> element, which must be defined before the scene is rendered. This makes sense for pre-loading/caching images, videos etc, but it seems there should be a way to dynamically create and use mixins.
Just adding the mixin to <a-assets> at runtime does not seem to work. The recommendation for adding image assets at runtime is to inline the image source and set it on the material directly. Is there some similar way of defining/altering a mixin at runtime? Or do I just need to set the relevant properties on all the objects to which the mixin is applied (taking care to also account for properties having been set by other mixins later in the mixin chain or directly on the object itself)
Edit: It looks like aframe-asset-on-demand-component is designed to do this for image/video assets. Unclear if works for mixins, but it also hasn't been updated in a year. Is this a (semi-)officially recommended solution?

Sorry if I've misunderstood your question but I seem to be able to add mixins to the assets tag at runtime. A basic version would mean writing a component as follows;
// add assets at run time
AFRAME.registerComponent('addasset', {
init: function () {
var assets = document.getElementsByTagName('a-assets')[0]
var mixin = document.createElement('a-mixin')
mixin.setAttribute('id', 'makeitred')
mixin.setAttribute('material', 'color: red')
assets.appendChild(mixin)
},
});
And then attach that component to the scene as follows;
<a-scene addasset>
<a-assets>
</a-assets>
<a-cylinder
mixin="makeitred"
position="0 0.5 -3">
</a-cylinder>
</a-scene>
Here is a working glitch
To demonstrate how that could be added once the scene is running here is a version of the same component with a setTimeout to demonstrate how the mixin could be added later on.
// add assets at run time, delayed
AFRAME.registerComponent('addasset', {
init: function () {
setTimeout(function(){
var assets = document.getElementsByTagName('a-assets')[0]
var mixin = document.createElement('a-mixin')
var cylinder = document.getElementsByTagName('a-cylinder')[0]
mixin.setAttribute('id', 'makeitred')
mixin.setAttribute('material', 'color: red')
assets.appendChild(mixin)
cylinder.setAttribute('mixin', 'makeitred')
}, 2000);
},
});
and then the HTML in which the mixin attribute will be added later
<a-scene addasset>
<a-assets>
</a-assets>
<a-cylinder
position="0 0.5 -3">
</a-cylinder>
</a-scene>
Here is a glitch of that
And for the sake of exploration, here is the same set up but triggered by an example event. First the same component but with an event listener
// add assets at run time, triggered by event
AFRAME.registerComponent('addasset', {
init: function () {
document.addEventListener("testevent", function(){
var assets = document.getElementsByTagName('a-assets')[0]
var mixin = document.createElement('a-mixin')
var cylinder = document.getElementsByTagName('a-cylinder')[0]
mixin.setAttribute('id', 'makeitred')
mixin.setAttribute('material', 'color: red')
assets.appendChild(mixin)
cylinder.setAttribute('mixin', 'makeitred')
});
},
});
Then a component that emits an event for testing
// test event to trigger adding of mixin
AFRAME.registerComponent('testevent', {
init: function () {
var self = this.el
setTimeout(function(){
self.emit("testevent")
}, 3000);
},
});
Then the HTML, as before but including a test entity that emits an event
<a-scene addasset>
<a-assets>
</a-assets>
<a-cylinder
position="0 0.5 -3">
</a-cylinder>
<a-entity
testevent>
</a-entity>
</a-scene>
And here is a glitch for that
So you could mix those up, add the mixin to assets but delay/trigger on event the addition of properties or add the mixin to assets with properties but delay/trigger on event the setting of that attribute on your target elements.
I hope that helps

Related

Aframe.io component update/rebuild after onchange in another js file

Hello I am new in WebVR and I will be pleased If you can help me.
I want to update my AFRAME init() variable after onchange my input in test.js and callback aframe(rebuild Aframe component). I would like do somethink like useEffectfrom React.
I have two file test.js and aframe.js
test.js
const el = document.getElementById("inputItem")
el.setAttribute("type", "date")
el.addEventListener('input', (e)=> { console.log(e.target.value) }
//other logic
aframe.js
AFRAME.registerComponent("test", {
init(){
this.input = document.getElementById("inputItem")
this.input.addEventListener('input', (e)=> { console.log("input changed", e.target.value) } );
console.log("my input value", this.input)
}
update(){
if( this.input.value != document.getElementById("inputItem").value )
{this.input = "New input"
console.log("input changed", this.input)
}
}
// some code here to build aframe element and append it to html
}
so my console.log function inside aframe work only one time. After onchange input my update function doesn't response. I know the problem is aframe component is calling only one time.
I tried this answer and read about component. I don't know how to recall/rebuild afraime component after changing my input.
Looks like you've mixed two ways of doing this:
1. Using setAttribute() to trigger the update() functions.
The update function in the custom component is called:
once after init
after any change made with setAttribute(<component_name>)
Here's an example of triggering update with setAttribute from an "external" js file (same behaviour, as long as the script is loaded after the <input> element is attached) :
const input = document.getElementById("logtext"); // grab the input
input.addEventListener("input", e => {
// on each 'input' event
const text = e.target.value; // extract the text
// set the [logger] "text" attribute, to trigger the update
document.querySelector("[logger]").setAttribute("logger", "text", text);
})
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("logger", {
schema: {
text: {
default: ""
}
},
// on any update
update: function() {
this.el.setAttribute("text", "value", this.data.text); // update the text component
}
})
</script>
<div style="position: fixed; z-index: 999">
<label for="logtext">Text to be rendered:</label>
<input type="text" id="logtext" name="logtext"><br><br>
</div>
<a-scene>
<a-text position="-1 1.75 -3" value="" color="black" logger></a-text>
<a-box position="0 1 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
</a-scene>
2. Using event listeners.
You can add any logic you want to the event listener callbacks. It can modify you own component, it can modify other components.
Here's an example of modyfing another component with setAttribute() in reaction to an event coming from the <input> element:
<script src="https://aframe.io/releases/1.4.0/aframe.min.js"></script>
<script>
AFRAME.registerComponent("logger", {
init: function() {
// grab the input element
const input = document.getElementById("logtext");
// on "input", do whatever you need with the updates
input.addEventListener("input", e => {
const text = e.target.value; // extract the text from the event
this.el.setAttribute("text", "value", text); // update the text component
});
}
})
</script>
<div style="position: fixed; z-index: 999">
<label for="logtext">Text to be rendered:</label>
<input type="text" id="logtext" name="logtext"><br><br>
</div>
<a-scene>
<a-text position="-1 1.75 -3" value="" color="black" logger></a-text>
<a-box position="0 1 -3" rotation="0 45 0" color="#4CC3D9"></a-box>
</a-scene>
I think the first option is similar to react since:
using the schema is similar to keeping a component "state" (as in useState)
the update function reacts to the component changes (as is useEffect)

How to run A-Frame code from another file to prevent clutter?

Using A-Frame creates a lot of clutter. Is their a way to access the code from another file? If not then what do you recommend for preventing cluster? My code is public # https://repl.it/#ColbzDaBoss1/A-Frame-Test
Thanks :)
Colby Bianco
Use components: https://aframe.io/docs/0.9.0/introduction/writing-a-component.html
<script src="my-component.js"></script>
<a-scene>
<a-entity my-component></a-entity>
</a-scene>
component file:
AFRAME.registerComponent('my-component', {
init: function () {
}
});

Use one element to pause sound on another

I've reviewed this question and the solutions and am using the JS provided:
A-Frame - playing / pausing sound with a custom ('a-sound') source.
I'm trying to construct a popup that has text displayed but also a narration. I want to have a stop audio button so the user can stop the narration at will. If I use <a-sound>, I don't seem to be able to access/create child elements. If I use <a-entity> I get an error:
"Uncaught TypeError: Cannot read property 'playSound' of undefined".
This is the element:
<a-entity id="wrapper" position="0 0.5 1">
<a-entity narration id="sound" mixin="alpr" sound="src: #piano; autoplay: false; loop: false; volume: 10;">
<a-text id="close" mixin="close">
</a-text>
<a-text stopsound id="stop" mixin="stop-sound">
</a-text>
</a-entity>
This is the JS:
AFRAME.registerComponent('narration', {
init:function() {
let playing = false;
let audio = this.el.components.sound;
this.el.addEventListener('click', () => {
if(!playing) {
audio.playSound();
} else {
audio.stopSound();
}
playing = !playing;
});
this.el.addEventListener('mouseleave', () => {
audio.stopSound();
})
var stopaudio = document.querySelector('#stop');
stopaudio.addEventListener('click', () => {
audio.stopSound();
})
var closeaudio = document.querySelector('#close');
stopaudio.addEventListener('click', () => {
audio.stopSound();
})
}
})
Please let me know what I'm missing. Thanks!
The sound component is not yet initialized. Add sound to your narration component dependencies as described in the docs:
AFRAME.registerComponent(‘narration’,{
dependencies: [‘sound’],
...
}

a-asset-item loaded event (A-Frame 0.5.0)

I have the following assets:
<a-assets>
<a-asset-item id="model-obj" src="the-source..."></a-asset-item>
<a-asset-item id="model-mtl" src="another-source..."></a-asset-item>
</a-assets>
And after the scene is loaded I attached the following event listener which is never called and I don't know why (although the model loads and is shown in the scene):
document.querySelector('#model-obj').addEventListener('loaded', function() {
console.log('loaded');
});
According to the docs it should work (https://aframe.io/docs/0.5.0/core/asset-management-system.html#lt-a-asset-item-gt).
That's odd. This code works for me (but I'm using a glTF model):
<a-assets>
<a-asset-item id="duck" src="duck/duck.gltf"></a-asset-item>
</a-assets>
and
document.getElementById('duck').addEventListener('loaded', function() {
alert('ok')
})
A few pointers:
Is your script running after you've defined the element?
Does the network inspector show you an unsuccessful load (HTTP 404, 500, etc.)?
Any JS errors on your page in the console?

How to use JavaScript with A-Frame?

I see that A-Frame is used to build virtual reality experiences with just markup/HTML. How can I use JavaScript alongside the A-Frame markup elements?
A-Frame is a JavaScript/three.js framework, and the HTML is just the outermost declarative layer.
Manipulating the DOM
https://aframe.io/docs/0.2.0/core/entity.html
All elements/objects in A-Frame are entities. We can manipulate these elements using standard DOM methods.
var boxEl = document.querySelector('a-box');
boxEl.setAttribute('width', 5);
boxEl.setAttribute('color', '#FFF');
boxEl.addEventListener('click', function () {
// If we are using the cursor component.
boxEl.setAttribute('color', '#FFF');
});
boxEl.emit('some-event');
boxEl.removeAttribute('color');
boxEl.querySelectorAll('a-sphere');
var sphereEl = document.createElement('a-sphere');
sphereEl.setAttribute('radius', 1);
document.querySelector('a-scene').appendChild(sphereEl);
sphereEl.addEventListener('loaded', function () {
console.log('sphere attached');
});
Entity-Component
https://aframe.io/docs/0.2.0/core/
A-Frame is built on an entity-component-system pattern (ECS), popular in game development and used in engines such as React and PlayCanvas. In ECS, every object (or entity) is created from the ground up by composing components (not to be confused with Web Components). Components add logic, behavior, appearance to entities.
https://aframe.io/docs/0.2.0/core/component.html
We can encapsulate JS within components:
AFRAME.registerComponent('color-on-click', function () {
schema: {
color: {default: 'blue'}
},
init: function () {
var el = this.el; // Reference to the entity.
var data = this.data; // Data passed in through HTML, defined in schema.
el.addEventListener('click', function () {
el.setAttribute('color', data.color);
});
}
});
And attach these components to our entities in HTML. Notice how we are declaratively attaching/plugging in JS to objects in HTML that can take inputs!:
<a-box color="red" color-on-click="color: blue"></a-box>
<a-sphere color="orange" color-on-click="color: white"></a-sphere>
And these components can do anything beyond simple JS. We have access to the entire three.js API so anything in three.js can be easily wrapped. In fact, we wrap any JS library in the world we want in components and use them declaratively.
Manipulating Components
Manipulating properties of components is similar to manipulating HTML attributes. <a-box> consists of geometry and material components. So it looks like under the hood:
<a-entity id="box" geometry="primitive: box" material="color: red" scale="2 2 2"></a-entity>
We can manipulate the individual properties:
var boxEl = document.querySelector('#box');
boxEl.setAttribute('geometry', 'width', 5);
boxEl.getAttribute('material').color;
boxEl.removeAttribute('scale');
Integration
By exposing its API in HTML, A-Frame works well with existing web frameworks and libraries like d3, React, Angular, templating engines.
aframe-react
d3 example
d3 in a-frame component

Resources