How do I reference a variable for clearInterval in a different function? - javascript

I have a circular image I want to spin with a button click and stop with a different button click.
The spin() function works, but the stop() does not because the values die outside of the spin() function (correct me if I'm wrong). How should I solve this? I'm open to using the same button to spin and stop, but my if else statement produces the same result - spinning the image works, but I can't stop it.
Another unintended bug is that if I click on the spin button more than once, the spinning gets faster.
Here's my code:
var spinImage;
function spin(){
spinImage = document.getElementById('spinImage'), degree = 0;
setInterval(function() {
spinImage.style.transform = "rotate(" + ++degree + "deg)";}, 1);
}
function stop(){
clearInterval(spinImage);
}
I need to stick to plain javascript and use the setInterval/clearInterval methods.

the value you are looking for is the return value of setInterval:
var intervalId;
function spin() {
var spinImage = document.getElementById('spinImage');
var degree = 0;
intervalId = setInterval(function() {
spinImage.style.transform = 'rotate(' + ++degree + 'deg)';
}, 1);
}
function stop() {
clearInterval(intervalId);
}

You need to get the id when initializing setInterval (here store in spin variable). That variable will have reference to setInterval so that you can clear it up using stop function.
Another unintended bug (faster spinning on consecutive clicks) is due to the fact that, previous setInterval id is not cleared and new one is created. So, the number of times you click; you will have that many setIntervals. To avoid it, you need to clear up the existing id and then re-initialize with the new set interval.
Have fixed the code. Please see:
var spinImage;
var spin;
function spin(){
spinImage = document.getElementById('spinImage');
degree = 0;
if(spin) stop();
spin = setInterval(function() {
spinImage.style.transform = "rotate(" + ++degree + "deg)";
}, 1);
}
function stop(){
clearInterval(spin);
}
Hope it helps. Revert for any doubts/clarifications

You need to remember the interval id, so that you can stop it.
let intervalId = null;
function spin() {
spinImage = document.getElementById('spinImage'), degree = 0;
intervalId = setInterval(function() {
spinImage.style.transform = "rotate(" + ++degree + "deg)";
}, 1);
}
function stop() {
clearInterval(intervalId);
}
ES6 Class
You can encapsulate this logic into a reusable class object i.e. ImageSpinner.
const main = () => {
const spinner = new ImageSpinner('#spinImage');
addEventListeners('.btn-toggle', 'click', (e) => {
spinner.toggle();
let attr = `data-text-${spinner.isRunning() ? 'off' : 'on'}`;
e.currentTarget.textContent = e.currentTarget.getAttribute(attr);
});
addEventListeners('.btn-reset', 'click', (e) => {
spinner.reset();
let btn = document.querySelector('.btn-toggle');
btn.textContent = btn.getAttribute('data-text-on');
});
}
class ImageSpinner {
constructor(selector, rate) {
/* #protected */ this.image = document.querySelector(selector);
/* #protected */ this.rate = rate;
/* #private */ this.__intervalId = null;
/* #private */ this.__degree = 0;
}
/* #public */ isRunning() {
return this.__intervalId != null;
}
/* #public */ reset() {
if (this.isRunning()) this.stop();
this.__degree = 0;
this.__rotate();
}
/* #public */ start() {
if (!this.isRunning()) {
this.__intervalId = setInterval(() => { this.update() }, this.rate);
} else {
console.log('Already running...');
}
}
/* #public */ stop() {
if (this.isRunning()) {
clearInterval(this.__intervalId);
this.__intervalId = null;
} else {
console.log('Not currently running...');
}
}
/* #public */ toggle() {
this.isRunning() ? this.stop() : this.start();
}
/* #protected */ update() {
this.__degree = (this.__degree + 1) % 360;
this.__rotate();
}
/* #private */ __rotate() {
this.image.style.transform = "rotate(" + this.__degree + "deg)";
}
}
/*
* Can either be an object of event handlers,
* or an event name followed by a function.
*/
function addEventListeners(elementsOrSelector, events) {
((els) => els.forEach(el =>
(typeof events === 'string' && arguments.length > 2)
? el.addEventListener(arguments[1], arguments[2])
: Object.keys(events)
.forEach(name => el.addEventListener(name, events[name]))))
(Array.from((typeof elementsOrSelector === 'string')
? document.querySelectorAll(elementsOrSelector)
: elementsOrSelector.entries
? elementsOrSelector
: [elementsOrSelector]));
}
main(); // Execute main function
body {
background: #222;
padding: 1.25em;
}
img {
border: 2px dotted white;
margin-left: 0.5em;
}
.btn-wrapper {
margin-top: 1.75em;
}
.btn-toggle {
display: inline-block;
width: 64px;
cursor: pointer;
}
<img id="spinImage" src="https://lh5.googleusercontent.com/-ajlbNdBMWwA/AAAAAAAAAAI/AAAAAAAAAAo/ND2LpoxaqAQ/photo.jpg?sz=96" alt="" width="96" height="96"/>
<div class="btn-wrapper">
<button class="btn btn-toggle" data-text-on="Start" data-text-off="Pause">Start</button>
<button class="btn btn-reset">Reset</button>
</div>

<!DOCTYPE html>
<head>
<title>Spin</title>
</head>
<body>
<button id='spinStart'>Start</button>
<button id='spinStop'>Stop</button>
<script>
var spinning = false;
var handle;
var counter = 0;
// Add click event listener to the start button
window.document.getElementById('spinStart').addEventListener("click", start);
// Add click event listener to the stop button
window.document.getElementById('spinStop').addEventListener("click", stop);
function start() {
if(spinning === true) {
console.log('spinning already')
// Do nothing here as it's already spinning.
spinning = !spinning;
}
if(spinning === false) {
// Not spinning here, so we have to init the setInterval
handle = setInterval(() => {
console.log(counter);
counter++;
}, 1000);
spinning = !spinning;
}
}
function stop() {
//We only want to stop the spinner if it's actually spinning
if(spinning === true){
console.log('clearing the interval now!');
clearInterval(handle);
}
}
</script>
</body>
</html>
Substitute your code as fits.

Related

How would you increase a variables value every second using a function?

I am trying to make a variable increase every second. What should I include inside the function autoClicker, so that the variable clicks increase by 1 every second? Also, if there are any more problems in the code, could you point them out to me? Sorry if this question seems basic, I am still quite new to JavaScript.
// The variable we are trying to increase
var clicks = 0;
var upgrade1 = 1;
function getClicks() {
clicks += upgrade1;
document.getElementById("clicks").innerHTML = clicks;
};
function buyAutoClicker() {
if (clicks >= 50) {
clicks -= 50
autoClicker()
} else {
alert = "Sorry, you don't have enough clicks to buy this";
}
}
// The function I will use to increase clicks
function autoClicker() {}
You could create an AutoClicker class that has a start, pause, ad update function. It will be in charge of managing the setInterval id.
Edit: I updated it to include upgrade buttons and the target can now be manually clicked.
const upgrades = [{
cost: 50,
rate: 2
}, {
cost: 100,
rate: 4
}];
const main = () => {
const target = document.querySelector('.auto-clicker');
const span = document.querySelector('.info > span');
const btn = document.querySelector('.btn-toggle');
const clicker = new AutoClicker(target, 1000, (clicks) => {
span.textContent = clicks;
}).start();
initializeUpgrades(clicker, upgrades);
btn.addEventListener('click', (e) => {
e.target.textContent = clicker.isRunning() ? 'Start' : 'Pause';
clicker.toggle();
});
};
const initializeUpgrades = (clicker, upgrades) => {
const upgradeContainer = document.querySelector('.upgrades');
upgrades.forEach(upgrade => {
const btn = document.createElement('button');
btn.textContent = upgrade.cost;
btn.value = upgrade.rate;
btn.addEventListener('click', (e) => {
let cost = parseInt(e.target.textContent, 10);
let value = parseInt(e.target.value, 10);
if (clicker.clicks >= cost) {
clicker.clicks -= cost;
clicker.step = value
} else {
console.log(`Cannot afford the ${value} click upgrade, it costs ${cost} clicks`);
}
});
upgradeContainer.appendChild(btn);
});
};
class AutoClicker {
constructor(target, rate, callback) {
if (typeof target === 'string') {
target = document.querySelector(target);
}
this.target = target;
this.rate = rate;
this.callback = callback;
this.init();
}
init() {
this.step = 1;
this.clicks = 0;
this._loopId = null;
this.target.addEventListener('click', (e) => {
this.update();
});
}
isRunning() {
return this._loopId != null;
}
toggle() {
this.isRunning() ? this.pause() : this.start();
}
update() {
this.clicks += this.step;
if (this.callback) {
this.callback(this.clicks);
}
}
start() {
this.update(); // Update immediately
this._loopId = setInterval(() => this.update(), this.rate);
return this;
}
pause() {
clearInterval(this._loopId);
this._loopId = null;
return this;
}
}
main();
.wrapper {
width: 10em;
text-align: center;
border: thin solid grey;
padding: 0.5em;
}
.auto-clicker {
width: 4em;
height: 4em;
background: #F00;
border: none;
border-radius: 2em;
}
.auto-clicker:focus {
outline: none;
}
.auto-clicker:hover {
background: #F44;
cursor: pointer;
}
.info {
margin: 1em 0;
}
.upgrades {
display: inline-block;
}
.upgrades button {
margin-right: 0.25em;
}
<div class="wrapper">
<button class="auto-clicker"></button>
<div class="info">Clicks: <span class="clicks"></span></div>
<button class="btn-toggle">Pause</button>
<div class="upgrades"></div>
</div>
// The variable we are trying to increase
var clicks = 0;
var upgrade1 = 1;
function getClicks() {
clicks += upgrade1;
document.getElementById("clicks").innerHTML = clicks;
};
function buyAutoClicker() {
if (clicks >= 50) {
clicks -= 50
autoClicker()
} else {
alert = "Sorry, you don't have enough clicks to buy this";
}
}
// The function I will use to increase clicks
setInterval(function(){ clicks++;console.log(clicks); }, 1000);
Use setInterval to run a function at a specified interval. This will run increaseClicks every 1000 milliseconds (every second):
function increaseClicks() {
clicks++;
}
var interval = setInterval(increaseClicks, 1000);
Use clearInterval to stop running it:
clearInterval(interval);
You can omit var interval = if you don't want to use clearInterval:
setInterval(increaseClicks, 1000);
There might be several things to improve this code
the use of textContent is preferable to innerHTML, it checks if there are no html tags in the text
then using inline functions like ()=>{} are more useful but in this program it does'nt make a difference, where you to use it in object oriented context you could use it several ways
you don't need document.getElementById, you could just use id.
And finaly (this is just à random tip which has nothing to do with much of anything) you may consider branchless programming because ifs are expensive.
Stackoverflow Branchless Programming Benefits
But anyways you should have fun :)
var clicks = 0;
var upgrade1 = 1;
function getClicks() {
clk.textContent = (clicks += upgrade1);
};
function buyAutoClicker() {
if (clicks >= 50) {
clicks -= 50
setInterval(()=>{getClicks();},1000);
} else {
alert("Sorry, you don't have enough clicks to buy this");
}
}
clk.onclick=()=>{getClicks();};
b.onclick=()=>{buyAutoClicker();};
html,body{height:100%;width:100%;margin:0;}
p{height:50px;width:50px;background:red;}
<p id="clk"></p>
<p id="b"></p>

