Use one element to pause sound on another - aframe

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’],
...
}

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 use setState after a css transition ends in React JS?

//sample piece of codes
constructor() {
super()
this.state.opacity= '0'
this.state.mover = 'translateY(-40%)'
}
this.setState({opacity:'1'})
this.setState({mover: 'translateY(-900%)'}, () => {this.setState({opacity:'0'})})
when I click on a button, I want a div to appear (using opacity 1) and transition to top and fade out after reaching the top(using opacity 0).
But it didn't work the way I expected. Currently, it fades out instantly. It doesn't wait for the transition to end. I want it to fade out after the transition ends.
Is there a way in React to fix it ? I am very new in react. Help is much appreciated. Thanks.
Found an easy workaround for this. I am not sure if this is the right way, but it works.
constructor() {
super()
this.state.opacity= '0'
this.state.mover = 'translateY(-40%)'
}
this.setState({opacity:'1'})
this.setState({mover: 'translateY(-900%)'}, () => {
setTimeout(() => { this.setState({ opacity: '0' })}, 1000);
})
Inside the call back function, I setup a settimeout function. The event inside the settimeout function will be triggered after xxx milliseconds. So basically you will have to calculate the duration of your previous transition and set the time accordingly.
How about using transitionend event listener?
yourDivElement.addEventListener('transitionEnd', (event) => {
this.setState({ yourProp: "your-new-value" })
}, false );
Annoyingly enough, you may need to add different event names for cross browser compatibility:
["transitionend", "webkitTransitionEnd", "mozTransitionEnd"].forEach((eventName) => {
yourDivElement.addEventListener(eventName, (event) => {
this.setState({ yourProp: "your-new-value" })
}, false );
})
Make sure you refer to the DOM element using ref.
Browser compatibility
Source

Aframe remove component inside for loop

