EDIT (more specific):
I have the following situation. I am building an experimental synthesizer interface with JavaScript. I am using the howler.js plugin to handle audio.
The idea is that a sound is played if a certain condition is met. I have a number stored variable whose value changes via the heading of an iPhone compass.
I use compass.js. It has a variable called heading.
$(function(){
Compass.watch(function (heading) {
$('.degrees').text(Math.floor(heading) + '°');
$('#rotate').css('-webkit-transform', 'rotate(' + (-heading) + 'deg)');
if (heading >= 10 && heading <= 30) {
sound.play("myFirstSound");
}
else if (heading >= 31 && heading <= 60) {
sound.play("mySecondSound");
} else { etc etc...
});
});
Even if heading is constant the .play command is executed not once but over and over again causing the audio engine to run out of memory. How can I execute the commands within the if loops only once? I can not access the value of heading outside the compass function. I always get var heading does not exist error.
Take a Boolean variable like
var checkOnce= false;
if (myVariable >= 10 && myVariable <= 30) {
checkOnce=true;
sound.play("myFirstSound");
}
else if (myVariable >= 31 && myVariable <= 60) {
checkOnce=true;
sound.play("mySecondSound");
} else { etc etc...
In loop check the value of checkOnce. Loop will run till checkOnce=false
Create a boolean flag and play the sound only when false.
var isPlayed = false;
var oldValue = myVariable;
if (myVariable >= 10 && myVariable <= 30 && !isPlayed) {
sound.play("myFirstSound");
isPlayed = false;
}
Then initialize variable when needed... For what you say i guess you will need a variable to compare the values inside the loop and set isPlayed flag to false:
if (myVariable != oldValue)
isPlayed = false;
Just use a boolean variable.
var alreadyPlayed = false;
for(somecondition){
if(alreadyPlayed) continue;
sound.play("myFirstSound");
alreadyPlayed = true;
}
You get the idea.
The boolean value is a good idea. You could also make a use of the onend property of the sound object, so you will know when you can start playing the sound again. Like this:
var playing = false;
sound.onend = function () { playing = false; };
while (condition) {
if (!playing & ...)
...
...
}
Saw your edit to the question, add a delay to the next trigger, the compass might be firing too rapidly. Not familiar with compass.js but as I can see it, it fires per degree, and that is a lot... if you tilt the phone 180degrees thats 180 triggers.
Something like,
setTimeout(function() { your_func(); }, 50);
for a 50ms delay
You can also limit the triggers available from 360 to 36 or less by adding a counter of 10, 1 degree is for 1 count. So it triggers only after 10 degree movement.
$(function(){
var counter = 0;
Compass.watch(function (heading) {
if(counter <=10){
counter++;
}
else{
counter = 0;
$('.degrees').text(Math.floor(heading) + '°');
$('#rotate').css('-webkit-transform', 'rotate(' + (-heading) + 'deg)');
if (heading >= 10 && heading <= 30) {
sound.play("myFirstSound");
}
else if (heading >= 31 && heading <= 60) {
sound.play("mySecondSound");
} else { etc etc...
}
});
});
Add just a small layer of abstraction that will keep track of what's currently playing.
var nameOfSoundPlaying = null;
function play(soundName) {
if (nameOfSoundPlaying == soundName) {
return;
} else {
if (nameOfSoundPlaying != null) {
// stop currently playing sound (if still playing)
...
}
nameOfSoundPlaying = soundName;
sound.play(soundName);
}
}
Then it'll simplify your code :
$(function(){
Compass.watch(function (heading) {
$('.degrees').text(Math.floor(heading) + '°');
$('#rotate').css('-webkit-transform', 'rotate(' + (-heading) + 'deg)');
if (heading >= 10 && heading <= 30) {
play("myFirstSound");
}
else if (heading >= 31 && heading <= 60) {
play("mySecondSound");
} else { etc etc...
});
});
Related
I have been trying to make a simple "smoothscroll" function using location.href that triggers on the mousewheel. The main problem is that the EventListener(wheel..) gets a bunch of inputs over the span of ca. 0,9 seconds which keeps triggering the function. "I only want the function to run once".
In the code below I have tried to remove the eventlistener as soon as the function runs, which actually kinda work, the problem is that I want it to be added again, hence the timed function at the bottom. This also kinda work but I dont want to wait a full second to be able to scroll and if I set it to anything lover the function will run multiple times.
I've also tried doing it with conditions "the commented out true or false variables" which works perfectly aslong as you are only scrolling up and down but you cant scroll twice or down twice.
window.addEventListener('wheel', scrolltest, true);
function scrolltest(event) {
window.removeEventListener('wheel', scrolltest, true);
i = event.deltaY;
console.log(i);
if (webstate == 0) {
if (i < 0 && !upexecuted) {
// upexecuted = true;
location.href = "#forside";
// downexecuted = false;
} else if (i > 0 && !downexecuted) {
// downexecuted = true;
location.href = "#underside";
// upexecuted = false;
}
}
setTimeout(function(){ window.addEventListener('wheel', scrolltest, true); }, 1000);
}
I had hoped there was a way to stop the wheel from constantly produce inputs over atleast 0.9 seconds.
"note: don't know if it can help in some way but when the browser is not clicked (the active window) the wheel will registre only one value a nice 100 for down and -100 for up"
What you're trying to do is called "debouncing" or "throttling". (Those aren't exactly the same thing, but you can look up the difference in case it's going to matter to you.) Functions for this are built into libraries like lodash, but if using a library like that is too non-vanilla for what you have in mind, you can always define your own debounce function: https://www.geeksforgeeks.org/debouncing-in-javascript/
You might also want to look into requestanimationframe.
a different approach
okey after fiddeling with this for just about 2 days i got fustrated and started over. no matter what i did the browsers integrated "glide-scroll" was messing up the event trigger. anyway i decided to animate the scrolling myself and honestly it works better than i had imagined: here is my code if anyone want to do this:
var body = document.getElementsByTagName("BODY")[0];
var p1 = document.getElementById('page1');
var p2 = document.getElementById('page2');
var p3 = document.getElementById('page3');
var p4 = document.getElementById('page4');
var p5 = document.getElementById('page5');
var whatpage = 1;
var snap = 50;
var i = 0;
// this part is really just to read what "page" you are on if you update the site. if you add more pages you should remember to add it here too.
window.onload = setcurrentpage;
function setcurrentpage() {
if (window.pageYOffset == p1.offsetTop) {
whatpage = 1;
} else if (window.pageYOffset == p2.offsetTop) {
whatpage = 2;
} else if (window.pageYOffset == p3.offsetTop) {
whatpage = 3;
} else if (window.pageYOffset == p4.offsetTop) {
whatpage = 4;
} else if (window.pageYOffset == p5.offsetTop) {
whatpage = 5;
}
}
// this code is designet to automaticly work with any "id" you have aslong as you give it a variable called p"number" fx p10 as seen above.
function smoothscroll() {
var whatpagenext = whatpage+1;
var whatpageprev = whatpage-1;
var currentpage = window['p'+whatpage];
var nextpage = window['p'+whatpagenext];
var prevpage = window['p'+whatpageprev];
console.log(currentpage);
if (window.pageYOffset > currentpage.offsetTop + snap && window.pageYOffset < nextpage.offsetTop - snap){
body.style.overflowY = "hidden";
i++
window.scrollTo(0, window.pageYOffset+i);
if (window.pageYOffset <= nextpage.offsetTop + snap && window.pageYOffset >= nextpage.offsetTop - snap) {
i=0;
window.scrollTo(0, nextpage.offsetTop);
whatpage += 1;
body.style.overflowY = "initial";
}
} else if (window.pageYOffset < currentpage.offsetTop - snap && window.pageYOffset > prevpage.offsetTop + snap){
body.style.overflowY = "hidden";
i--
window.scrollTo(0, window.pageYOffset+i);
if (window.pageYOffset >= prevpage.offsetTop - snap && window.pageYOffset <= prevpage.offsetTop + snap) {
i=0;
window.scrollTo(0, prevpage.offsetTop);
whatpage -= 1;
body.style.overflowY = "initial";
}
}
}
to remove the scrollbar completely just add this to your stylesheet:
::-webkit-scrollbar {
width: 0px;
background: transparent;
}
I have created a function that when called with certain parameters will play out and then either tell the player they won or died based on the math. I have tested the function and it properly works, I set the parameters in the function calling so that the player would win and it displayed the win message alert. But if I increase the monster health to where the player will not win the first time the function is called, the function will not repeat itself as I tried to set up with the "else fight()" (as seen below).
My question is this, how to loop the function with the same parameters as before until the else or else if arguments are met?
Calling the function in HTML:
<a class="button" onclick="javascript:fight(8, 5, 1, 10, 3);">Test fight</a><br>
JS Function:
var playerHealth = 100;
function fight(monsterHealth, monsterDmg, monsterArmor, itemDmg, armorSts) {
newPlayerHealth = playerHealth - monsterDmg / armorSts + 2;
newMonsterHealth = monsterHealth - itemDmg / monsterArmor + 2;
if (playerHealth <= 0) {
alert('You died...');
}
else if (newMonsterHealth <= 0) {
alert('You won!');
}
else
{
var newPlayerHealth = playerHealth;
fight(newMonsterHealth, monsterDmg, monsterArmor, itemDmg, armorSts);
}
}
EDIT: Updated function and it says you win no matter how high the monster's damage or health is.
In your code playerHealth was intialize inside function which was resetting players health to 100 in recursive call.
newMonsterHealth was also not persisted considering recursive calls.
<html>
<head>
<script>
var playerHealth = 100;
function fight(monsterHealth, monsterDmg, monsterArmor, itemDmg, armorSts) {
playerHealth = playerHealth - Math.floor(monsterDmg / armorSts) ;
console.log("player helath"+playerHealth);
monsterHealth = monsterHealth - Math.floor(itemDmg / monsterArmor) ;
console.log("monster helath"+monsterHealth);
if (playerHealth <= 0) {
alert('You died...');
}
else if (monsterHealth <= 0) {
alert('You won!');
}
else fight(monsterHealth, monsterDmg, monsterArmor, itemDmg, armorSts);
}
</script>
</head>
<body>
<a class="button" onclick="javascript:fight(100, 40, 1, 10, 3);">Test fight</a><br>
</body>
</html>
P.S. I removed your +2 logic (I won't understood why you were adding)
You probably need for a recursive function that ends when certain condition will be met.
here is an example:
function recursive(target, i) {
target.innerHTML += '<br>' + i;
console.log('index is', i);
if(i > 100) {
return target.innerHTML += '<br>' + 'END';
}
return recursive(target, ++i);
}
document.addEventListener('DOMContentLoaded', function() {
recursive(document.getElementById('test'), 0)
});
<h1 id="test"></h1>
I was helped along a while back with some code for changing background color in a table cell; the final solution works very well:
change between 3 different background color based on cell value
Now I'd like to add another condition for this, please take a look here to see what I mean: unable to post link: jsfiddle dot net/Bouncer/LeyqceLe/4/
Is this at all possible without loosing the current function?
Here it is my friend: jsFiddle
It gets the value of avai :
var diff = $('td.avai').html();
then checks if the value of the avai cell is other than 20, and skips the cell colouring:
if(diff != 20) { ... }
if i understand right this is what you want?
// JavaScript Document
var cell = $('td.bg_status');
// Get the td value
var avai_value = $('.avai').text();
cell.each(function() {
var cell_value = $(this).html();
// continue if td value is not 20
if(avai_value!=20){
if ((cell_value >= 0) && (cell_value <=19)) {
$(this).css({'background' : '#FF9900'});
} else if ((cell_value >= 20) && (cell_value <=39)) {
$(this).css({'background' : '#99cc00'});
} else if (cell_value >= 40) {
$(this).css({'background' : '#99ccff'});
}
}
});
I forgot to mention earlier that my site is running Joomla! 3. I was just informed that Joomla doesn't allow to use $ signs for a while for jQuery codes. Also the line with avai_value caused an error and had to be rewritten like so:
// JavaScript Document
if(jQuery('td.avai').length){
var cell = jQuery('td.bg_status');
var diff = jQuery('td.avai').html();
cell.each(function() {
var cell_value = jQuery(this).html();
if(diff != 20) {
if ((cell_value >= 0) && (cell_value <=19)) {
jQuery(this).css({'background' : '#FF9900'});
} else if ((cell_value >= 20) && (cell_value <=39)) {
jQuery(this).css({'background' : '#99cc00'});
} else if (cell_value >= 40) {
jQuery(this).css({'background' : '#99ccff'});
}
}
});
}
I'm trying to implement touch evens with jGestures. swipeone works fine but anything else (swipeleft, swiperight etc) is not firing.
<div id="wrap" style="height:500px; width:500px; background: blue;">
</div>
<script type="text/javascript">
$('#wrap').bind('swipeleft', function(){
alert("test");
});
</script>
This is just a test page I did. It was actually working at one point in my main project but seemed to have stopped for no reason at all, not even when I reverted to an older version. I've tried a different version of jGestures with no luck.
SwipeLeft, SwipeRight, -Up and -Down are kind of poorly implemented. They are only triggered if you stay EXACTLY on the axis where you started the touch event.
For example, SwipeRight will only work if your finger moves from (X,Y) (120, 0) to (250, 0).
If the Y-Coordinates from Start- and Endpoint differ, it's not gonna work.
jGestures.js (ca. line 1095) should better look something like this (readable):
/**
* U Up, LU LeftUp, RU RightUp, etc.
*
* \U|U/
* LU\|/RU
*L---+---R
* LD/|\RD
* /D|D\
*
*/
if ( _bHasTouches && _bHasMoved === true && _bHasSwipeGesture===true) {
_bIsSwipe = true;
var deltaX = _oDetails.delta[0].lastX;
var deltaY = _oDetails.delta[0].lastY;
var hor = ver = '';
if (deltaX > 0) { // right
hor = 'right';
if (deltaY > 0) {
ver = 'down'
} else {
ver = 'up';
}
if (Math.abs(deltaY) < deltaX * 0.3) {
ver = '';
} else if (Math.abs(deltaY) >= deltaX * 2.2) {
hor = '';
}
} else { // left
hor = 'left';
if (deltaY > 0) {
ver = 'down'
} else {
ver = 'up';
}
if (Math.abs(deltaY) < Math.abs(deltaX) * 0.3) {
ver = '';
} else if (Math.abs(deltaY) > Math.abs(deltaX) * 2.2) {
hor = '';
}
}
// (_oDetails.delta[0].lastX < 0) -> 'left'
// (_oDetails.delta[0].lastY > 0) -> 'down'
// (_oDetails.delta[0].lastY < 0) -> 'up'
// alert('function swipe_' + hor + '_' + ver);
_oDetails.type = ['swipe', hor, ver].join('');
_$element.triggerHandler(_oDetails.type, _oDetails);
}
try this:
$('#wrap').bind('swipeone', function (event, obj) {
var direction=obj.description.split(":")[2]
if(direction=="left"){
doSomething();
}
});
This is already answered here:
stackoverflow about jGesture swipe events
The trick is:
… .bind('swipeone swiperight', …
You have to bind it to both events. only swiperight won't work. took me 3 hrs to figure that out :D
Best,
Rico
replace the cases on line 1326 in version 0.90.1 with this code
if ( _bHasTouches && _bHasMoved === true && _bHasSwipeGesture===true) {
_bIsSwipe = true;
_oDetails.type = 'swipe';
_vLimit = $(window).height()/4;
_wLimit = $(window).width()/4;
_sMoveY = _oDetails.delta[0].lastY;
_sMoveX = _oDetails.delta[0].lastX;
_sMoveYCompare = _sMoveY.toString().replace('-','');
_sMoveXCompare = _sMoveX.toString().replace('-','');
if(_sMoveX < 0){
if(_oDetails.delta[0].lastY < _vLimit) {
if(_sMoveYCompare < _vLimit) {
_oDetails.type += 'left';
}
}
}else if(_sMoveX > 0){
if(_sMoveYCompare < _vLimit) {
_oDetails.type += 'right'
}
}else{
_oDetails.type += '';
}
if(_sMoveY < 0){
if(_sMoveXCompare < _wLimit) {
_oDetails.type += 'up'
}
}else if(_sMoveY > 0){
if(_sMoveXCompare < _wLimit) {
_oDetails.type += 'down'
}
}else{
_oDetails.type += '';
}
// alert(_oDetails.type);
_$element.triggerHandler(_oDetails.type, _oDetails);
}
You could try both:
$('#wrap').bind('swipeleftup', function(){
doSomething();
});
$('#wrap').bind('swipeleftdown', function(){
doSomething();
});
And forget about 'swipeleft' as is quite difficult to trigger, as mentioned before.
I was just looking for the same thing and figured out that you can try all in the same function like so:
$("#wrap").on('swipeleft swipeleftup swipeleftdown', function(e){
doSomething();
});
as well as the equivalent for right:
$("#wrap").on('swiperight swiperightup swiperightdown', function(e){
doSomething();
});
You can list multiple events together with the on method.
I'm using Willian El-Turk's solution like this:
// jGestures http://stackoverflow.com/a/14403116/796538
$('.slides').bind('swipeone', function (event, obj) {
var direction=obj.description.split(":")[2]
if (direction=="left"){
//alert("left");
} else if (direction=="right"){
//alert("right");
}
});
Excellent solution, except because it executes as soon as there's movement left to right it's really sensitive even with more of a vertical swipe. it'd be great to have a minimum swipe distance on the x axis. Something like:
if (direction=="left" && distance>=50px){
Except i'm not sure how... Please feel free to edit this !
Edit - You can check distance (x axis) like this, it works for me :
$('.slides').bind('swipeone', function (event, obj) {
var xMovement = Math.abs(obj.delta[0].lastX);
var direction=obj.description.split(":")[2]
//I think 75 treshold is good enough. You can change this.
if(xMovement >= 75) {
//continue to event
//ONLY x axis swipe here. (left-right)
if (direction=="left"){
//alert("left");
} else if (direction=="right"){
//alert("right");
}
}
}
I have a Javascript file that I am using to try to animate a dropdown menu. I have the "Toggle" function in that file set to run when I click on a certain div. Here's the script I'm using:
var up = true;
function Toggle(x)
{
if (up)
{
for (var i = x.offsetTop; i <= 0; i++)
{
x.style.top = i;
if (i == 0)
{
up = false;
}
}
}
else if (up == false)
{
for (var i = x.offsetTop; i >= -50; i--)
{
x.style.top = i;
if (i == -50)
{
up = true;
}
}
}
}
In the HTML div I want to animate, I have the "onclick" property set to "onclick=Toggle(this)". The first for loop works as it should (it sets the div's top offset to 0). However, the second for loop doesn't set the offsetTop. I know that the for loop is activating because I've tested it and it gives me every integer between 0 and -50. Why isn't it setting the offset position?
1) You must specify a unit to the top ie: x.style.top = i +"px"
2) Your function won't animate instead of you use a setInterval or a setTimeout
Like you asked, an example. I wouldn't do it like this for one of my project, but i kept your function to make you more familiar with the code.
I Used setTimeout instead of setInterval because setInterval must be cleared when not needed and setTimeout is just launched one time :
var Toggle = (function() { // use scope to define up/down
var up = true;
return function(element) {
var top = parseInt(element.style.top, 10); // element.offsetTop ?
if ( !top ) {
top = 0;
}
if (up) {
if (element.offsetTop < 0) { // if we are not at 0 and want to get up
element.style.top = (top+1) + "px";
setTimeout(function() { Toggle(element); }, 10); // recall the function in 10 ms
} else { // we change up value
up = !up;
}
}
else {
if (element.offsetTop > -50) {
element.style.top = (top-1) + "px";
setTimeout(function() { Toggle(element); }, 10); // recall the function in 10 ms
} else {
up=!up;
}
}
}
})();
You'd have to use x.style.top = i + 'px' as top and similar css properties must define the type (px, em, %, etc.) unless they are 0, as this is 0 in any case.
But your script would actually snap the div directly to -50px, as you do not wait between those iteration steps.
I'd recommend to use a library like jQuery to use it's animate() method.
function Toggle(obj) {
$(obj).animate({
top: parseInt($(obj).css('top')) === 0 ? '-50px' : '0px'
})
}