How could I make this active onclick?

var span = document.getElementById('loading_dots');
var int = setInterval(function() {
if ((span.innerHTML += '●').length == 4)
span.innerHTML = '';
}, 400);
(function(){
var loading_dots = document.getElementById("loading_dots"),
show = function(){
loading_dots.style.display = "block";
setTimeout(hide, 5000); // 5 seconds
},
hide = function(){
loading_dots.style.display = "none";
};
show();
})();
How can I make it so loading_dots start on the click of a button, and re-activates everytime I click the button? the bottom function is to stop it after 5 seconds, maybe could merge it into one function?
Needs to work for 3 seperate buttons and relaunch on click of each, also needs to display inside of <span class="loading_dots" id="loading_dots"></span> any method is fine, css, jquery, or javascript
here is a jQuery version:
(function ( $ ) {
$.fn.loader = function( options ) {
var settings = $.extend({
text:"●",
spn: undefined
}, options );
$.each(this, function(){
var btn = this;
var int;
var spn;
if (settings.spn === undefined) {
spn = $("<span/>" , { "class":"loading_dots" });
$(btn).append(spn);
} else {
spn= $(settings.spn);
}
var show = function(){
btn.setAttribute("disabled", "disabled")
clearInterval(int);
spn.show();
int = setInterval(function() {
if ((spn[0].innerHTML += settings.text).length == 4)
spn.html("");
}, 400);
setTimeout(hide, 5000); // 5 seconds
}
var hide = function (){
spn.hide();
btn.removeAttribute("disabled", "disabled")
clearInterval(int);
}
btn.addEventListener("click", show);
});
};
}( jQuery ));
// now bind it by its class, this only need to be run once every time new button is added to the html
$(".btn").loader({spn:".loading_dots"});
// and you could also specify the text by
// $(".btn").loader({text: "*"});
.loading_dots {
color:red;
display:none;
width:100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
<span class="loading_dots"></span>
<button class="btn" type="button" >
submit
</button>
<button class="btn" type="button" >
submit
</button>
</div>
If you want to add an event listener for a button click, just select the buttons, and add the listeners in a loop:
document.querySelectorAll("button").forEach(e => e.addEventListener("click", myFunc));
Alternatively, listen for any click, then check if the event's target is a button:
document.addEventListener("click", (e) => if (e.target.tagName == "BUTTON") myFunc());
You could use CSS for the most part of your code, and than simply toggle a show class on the parent #loading element:
const Loading = () => {
let tOut = null;
const el = document.querySelector("#loading");
const show = () => {
el.classList.add('show');
tOut = setTimeout(hide, 5000);
};
const hide = () => {
el.classList.remove('show');
clearTimeout(tOut);
};
return {
show,
hide
};
};
const loadingDots = Loading();
const loadBtns = document.querySelectorAll('.load');
[...loadBtns].forEach(el => el.addEventListener('click', loadingDots.show));
// you can always use loadingDots.hide() to hide when needed (before the 5sec ticks out)
#loading {
position: fixed;
z-index: 100;
top:0;
left: 0;
width:100vw;
height:100vh;
display:flex;
background: rgba(0,0,0,0.5);
color: #fff;
font-size: 3em;
align-items: center;
justify-content:center;
visibility: hidden;
opacity: 0;
transition: 0.4s;
}
#loading.show {
opacity: 1;
visibility: visible;
}
#keyframes blink {
50% {opacity: 1;}
}
#loading i:after {content: "\25cf";}
#loading i { opacity: 0; animation: blink 1.2s infinite; }
#loading i:nth-child(2) { animation-delay: .2s; }
#loading i:nth-child(3) { animation-delay: .4s; }
<div id="loading"><i></i><i></i><i></i></div>
<button class="load">LOAD</button>
<button class="load">LOAD</button>
<button class="load">LOAD</button>
A plain javascript version with the option to programmatically/manually stop displaying the loading dots. Just pass the id of the parent element you want the loading to be attached to. By default the loading will be appended to the parent but you can optionally pass an object as the last parameter with a position property.
function removeLoading(id) {
var parent = document.getElementById(id);
var spans = parent.getElementsByClassName("loading_dots");
while (spans.length > 0) {
var span = spans[0];
if (span.dataset.timerId) {
clearTimeout(span.dataset.timerId);
}
span.remove();
}
}
function addLoading(id, options) {
options = options || {};
var parent = document.getElementById(id);
var existingSpans = parent.getElementsByClassName("loading_dots");
if (existingSpans.length > 0) {
removeLoading(id);
}
var span = document.createElement("span");
span.setAttribute("class", "loading_dots");
if (options.timerId) {
span.dataset.timerId = options.timerId;
}
parent.insertAdjacentElement(options.position || "beforeend", span);
setInterval(function () {
if ((span.innerHTML += '●').length == 4)
span.innerHTML = '';
}, 400)
}
function addLoadingWithTimeout(id, ms, options) {
options = options || {};
var timerId = setTimeout(function () { removeLoading(id) }, ms);
options.timerId = timerId;
addLoading(id, options);
}
<p id="load1">Load 1 - Will stop automatically in 3 seconds after starting. </p>
<button onclick="addLoadingWithTimeout('load1', 3000)">Start Load 1</button>
<button onclick="removeLoading('load1')">Stop Load 1</button>
<p id="load2">Load 2 - Only manual Stop </p>
<button onclick="addLoading('load2')">Start Load 2</button>
<button onclick="removeLoading('load2')">Stop Load 2</button>
Here you go. on the HTML side, you just pass the event to the button that you want and then the id, as a string, of the span/div where you want the load icons to appear.
HTML:
<button id="btn" onclick="load(event, 'loadDiv')">Load</button>
<div>
<span id="loadDiv"></span>
</div>
Below, we are getting the btn id from event so you don't have to manually pass it everytime. Then we are defining function for the innerhtml icons. Lastly, we are running the showIcon function every .4s and then clearing the interval after 5 seconds.
JS:
function load(e, location) {
var btn = document.getElementById(e.srcElement.id)
var loadDiv = document.getElementById(location)
function showLoad() {
if (loadDiv.innerHTML.length < 3) {
return loadDiv.innerHTML += '●'
}
loadDiv.innerHTML = ''
}
(function() {
var loadIcons = setInterval(function() {
showLoad()
}, 400)
var clear = setTimeout(function() {
clearInterval(loadIcons)
}, 5000)
})()
}
Hope this helps!
You can define your code in a function and add click handler to the button.
function myFunc() {
var span = document.getElementById('loading_dots');
var int = setInterval(function() {
if ((span.innerHTML += '●').length == 4)
span.innerHTML = '';
}, 400);
(function(){
var loading_dots = document.getElementById("loading_dots"),
show = function(){
loading_dots.style.display = "block";
setTimeout(hide, 5000); // 5 seconds
},
hide = function(){
loading_dots.style.display = "none";
};
show();
})();
}
document.getElementById("myBtn1").addEventListener("click", myFunc);
document.getElementById("myBtn2").addEventListener("click", myFunc);