I have a html template similar to the below code
<a-entity id="id1">
<a-entity template="src: t1.template;type:nunjucks">
</a-entity>
</a-entity>
t1.template
<a-entity id="id2">
{% for i in 4 %}
<a-entity template="src: t2.template; type: nunjucks"></a-entity>
{% endfor %}
</a-entity>
t2.template
<a-entity id="id3" myComponent="x:4">
<a-entity>...
<a-entity>...</a-entity>
</a-entity>
<a-entity>
Entity components are displayed in the screen as required. I now want to remove the entire id1 when user clicks on any of the 4 component in id3. My component code is as below
AFRAME.registerComponent('myComponent', {
schema: {
x: {type: 'number', default: 0}
},
update: function () {
//set some attribute for entities inside id3
//adding event listener to id3.
this.el.addEventListener('click', function () {
setTimeout(function () {
var categoryEl = scene.querySelectorAll('#id3');
totalCategory = categoryEl.length;
for(i=0;i<totalCategory;i++){
categoryEl[i].removeAttribute('myComponent');
removeAttributeCount++;
}
}, 1500);
});
},
remove: function () {
//To check whether component is removed from all element
if(removeAttributeCount == totalCategory){
var id1 = this.el.sceneEl.querySelector('#id1');
id1.parentNode.removeChild(id1);
}
}
});
I am getting error as
Uncaught TypeError: Cannot convert undefined or null to object
at NewComponent.remove (https://cdn.rawgit.com/donmccurdy/aframe-extras/v3.2.7/dist/aframe-extras.js:4742:16)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:71889:17)
at bound (https://aframe.io/releases/0.5.0/aframe.js:76993:17)
at Array.forEach (native)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:71567:36)
at NewComponent.remove (http://localhost:63342/myProj1HTML/Shop/ShopTrail-1/index.js:91:32)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:71889:17)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:71970:16)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:72095:14)
at HTMLElement.value (https://aframe.io/releases/0.5.0/aframe.js:72015:14)
All the elements are captured perfectly. But error occurs on removing the child from parent node.
Someone please help to get this working. Thanks in advance

ReactTransitionGroup Lower Level API

Im really new to React and animation and I am trying to animate my components with ReactTransitionGroup and I am not quite sure how to do it. None of the ReactTransitionGroup lifecycle methods (componentWillAppear or ComponentDidAppear) are being called.
var React = require('react');
var ReactTransitionGroup = require('react-addons-transition-group');
var App = React.createClass({
render: function(){
return (
<div>
<h3>Type in the box below to watch it change color.</h3>
<div>
<ReactTransitionGroup component={List}>
{this.props.children}
</ReactTransitionGroup>
</div>
</div>
);
}
});
var List = React.createClass({
componentWillAppear: function(callback){
console.log('componentWillAppear');
setTimeout(callback, 1);
},
componentDidAppear: function(){
console.log('componentDidAppear');
},
componentWillLeave: function(callback){
console.log('componentWillLeave');
},
componentDidLeave: function(){
console.log('componentWillLeave');
},
render: function(){
return <div>{this.props.children}</div>
}
});
module.exports = App;
why aren't these ReactTransitionGroup hooks being called?? Please help.
Your problem is your attaching the life cycle methods to your custom component which is the Parent of the animating children.
From the docs:
When children are declaratively added or removed from it (as in the example above) special lifecycle hooks are called on them.
So it's the {this.props.children} which are expecting the life cycle methods, not List.
children need a key
change:
{this.props.children}
to:
{React.cloneElement(this.props.children, {
key: Math.random()
})}
Do your testing rendering hard coded content in List, try:
render: function(){
return Hello world
}
Also, ReactTransitionGroup takes propeties, you might be looking for an "appear" transition like documented here: https://facebook.github.io/react/docs/animation.html#getting-started

Google Maps on PhoneGap not showing Full

I have a problem where my Google Maps is only showing top tiles (much like this issue PhoneGap + JQuery Mobile + Google Maps v3: map shows Top Left tiles?) but in iOS and with jQuery UI Map.
However this only occurs after I play around with the app for some time switching tabs (it works fine on a Desktop Browser, only fails on the Device App)
I've tried several solutions from other posts (as you can see from the code) but my problem is a bit different, as it doesn't happen at first
Here is my HTML
<div data-role="page" id="page3" data-url="page3" tabindex="0" style="padding-bottom:19px">
....
<div data-role="content" id="ct">
<div id="map_canvas" style="height:100%"></div>
</div>
....
</div>
And JS
$(document).bind('pagechange', function () {
if ($.mobile.activePage.attr('id') === 'page3') {
if (!mapInited) {
mapInited = true;
$('#map_canvas').gmap().bind('init', function () {
var bounds = new google.maps.LatLngBounds();
navigator.geolocation.getCurrentPosition(locSuccess, locError);
$.each(markers, function (i, marker) {
var latlong = new google.maps.LatLng(marker.latitude, marker.longitude);
bounds.extend(latlong);
$('#map_canvas').gmap('addMarker', {
'position': latlong,
'bounds': true,
'primaryColor': "#0000FF",
'icon': './img/train.png'
}).click(function () {
$('#map_canvas').gmap('openInfoWindow', {
'content': marker.content
}, this);
});
});
$('#map_canvas').css('height', getRealContentHeight());
$('#map_canvas').css('width', '100%');
google.maps.event.trigger($('#map_canvas'), "resize");
setTimeout(function () {
google.maps.event.trigger($('#map_canvas'), 'resize');
}, 500);
});
}
}
});
}
Thanks in advance for any thoughts
"Fixed" the issue with a very ugly work around
Basically I recreate the Map everytime the page is loaded, like this
if (!mapInited) mapInited = true;
else { $('#map_canvas').remove(); $('#ct').append('<div id="map_canvas" style="height:100%"></div>'); }
You trigger the resize-event for a jQuery-object, what will not have any effect, because you must trigger the event for the google.maps.Map-instance:
google.maps.event.trigger($('#map_canvas').gmap('get','map'),'resize');
You may also use the plugin-method triggerEvent to trigger the event:
$('#map_canvas').gmap().triggerEvent('resize');
If you are using jquery-ui-map, why are you using the native google maps api instead of the gmap functions?
Why not call the $('#map_canvas').gmap('refresh');

Resources