I'm trying to "animate" a div's css by choosing a random class at random intervals. Currently i'm using a recursive function that looks like this:
$scope.spin = function() {
console.log('spinning');
var maxCycle = 100;
var currentCycle = 0;
recursiveRandomChange();
function recursiveRandomChange() {
if (currentCycle <= maxCycle) {
currentCycle += 1;
console.log(currentCycle);
$interval(blockOne(), getRandomTime());
recursiveRandomChange();
}
}
function blockOne() {
var currentClass = getRandomClass();
$scope.engine.one = currentClass;
console.log('changing color ', currentClass);
}
};
As you can see it only allows for 100 cycles but it's supposed to set "blockOne"s class ($scope.engine.one) to a random class everytime the interval finishes. I can see in the console log the code running correctly but instead of cycling through 100 random classes at random times it only changes once (from the original color to the class randomly picked on the 100th iteration).
Any advice is appreciated. Thanks!
check this: http://jsfiddle.net/pfgkna8k/4/
Inside recursiveRandomChange, you were recursively calling recursiveRandomChange & using $interval too. $interval is itself recursive.
angular.module('lists', []).controller('ProductList', function($scope, $interval) {
var getRandomTime = function() {
return 1000;
};
$scope.spin = function() {
console.log('spinning');
var maxCycle = 100;
var currentCycle = 0;
recursiveRandomChange();
function recursiveRandomChange() {
if (currentCycle <= maxCycle) {
currentCycle += 1;
console.log(currentCycle);
$interval(blockOne, getRandomTime());
//recursiveRandomChange();
}
}
function blockOne() {
var currentClass = getRandomClass();
//$scope.engine.one = currentClass;
var element = document.querySelector("#test");
element.className = currentClass;
console.log('changing color ', currentClass);
}
function getRandomClass() {
var classes = ["abc", "abc1", "abc2", "abc3"];
return classes[Math.round(Math.random() * 3)];
}
};
$scope.spin();
});
What's needed is $timeout not $interval as you want to change the color-changing delay period differently each time. And you want to repeat this randomness for 100 times.
$timeout is to do it once. $interval is to do it multiple times at a fixed interval.
$interval(blockOne, getRandomTime()); in Ayush's answer doesn't specify how many times blockOne will run. Thus, it will run forever..
I put some logging in Ayush code, you will see the random delay period is never changed.
What you really want is chaining 100 $timeout so that it runs one after another but not in parallel.
You will need to wrap function around each $timeout and then chain them later.
var changes = [];
changes[0] = function() {
return $timeout(setColor,getRandomTime());
}
changes[1] = function() {
return $timeout(setColor,getRandomTime());
}
...
// Chain them
changes[0]()
.then(changes[1])
.then(changes[2]);
In this case, you want to chain 100 times. We don't have to type .then() 100 times as each $timeout.then() is still a promise.
var executeChanges = changes[0]().then(changes[1]);
executeChanges = executeChanges.then(changes[2]);
I created a working demo
Related
I'm working on a page in which one element ('.item--itemprice') updates its text through another function that I'd prefer not to touch. What I'd like to do is get another element ('.header--itemprice') to update so that its text matches the first element.
Unfortunately, it seems that handler below is acting faster than the updating function. As a result, the header either stays with the previous text or changes to a blank string. Is there a way to delay the final line below until after the first element is finished updating?
$('select').on('change', function() {
const headPrice = document.querySelector('.header--itemprice');
const lowerPrice = document.querySelector('span.item--itemprice');
const $lowerText = $(lowerPrice).text();
$(headPrice).text($lowerText);
});
Here's the preexisting function:
$(document).ready( function () {
$('#txtQuantity, .ProductGroupItemQuantity').blur(updatePrice);
});
function updatePrice() {
var itemPriceEl = $('.item--itemprice');
var itemCountEl = $('#txtQuantity');
var groupUpdateEl = $('#lnkProductGroupUpdatePrice');
var groupPriceEl = $('.pdetail--price-total');
var totalPriceEl = $('.ProductDetailsPricing');
var itemPrice = moneyToNumber(itemPriceEl.text());
var itemCount = moneyToNumber(itemCountEl.val());
var itemTotalPrice = itemCount * itemPrice;
var groupTotalPrice = 0;
// Trigger Group Update
groupUpdateEl.click();
groupTotalPrice = moneyToNumber(groupPriceEl.text());
// Calculate Total Price
totalPriceEl.text('Total: $' + Number(groupTotalPrice + itemTotalPrice) / 100);
}
/*$('select').on('change', function() {
const headPrice = document.querySelector('.header--itemprice');
const lowerPrice = document.querySelector('span.item--itemprice');
const $lowerText = $(lowerPrice).text();
$(headPrice).text($lowerText);
});*/
function moneyToNumber(moneyEl) {
try {
return Number(moneyEl.replace(/[^0-9\.]+/g,"").replace(/\D/g,''));
} catch (err) {
return 0;
}
}
If you don't want to touch the other function at all and assuming it is also being called on the change event of select. A really hacky way could be, something like this -
$('select').on('change', function() {
setTimeout (function()
{
const headPrice = document.querySelector('.header--itemprice');
const lowerPrice = document.querySelector('span.item--itemprice');
const $lowerText = $(lowerPrice).text();
$(headPrice).text($lowerText);
}, 0);
});
In that case, the better way is changing the function, you can even trigger a event when the function is executed and watch this event to trigger the other function to change the element ('.header--itemprice')
I am attempting to have the carousel start immediately upon visiting the page and cycle through a series of images every 2.5 seconds, however it will not advance past the first image. I've looked around here and w3schools but haven't been able to locate where my issue is. Any help would be greatly appreciated!
JavaScript is as follows:
window.onload slideShow();
var i=0;
function slideShow() {
window.setInterval("nextSlide()", 2500);
}
function nextSlide() {
var images["images/stockton0.jpg",
"images/stockton1.jpg"
"images/stockton2.jpg"
"images/stockton3.jpg"
"images/stockton4.jpg"
"images/stockton5.jpg"
"images/stockton6.jpg"
"images/stockton7.jpg"
"images/stockton8.jpg"
"images/stockton9.jpg"
"images/stockton10.jpg"]
var photo = document.getElementByClass("stocktonPics");
photo.src = images[i];
i++;
}
HTML code:
<img class="stocktonPics" src="images/stockton0.jpg" alt="slides">
A good tip is to check the console for errors.
There's nothing wrong with the flow of your code, besides some tips on making it more maintainable, readable, or semantically correct.
You simply forgot an = in window.onload = slideShow;
And document.getElementByClass doesn't exist. You need to use document.getElementsByClassName(...) to get an array of elements with that class, and finally get its first item with [0] like so:
var photo = document.getElementsByClassName("stocktonPics")[0];
Note that slideShow no longer has the () to call it, when window.onload is assigned to it. This is because you're assigning window.onload to the slideShow function, not the result of calling slideShow(), which in this case is undefined, as nothing is returned.
Your image array should be assigned in this way: var images = [ a, b, c ]
The other thing you should do is keep the array of images outside the scope of the function, so you can use it more easily and change it, rather than creating one (if you don't count optimizations) ever time the function is called. And lastly, window.setInterval( a, b ) can take either a string that will be eval()ed (or equivalent to it), which is what you did, or a function itself. In your case, what you want is simply the function: window.setInterval( nextSlide, 2500 ).
Here's the final full code:
var i=0;
var images=[
"images/stockton0.jpg",
"images/stockton1.jpg",
"images/stockton2.jpg",
"images/stockton3.jpg",
"images/stockton4.jpg",
"images/stockton5.jpg",
"images/stockton6.jpg",
"images/stockton7.jpg",
"images/stockton8.jpg",
"images/stockton9.jpg",
"images/stockton10.jpg" ];
function slideShow() {
window.setInterval( nextSlide, 2500);
}
function nextSlide() {
var photo = document.getElementsByClassName("stocktonPics")[0];
photo.src = images[i];
i++;
}
window.onload = slideShow;
getElementsByClass
method is will get a like array elements object,
so, you should use [0] to get the first element object and set src attr.
var pics = document.getElementsByClassName("stocktonPics")
It's a like array elements object
var firstPic = document.getElementsByClassName("stocktonPics")[0]
It's a element object
let i = 0;
const images = ["images/stockton0.jpg",
"images/stockton1.jpg",
"images/stockton2.jpg",
"images/stockton3.jpg",
"images/stockton4.jpg",
"images/stockton5.jpg",
"images/stockton6.jpg",
"images/stockton7.jpg",
"images/stockton8.jpg",
"images/stockton9.jpg",
"images/stockton10.jpg"];
const stockton = document.getElementsByClassName("example")[0];
const slideShow = () => {
window.setInterval(nextSlide, 2500);
}
const nextSlide = () => {
stockton.src = images[i];
if(i >= images.length - 1) {
i = 0;
} else {
i++;
}
console.log(i, stockton)
}
slideShow()
<img class="example">
I work with javascript. Let's suppose I have the following function in my app:
function verify(cats) {
if ( cats > 20 ) {
// do something
}
}
Supposing i have a button to add cats, I may use that function after every cat adding.
However, i don't like this method.
I want that this function be in the background and is executed automatically when the condition states true
Is there some way to do so ?
Use a setter, that runs on every assignment. Before:
var obj = {};
obj.cats = 10;
obj.cats += 30;
verify(obj.cats); // we don't want to call this each time.
After:
var obj = {
_cats : 0, // private
get cats() { return this._cats; },
set cats(num) {
verify(num); // any verification here
this._cats = num;
}
};
Later, you can do:
obj.cats += 10; // will run verification
obj.cats = 15; // same
Alternatively, you can use a proxy, but those aren't really widely supported by JS engines yet.
How about setInterval()
$(function() {
setInterval(
function verify(cats) {
if ( cats > 20 ) {
// do something
}
}
, 10);
})
sorry am still learning JavaScript, read W3Cschools javascript and Jquery but there is a lot they don't teach.
I am studying animation at the moment, how do I auto start this rather then wait for someone to click (event listener), I've attempted turning it into a function but I must be doing it wrong, also 1 more what does (Idx) mean, I understand (id) is Html ID element but not sure Idx, not easy to find on google. to read, event listener starts at 5th line from the bottom, and the shuffle cards is 6th line from top (not sure if that helps), original code is located here http://www.the-art-of-web.com/javascript/css-animation/ thanks for any help.
Regards. William.
var cardClick = function(id)
{
if(started) {
showCard(id);
} else {
// shuffle and deal cards
card_value.sort(function() { return Math.round(Math.random()) - 0.5; });
for(i=0; i < 16; i++) {
(function(idx) {
setTimeout(function() { moveToPlace(idx); }, idx * 100);
})(i);
}
started = true;
}
};
// initialise
var stage = document.getElementById(targetId);
var felt = document.createElement("div");
felt.id = "felt";
stage.appendChild(felt);
// template for card
var card = document.createElement("div");
card.innerHTML = "<img src=\"/images/cards/back.png\">";
for(var i=0; i < 16; i++) {
var newCard = card.cloneNode(true);
newCard.fromtop = 15 + 120 * Math.floor(i/4);
newCard.fromleft = 70 + 100 * (i%4);
(function(idx) {
newCard.addEventListener("click", function() { cardClick(idx); }, false);
})(i);
felt.appendChild(newCard);
cards.push(newCard);
I've gone through your code and added comments to try and help explain what is going on in this file:
//Declare card click function. Takes one parameter (id)
var cardClick = function(id){
if(started) {
showCard(id);
} else {
// shuffle and deal cards
card_value.sort(function() {
return Math.round(Math.random()) - 0.5;
});
for(i=0; i < 16; i++) {
(function(idx) {
setTimeout(function() {
moveToPlace(idx);
}, idx * 100);
})(i);
}
started = true;
}
};
// initialise
//set stage as reference to some element
var stage = document.getElementById(targetId);
//append a div with ID "felt" to the stage element
var felt = document.createElement("div");
felt.id = "felt";
stage.appendChild(felt);
// template for card
//declare card variable as a div with some html content
var card = document.createElement("div");
card.innerHTML = "<img src=\"/images/cards/back.png\">";
//Loop from 0 to 16, where i = current value
for(var i=0; i < 16; i++) {
//create a copy of the card made earlier
var newCard = card.cloneNode(true);
//apply some attributes to the new card
newCard.fromtop = 15 + 120 * Math.floor(i/4);
newCard.fromleft = 70 + 100 * (i%4);
//Create and run an anonymous function.
//The function takes one parameter (idx)
//The function is called using (i) as (idx)
(function(idx) {
//add click handler to the card element that triggers the card click
//function with parameter (idx)
newCard.addEventListener("click", function() { cardClick(idx); }, false);
})(i);
//add new card to the stage
felt.appendChild(newCard);
//add new card to an array of cards
cards.push(newCard);
} //end for loop (I added this. It should be here)
how do I auto start this rather then wait for someone to click
The way I would do it, is add a manual click event after the for loop that targets the first card that has the event handler. Because there is no ID set on the cards, I would try using the array that the cards are added to. Assuming that the cards array was empty when we started:
cards[0].click();
If that doesn't work, I would try targeting the item in the DOM. We know that each card is added to the end of div#felt. So, if we can target the first div inside felt, we should be able to trigger the click event on it.
document.getElementByID("felt").firstChild.click();
what does (Idx) mean
I'm hoping the comments help explain this. It looks like the variable idx is just used as an extended reference of i. Inside a for loop, the writer creates a function that takes one parameter (idx). The for loop has a variable (i) that increases by one for each instance of the loop. Each time the loop happens, i is passed into function as idx.
I hope that helps to get you an understanding of how this code works.
I often see questions like this one and there are multiple solutions. I'm trying to come up with something short that can be reusable. My question is, given the following code, do I need to clearTimeout() and where to do it? And also, anything you would improve? How good or bad is this piece of code for performance?
http://jsfiddle.net/elclanrs/fQX8M/15/
var fade1by1 = function ($elms, props) {
props = props || {};
props.delay = props.delay || 1; // s
props.speed = props.speed || 400; // ms
props.ease = props.ease || 'linear';
for (var i=0, d=0, l=$elms.length; i<l; i++, d+=props.delay*1000) {
(function (i, d) {
// Using `delay()` instead of `setTimeout()`
// as Alexander suggested
$elms.eq(i).delay(d).fadeIn(props.speed, props.ease);
})(i, d);
}
};
I don't think you need to window.clearTimeout since it does not seem like you want to stop the animation. If you are still undecided then what about using .delay, it clearly uses window.setTimeout also.
var fade1by1 = function ($elms, speed) {
speed = speed || 1; // Seconds
for (var i=0, s=0, l=$elms.length; i<l; i++, s+=speed*1000) {
$elms.eq(i).delay(s).fadeIn('slow');
}
};
See it in action here.
Seems to me you should do this with the fadeIn function's callback. Something like the following should accomplish all of your goals (substitute in your new params):
var customFade = function(parent, speed){
$(':hidden:first', parent).fadeIn(speed, function(){
customFade(parent, speed)
});
}
$('button').click(function(){ customFade($('ul'), 1000); });
Instead of setting all the actions at once with increasingly long wait periods, another approach is to bind the show behavior directly to each selected element as a custom event, and include in that bound function a fixed waiting period plus a call to trigger the custom event of the "next" element, if present. To get it started, you just light the fuse of the first element.
So, something like this:
var fadeCascade = function fadeCascade(your_selector, props) {
props = props || {};
props.delay = props.delay || 1; // s
props.speed = props.speed || 2000; // ms
props.ease = props.ease || 'linear';
$(your_selector)
.addClass('showme') // Being a little lazy here, but it works
// You could work out an inspection by attached event
.bind('showme', function() { // custom event
$(this)
.delay( props.delay * 1000 )
.fadeIn(props.speed, props.ease, function() {
$(this).nextAll('.showme:first').trigger('showme'); // jqueryish recursion
});
}).hide() // or just hide in initial css
.first().trigger('showme'); // set the dominoes falling
};
And to trigger the reveal:
fadeCascade('div.bar');
If you're concerned about hygiene, you could unbind events and remove classes as you go.
Example: http://jsfiddle.net/redler/EKx6s/1/
Update: Added delay, thanks #Alexander.