Remove div after fade out animation - javascript

I'm trying to remove a div from the body AFTER an animation is completed, but at the moment looks like the remove happens right after the first iteration of the animation.
function $(el) { return document.getElementById(el); }
var divFirst = $('first');
if(divFirst)
divFirst.addEventListener("click", addSecond);
function removeSecond()
{
fadeOut();
var child = $('second');
console.log("remove called");
child.remove();
}
function addSecond()
{
console.log("addSecond called");
var aContainer = document.createElement('div');
aContainer.setAttribute('id', 'second');
aContainer.innerHTML = "Second";
aContainer.addEventListener("click", removeSecond);
document.body.appendChild(aContainer);
fadeIn();
}
function fadeIn()
{
var secondDiv = $('second');
if(secondDiv)
{
secondDiv.style.opacity ? secondDiv.style.opacity :
secondDiv.style.opacity = "0.0";
if(parseFloat(secondDiv.style.opacity) <= 1)
{
secondDiv.style.opacity = parseFloat(secondDiv.style.opacity) + 0.05;
setTimeout(fadeIn, 50);
}
}
}
function fadeOut()
{
var secondDiv = $('second');
if(secondDiv)
{
console.log(secondDiv.style.opacity);
if(parseFloat(secondDiv.style.opacity) >0 )
{
secondDiv.style.opacity = parseFloat(secondDiv.style.opacity) - 0.05;
setTimeout(fadeOut, 50);
}
}
}
Here the jsfiddle: http://jsfiddle.net/ny85ckk2/
If I remove the child.remove() call, the animation continues till the end.
Any idea?
Thanks a lot
Your fadeOut is an asynchronous operation. When you call it, it starts the fade, but then the fade continues and completes asynchronously because you're using setTimeout. So your code after callilng fadeOut runs just after it starts.
To remove the element when done, remove that code and instead remove the element in fadeOut when done:
function fadeOut()
{
var secondDiv = $('second');
if(secondDiv)
{
console.log(secondDiv.style.opacity);
if(parseFloat(secondDiv.style.opacity) >0 )
{
secondDiv.style.opacity = parseFloat(secondDiv.style.opacity) - 0.05;
setTimeout(fadeOut, 50);
}
else // Added
{ // Added
secondDiv.remove(); // Added
} // Added
}
}
Or if you want more flexibility, have fadeOut call a callback when done and remove the element in the callback; we do that by separating out the actual work of the fade from starting it:
function fadeOut(callback)
{
var secondDiv = $('second');
if (secondDiv)
{
doFade();
}
function doFade() {
if(parseFloat(secondDiv.style.opacity) >0 )
{
secondDiv.style.opacity = parseFloat(secondDiv.style.opacity) - 0.05;
setTimeout(doFade, 50);
}
else if (callback)
{
callback(secondDiv);
}
}
}
Usage:
function removeSecond()
{
fadeOut(function(div) {
div.remove();
});
}
Fiddle

