First, I'm relatively new to both js and jQuery, so I apologize in advance if this is a really stupid question. That said, here it is:
I'm trying to create a cannon-like animation for a background that does a slow 'sweep-like' transition from one image to another.
The biggest issue I've been running into is ensuring that;
a. The increment counter is progressed and;
b. Each 'slice' of the image completes its fadeOut before the next begins.
If there's an easy (or obvious) way of doing this, I'd love to hear it. I've been pulling my hair out for a while now trying to figure out why these (and other similar variations) aren't working.
HTML:
img class="bg" (10 instances of this)
(function () {
// --- Variation 1 ---
function effect() {
var i = 0,
var current = $(".bg_1:eq(" + i + ")"),
arrLength = $(".bg_1").length;
while (i < arrLength) {
current.fadeOut(1000, 0);
i++;
}
}
effect();
// --- Variation 2 ---
function effect() {
var i = 0,
var current = $(".bg_1:eq(" + i + ")"),
arrLength = $(".bg_1").length;
while (i < arrLength) {
current.fadeOut(1000, 0, function () {
i++;
});
}
}
effect();
})();
I think it may be a problem with the scope of the 'i' variable, or a conflict in jQuery at that depth of scope. Any possible solutions would be GREATLY appreciated!
Thanks.
Without seeing your html, it's a bit hard to answer, but here's a general way to animate multiple elements in sequence:
(function _loop(idx) {
var $elements = $('#wrapper .bg'), idx = idx % $elements.length;
$elements.eq(idx).fadeIn('slow').delay(2500).fadeOut('slow', function () {
_loop(idx + 1);
});
}(0));
demo: http://jsfiddle.net/UU5AM/
Your var current will always be the same
try this:
function effect() {
var i = 0;
var arrLength = $(".bg_1").length;
while (i<arrLength) {
$(".bg_1:eq(" + i + ")").fadeOut(1000, 0);
i++;
}
}
effect();
Only now it will run as fast as the while loop goes. That means it will fade everything out almost immediately. You might want to run a setTimeout function for as long as the fadeOut goes:
var i = 0;
setTimeout(function(){
$(".bg_1:eq(" + i + ")").fadeOut(1000, 0);
i++;
}, 1000);
And ofcourse you will need to reset it when it reaches the end.
Edit:
Beat Richartz way, running the function again when the fadeOut is completed, is even better then the setTimeout.
You can use fadeOut's callback argument to provide it with a function which it will execute when the animation is completed. You can use this to raise the counter and (if necessary) animate the next element.
For example:
(function () {
function effect() {
var i = 0,
var current = $(".bg_1:eq(" + i + ")"),
arrLength = $(".bg_1").length;
var animateNext = function() {
current.fadeOut(1000, 0, function() {
i++;
if (i < arrLength) {
animateNext();
}
});
}
animateNext();
}
effect();
})();
As you can see, we've stored a reusable function in animateNext. We call it at the end of effect the first time to start the string of animation. After that, each next animation is started from the callback in fadeOut.
Your solutions are animating all the pictures at once. You have to install a recursive chain of events to do this:
// initial count value declared outside the function to not reinitialize
var count = 0;
function effect() {
// search the current image
var current = $(".bg_1:eq(" + count + ")");
// if there's an image, fade it out
if (current.length) {
current.fadeOut(1000, function() {
// increment the count;
count++;
// call the function recursively
effect();
});
}
}
// call the function
effect();
See it working with the JSFiddle here
Related
I have a function triggerWave() which makes the points on the canvas animate in the wave form. I am using d3.ease('quad-in') for easing and I would like to use d3.timer() to make the triggerWave() function call over 200ms timeframe. I am out of luck in finding the tutorials or examples on d3.timer.
triggerWave() {
//function logic
let count = 0;
let xScale = d3.scale.linear().range([1,2]); // want the value to change from 1 to 2.
let zScale = d3.scale.linear().domain([0, 200]); // 200 ms.
let value = xScale(d3.ease('quad-in')(zScale(count)));
if(count < 200){
count++;
d3.timer(() => triggerWave());
} else {
// do something
}
this.wave.next({currentFrame: value});
}
When I call d3.timer() as above, the triggerWave() function gets called infinite times and never stops. I want to manipulate or control the time. In my case, I want the timer() to be triggered for 200ms.
How can I understand how to use the d3.timer() function?
(EDIT: I totally and completely missed the huge, big "V3" which is right there, in the title of the question. Sorry. I'll keep this answer here as reference for v4 users)
Since you are calling triggerWave inside the triggerWave function itself, you don't need d3.timer, but d3.timeout instead. According to the API, d3.timeout:
Like timer, except the timer automatically stops on its first callback. A suitable replacement for setTimeout that is guaranteed to not run in the background. The callback is passed the elapsed time.
Also, pay attention to the fact that you are reseting count every time the function runs, which will not work. Set its initial value outside the function.
Here is a demo with those changes. I'm calling the function every 200 ms, until count gets to 50:
var p = d3.select("p")
var count = 0;
triggerWave();
function triggerWave() {
p.html("Count is " + count)
if (count < 50) {
count++;
d3.timeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<p></p>
You can also keep track of the total elapsed time, using the argument passed to triggerWave by d3.timeout:
var p = d3.select("p")
var count = 0;
var elapsed = 0;
var format = d3.format(".2")
triggerWave();
function triggerWave(t) {
elapsed = t ? elapsed + t : elapsed;
p.html("Count is " + count + ", and the elapsed time is " + format(elapsed/1000) + " seconds")
if (count < 50) {
count++;
d3.timeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<p></p>
Since you are using D3 v3, and as there is no d3.timeout in v3, you can do the same approach using vanilla JavaScript: setTimeout.
Here is a demo:
var p = d3.select("p")
var count = 0;
triggerWave();
function triggerWave() {
p.html("Count is " + count)
if (count < 50) {
count++;
setTimeout(triggerWave, 200)
} else {
return
}
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<p></p>
In version 3, there is no d3.timer.stop() function. You have to return true after a certain period of time to stop the timer.
In Gerardo's answer, he explained fabulously how to use the d3 timeout which would be a valid solution to your problem i.e., to call the function over and over for a certain period of time. But looking at your comments on Gerardo's answer, I think you are looking for something else.
Here's what I came up with and I think this is what you are looking for:
You can create an another function called as activateTriggerWave() which will be invoked on the button click and inside this function, you can call your triggerWave() method using the d3 timer.
function activateTriggerWave() {
d3.timer(elapsed => {
this.triggerWave();
if(elapsed >= 200){
return true; // this will stop the d3 timer.
}
});
}
triggerWave() {
// here you can do whatever logic you want to implement.
}
I hope this helps.
I use d3.js v3, and the timer can be stopped by any user action. In the d3.js docs it is shown to use it as:
d3.timer(function(elapsed) {
console.log(elapsed);
return elapsed >= 1000;
});
I have few examples in which the animation is forever and there is no reason to set a limit on it. Checking the standalone d3.timer which comes with a stop(), I found that it behaves quite slow comparing it with the default timer included in the v3 toolset, probably for some version incompatibility.
The solution is use is to:
var timer_1_stop=false;
set it as global var accesible from the page scope. Then the timer:
const run=function(){
//...
d3.timer(function() {
voronoi = d3.geom.voronoi(points).map(function(cell) { return bounds.clip(cell); });
path.attr("d", function(point, i) { return line(resample(voronoi[i])); });
return timer_1_stop;
});
}
const stopVoro=function(){
timer_1_stop=true;
}
It allows to do:
class Menu extends React.Component {
render(){
return(<ul>
<li><span onClick={()=>{stopVoro()}}>StopVoro</span></li>
</ul>)
}
}
I am working on a WordPress (Javascript) plugin that alters text fields based on user interaction with an HTML5 slider. One of its effects is to reveal a <span> string one character at a time using SetTimeout to create a delay (a few ms) so the effect is perceptible. I'm accomplishing this by getting the DOM element's contents and then rebuilding it one character at a time.
The problem is that since SetTimeout is aynsynchronous, the user can potentially move the slider faster than a single reveal loop can complete, resulting in half-empty DOM elements that never get corrected.
Is there a way to prevent this, or alternatively, a way to accomplish the task that avoids the conflict altogether? I have tried turning off the EventListener (for the HMTL5) at various points in the delay loop but cannot find a place that avoids the issue. The other possibility is to load all the <span> contents into arrays in order to retain intact copies of everything ... but something tells me there's a better way to do it that I don't know.
Here is example code. Initialize() is called when the HTML page involved loads.
function Initialize () {
document.getElementById(name).addEventListener('input', UpdateSlider);
}
function UpdateSlider()
{ if (
// conditions
)
{ var cols = document.getElementsByClassName(attr+i);
RevealTextLines (cols);
}
// 'delay' is a global variable to set the delay length
function RevealTextLines (cols)
{
[].forEach.call(cols, function(el) {
var snippet = el.innerHTML;
el.innerHTML = '';
el.style.display = 'inline';
(function addNextCharacter(h) {
el.innerHTML = snippet.substr(0,h);
h = h + numchars;
if (h <= snippet.length) {
setTimeout(function() {
addNextCharacter(h);
}, delay);
}
})(1);
});
}
The boolean flag suggested above does not work in this case, but it did inspire the following solution:
Provided the number of iterations are known in advance (which in this case they are), define a global counter variable outside the functions. Before the SetTimeout loop, set it to the number of iterations and decrease it by 1 every time through. Then have the calling function proceed only when the counter's value is zero.
var counter = 0;
function Initialize () {
document.getElementById(name).addEventListener('input', UpdateSlider);
}
function UpdateSlider()
{ if ( counter == 0)
{ var cols = document.getElementsByClassName(classname);
RevealTextLines (cols);
}
function RevealTextLines (cols)
{
[].forEach.call(cols, function(el) {
timer = el.length;
var snippet = el.innerHTML;
el.innerHTML = '';
el.style.display = 'inline';
(function addNextCharacter(h) {
el.innerHTML = snippet.substr(0,h);
h++;
if (h <= snippet.length) {
setTimeout(function() {
addNextCharacter(h);
timer--;
UpdateSlider();
}, delay);
}
})(1);
});
}
If anyone knows a more efficient solution, I would remain interested.
I've been trying to figure out why after a random amount of loops this function fades in multiple lines instead of one at a time. It seems to be that the setInterval gets out of sync. Everything looks correct to me and this is a question to find out what im doing wrong.
Also if there is anything here that you think i could do better that would benefit myself and others please do let me know as I am new to Jquery.
Update ****
I only want one shown at one time the problem im having is that after a random amount of loops it shows more than one or all of them fading at different times.
$(function() {
var items = $('.top-header-fading-headings li');
var index = 0;
items.hide();
function fadingSlider() {
$(items[index]).fadeIn(4000).fadeOut(4000);
index++;
if (index === items.length) {
index = 0;
}
}
fadingSlider();
setInterval( fadingSlider, 8000);
});
Html:
<ul class="no-bullet top-header-fading-headings">
<li><h5>Instant online feedback from outstanding Maths tutors</h5></li>
<li><h5>Smart assessment, tracking and analysis</h5></li>
<li><h5>Cutting edge tools for online tuition</h5></li>
</ul>
Use instead relevant animation method complete callback to call same function recursively:
$(function () {
var items = $('.top-header-fading-headings li');
var index = 0;
items.hide();
function fadingSlider() {
$(items[index]).fadeIn(4000).fadeOut(4000, fadingSlider);//<<<<
index++;
if (index === items.length) {
index = 0;
}
}
fadingSlider();
});
-jsFiddle-
Or use setTimeout inside the function instead:
function fadingSlider() {
$(items[index]).fadeIn(4000).fadeOut(4000);
index++;
if (index === items.length) {
index = 0;
}
setTimeout( fadingSlider, 8000);
}
fadingSlider();
you can try with call back function, no need setInterval,
$(function() {
var items = $('.top-header-fading-headings li');
var index = 0;
items.hide();
function fadingSlider(index) {//pass index as parameter
$(items[index]).fadeIn(4000).fadeOut(4000, function(){
fadingSlider((index === items.length - 1)? 0 : ++index); //recursive call
});
}
fadingSlider(0);//initiate animation
});
I'm trying to work out how to make this counter work. After a certain amount of animations have played, I want to make the page refresh. I know my code sucks. I'm not educated in this and I'm very new, not to mention it being difficult to concentrate (to say the least) where I'm staying at the moment... so be nice. Here it is:
$(document).ready(function () {
function loop() {
var p = 0;
if (p = 3) {
location.reload(true);
} else {
$("#p3").delay("1000").fadeIn("slow");
$("#p3").delay("1000").fadeOut("slow", loop);
p + 1;
};
loop();
});
Your if (p = 3) statement is using the assignment operator = instead of the comparison operator === or ==. So p gets assigned to 3, the result of which is truthy, so the else statement is never executed.
Also your p variable is declared inside your loop() function, so it gets reset every time the function is called - you could move that declaration to just before the function (keep it inside the document ready handler: no need to make it global).
Also the line p + 1; doesn't do anything: it doesn't increment p because you'd need to assign the result back to p with p = p + 1, the shorthand for which is p += 1 or just p++.
Finally, your code as posted has a syntax error: you are missing the closing } from the loop() function. I would guess the intention is to end the function and then call it, so:
$(document).ready(function () {
var p = 0; // <--- moved outside function
function loop() {
if (p === 3) { // <-- changed = to ===
location.reload(true);
} else {
$("#p3").delay("1000").fadeIn("slow");
$("#p3").delay("1000").fadeOut("slow", loop);
p++; // <-- changed from p + 1
};
} // <--- this is the missing bracket
loop();
});
I've made some assumptions and written what I believe is what you want, code is un-tested.:
Change it to:
$(document).ready(function () {
var globalP = 0;
//this is called when fadeOut completes.
function fadeComplete() {
if (globalP == 3) {//if it is 3 reload..
location.reload(true);
} else {
globalP++;//increment counter
animate();//start animation again...
}
}
function animate() {
//start fading in...
$("#p3").delay("1000").fadeIn("slow", function() {
//start fading out when the fadeIn completes.
//should this happen? Since you're fading in the SAME element.
$("#p3").delay("1000").fadeOut("slow", fadeComplete);
});
};
animate();
});
I have some code (below) which I wrote for a small piece of text to fade in out through a cycle of around 4 paragraphs. It works, but whenever I bring up the Web Inspector, it just tells me that it's an 'Anonymous function'. This is really annoying. Does anyone know how to fix it?
Btw, the bit that it hightlights as an anonymous function is:
slides[current].fadeOut("slow");
slides[target].fadeIn("slow");
The whole extract of code is here:
$(document).ready(function() {
var About = {
init: function() {
var slide_images = $('#widget p')
slides = new Array(),
delay = 5,
current = 0;
slide_images.each(function(index) {
current = index;
slides.push($(this));
});
var interval = setInterval(function() {
target = (current < (slides.length - 1)) ? current + 1 : 0;
slides[current].fadeOut("slow");
slides[target].fadeIn("slow");
current = target;
}, delay * 750);
}
}
About.init();
});
I made a jsfiddle here.
Because it is an anonymous function, as opposed to a named function.
One potential solution could be to roll the code into a named function and reference that function by named for the init option.