Attach 2 event handlers to one element to start/stop animation

I want to attach 2 event handlers to one button to start and stop an animation respectively. I'm trying to use a boolean value to test if the animation is already running or not so the right event handler is triggered. i.e. if the animation is not running the boolean is false so the run animation event handler will trigger. As soon as I try to add either or both of these handlers the code breaks and the animation won't run. I've not tried using 2 event handlers on an element before and have seen little about it online so I'm not sure if I'm going about this the right way.
How can I attach these handlers to start and stop the animation ?
<div id='robotCont'></div>
<button id='animeBtn'>start/stop animation</button>
div#robotCont {
width:125px;
height:160px;
border:1px solid #22e;
background-image:url("images/robot.jpg");
background-repeat: no-repeat;
background-position: 0px 0px;
}
var robotSwitch = false;
document.getElementById('animeBtn').onclick = function() {
if(robotSwitch == false) {
runningRobot();
} else {
stopRobot();
}
}
var decrement = 0;
function runningRobot() {
var robotCont = document.getElementById('robotCont');
if(decrement < -1900) {
decrement = 0;
}
robotCont.style.backgroundPosition = decrement+ 'px 0px';
decrement -= 120;
timer = setTimeout(runningRobot,100);
robotSwitch = true;
}
function stopRobot() {
clearTimeout(timer);
}
what about this:
var animate = false
timer;
document.getElementById('animeBtn').onclick = function() {
animate = !animate;
if(animate) {
runningRobot();
} else {
stopRobot();
}
}
var decrement = 0;
function runningRobot() {
var robotCont = document.getElementById('robotCont');
if(decrement < -1900) {
decrement = 0;
}
robotCont.style.backgroundPosition = decrement+ 'px 0px';
decrement -= 120;
timer = setTimeout(runningRobot,100);
}
function stopRobot() {
clearTimeout(timer);
}

Clear a non-global timeout launched in jQuery plug-in

I try to set a timeout on an element, fired with a jQuery plugin. This timeout is set again in the function depending on conditions. But, I want to clear this element's timeout before set another (if I relaunch the plug-in), or clear this manually.
<div id="aaa" style="top: 0; width: 100px; height: 100px; background-color: #ff0000;"></div>
Here's my code (now on http://jsfiddle.net/Ppvf9/)
$(function() {
$('#aaa').myPlugin(0);
});
(function($) {
$.fn.myPlugin = function(loops) {
loops = loops === undefined ? 0 : loops;
this.each(function() {
var el = $(this),
loop = loops,
i = 0;
if (loops === false) {
clearTimeout(el.timer);
return;
}
var animate = function() {
var hPos = 0;
hPos = (i * 10) + 'px';
el.css('margin-top', hPos);
if (i < 25) {
i++;
} else {
if (loops === 0) {
i = 0;
} else {
loop--;
if (loop === 0) {
return;
} else {
i = 0;
}
}
}
el.timer = window.setTimeout(function () {
animate();
}, 1000/25);
};
clearTimeout(el.timer);
//$('<img/>').load(function() {
// there's more here but it's not very important
animate();
//});
});
return this;
};
})(jQuery);
If I make $('#element').myPlugin();, it's launched. If I make it a second time, there's two timeout on it (see it by doing $('#aaa').myPlugin(0);
in console). And I want to be able to clear this with $('#element').myPlugin(false);.
What am I doing wrong?
EDIT :
SOLVED by setting two var to access this and $(this) here : http://jsfiddle.net/Ppvf9/2/
try saving the timeout handle as a property of the element. Or maintain a static lookup table that maps elements to their timeout handles.
Something like this:
el.timer = window.setTimeout(...);
I assume you need one timer per element. Not a single timer for all elements.

Categories