I'm learning event phasing of nested elements so I create small project. Codepen JS starts on 43rd line.
So here's simple nested divs.
<div id="zzz" class="thir">
0
<div id="xxx" class="thir">
0
<div id="sss" class="thir">
0
</div>
</div>
</div>
And here what we do with them.
const ar2 = [zzz, xxx, sss];
ar2.map(e => {
e.addEventListener('click', nestedClick, phase);
})
function nestedClick(e) {
// e.stopPropagation();
const meow = this;
const prevColor = this.style.backgroundColor;
this.style.backgroundColor = '#757575';
window.setTimeout(() => { meow.style.backgroundColor = prevColor}, 500);
}
To visually show how capturing/bubbling works I'd like to change background color and set timeout on each step, wait until it's done and trigger next click with the same strategy.
But here I see after I click on any element event still goes through, changing color and forces all .setTimeout() like at the same time. How can I repair it?
Side question: why e.stopPropagation() works whether it's capturing or bubbling phase?
Thank you for attention!
You need to shift the start time of the timers. And for a flashing effect having a second timer would be good.
let counter = 1;
const ar2 = [...document.getElementsByClassName('thir')];
ar2.map(e => {
e.addEventListener('click', nestedClick);
e.addEventListener('mouseup', function() {
counter = 1;
});
});
function nestedClick(e) {
const prevColor = this.style.backgroundColor;
debugger;
setTimeout( () => {
this.style.backgroundColor = '#757575';
setTimeout( () => {
this.style.backgroundColor = prevColor;
}, 50 * (counter++));
}, 500 * (counter++));
}
<div id="zzz" class="thir">
CLICK ME
<div id="xxx" class="thir">
CLICK ME
<div id="sss" class="thir">
CLICK ME
</div>
</div>
</div>
Related
I'm stuck in Javascript at looping a piece of code when an event listener is being
placed.
For example, let's say I have a div:
<div id="option">
</div>
and I added a Javascript mouseenter event listener:
const divItem = document.getElementById("option")
divItem.addEventListner("mouseenter", () => {
console.log("Mouse is entered")
})
Now the console log happens once and after I hover the mouse, but I want it to happen every after 4 seconds
and log the same message in the console until the mouse is moving out of the div.
I tried using setTimeout:
divItem.addEventListner("mouseenter", () => {
const timeoutEvent = () => {
console.log("Mouse is entered")
setTimeout( () => { timeoutEvent() }, 4000 )
}
timeoutEvent()
})
but it is logging even after the mouse left the div,
so how can I solve this?
You're on the right track. If you want every four seconds, you want to:
Use setInterval (or set a new setTimeout every time), and
Cancel it when you see mouseleave
const divItem = document.getElementById("option")
// The timer handle so we can cancel it
let timer = 0; // A real timer handle is never 0, so we can use it as a flag
divItem.addEventListener("mouseenter", () => {
console.log("Mouse is entered");
timer = setInterval(() => {
if (timer) {
console.log("Mouse is still here");
}
}, 1000);
})
divItem.addEventListener("mouseleave", () => {
console.log("Mouse left");
clearInterval(timer);
timer = 0;
});
<div id="option">
this is the div
</div>
(I've used one second in that example instead of four so it's easier to see it working.)
Or using setTimeout rescheduling itself instead:
const divItem = document.getElementById("option")
// The timer handle so we can cancel it
let timer = 0; // A real timer handle is never 0, so we can use it as a flag
const timerInterval = 1000;
function tick() {
if (timer) {
console.log("Mouse is still here");
timer = setTimeout(tick, timerInterval);
}
}
divItem.addEventListener("mouseenter", () => {
console.log("Mouse is entered");
timer = setTimeout(tick, timerInterval);
})
divItem.addEventListener("mouseleave", () => {
console.log("Mouse left");
clearTimeout(timer);
timer = 0;
});
<div id="option">
this is the div
</div>
You should use setInterval instead of setTimeout.
You can define your 'interval' function and the interval in global scope and then use them to set and start the interval execution and clearInterval ( stop de execution ) on mouseenter/mouseleave events.
const divItem = document.getElementById("option")
let myInterval;
const timeoutEvent = () => {
console.log("Mouse is entered")
}
divItem.addEventListener("mouseenter", () => {
myInterval = setInterval(timeoutEvent, 1000);
})
divItem.addEventListener("mouseleave", () => clearInterval(myInterval))
<div id="option">
My Option
</div>
divItem.addEventListener("mouseenter", () => {
console.log("Mouse is entered");
timer = setInterval(() => {
console.log("Mouse is still here");
}
}, 4000);
})
I have this script where I highlight the clicked div and its parents, then I make them white again respectively. However, I would like to stop consecutive clicks, allow only one click until setTimeout finishes. Basically, user should wait for the whole animation to finish before clicking again.
const allDivElements = document.querySelectorAll("div");
let timeout = 300;
allDivElements.forEach((div) => {
div.addEventListener("click", function (e) {
setTimeout(() => {
changeBg(this, true);
setTimeout(() => {
changeBg(this, false);
timeout = 300;
}, timeout);
}, timeout);
timeout += 300;
});
});
function changeBg(div, phase) {
if (phase) div.style.backgroundColor = "lightblue";
else div.style.backgroundColor = "#fff";
}
As far as I looked over the possible solutions, I was not able to find one that prevents click events until setTimeout methods. Any detailed help will be appreciated.
EDIT: Sorry if I cause confusion. This is the link to the whole application in case you'd like to test it out: https://codesandbox.io/s/busy-goldstine-ope9w?file=/src/index.js
Thanks in advance!
It looks like you're trying to make this too complicated. Just use a Boolean value for each element to track whether the click should work or not:
const timeout = 300;
allDivElements.forEach((div) => {
let clickAllowed = true;
div.addEventListener("click", function (e) {
if (clickAllowed) {
clickAllowed = false;
changeBg(this, true);
setTimeout(() => {
changeBg(this, false);
clickAllowed = true;
}, timeout);
}
});
});
Handle your listeners using addEventListener and removeEventListener
Once all the elements are processed, add back the events using addEventListener
A recursion could also come handy to iterate over parent Elements:
const divs = document.querySelectorAll("div");
const EVT = (el, t, n, f, o = {}) => el.forEach(e => e[`${{on:"add",off:"remove"}[t]}EventListener`](n, f, o));
const changeBg = (div) => {
if ([...divs].indexOf(div) < 0) return EVT(divs, "on", "click", clickHandler); // Add
div.style.backgroundColor = "lightblue";
setTimeout(() => {
div.style.backgroundColor = "white";
changeBg(div.parentElement); // Recursive call, this time pass the parent
}, 300);
};
const clickHandler = async(ev) => {
EVT(divs, "off", "click", clickHandler); // Remove listeners
changeBg(ev.currentTarget); // Start
};
EVT(divs, "on", "click", clickHandler); // Add listeners
body {
font-family: sans-serif;
}
div {
border: 1px solid black;
padding: 5px;
background: #fff;
}
<div id="1">1
<div id="2">2
<div id="3">3
<div id="4">4
<div id="5">5
</div>
</div>
</div>
</div>
</div>
So, I have a button and I want to constantly have an event triggering when the button is hovered. If I use mouseover, then this event triggers only once when the cursor comes on it from somewhere outside.
btn.addEventListener("mouseover", function(){
console.log("Hello");
});
For instance, I want this console log to happen constantly while the cursor is over the button.
Please check if mousemove event works for you.
let interval = null;
btn.addEventListener("mousemove", function(){
console.log("Hello");
});
btn.addEventListener('mouseenter', function(){
interval= setInterval(()=>console.log('Hello'), 100)
})
btn.addEventListener('mouseout', function(){
clearInterval(interval)
})
#btn{
width: 300px;
height: 60px;
border-radius: 8px;
}
<button id="btn">
hover me
</button>
You could do this by using mouseover and mouseout (and a setInterval). Basically, start an interval when the mouse enters, and clear it when it exits.
let interval;
btn.addEventListener("mouseover", function(){
interval = setInterval(function() {
console.log("Hello");
}, 100);
});
btn.addEventListener("mouseout", function(){
clearInterval(interval);
});
You can use a combination of
flags, either on the elements themselves as here, or as separate variable(s)
a setInterval function
to figure out which elements are being hovered at any given time. (This is obviously a slightly silly example since you can't very well mouse-over multiple elements that sit next to each other at a time.)
function addEventListeners(button) {
button.addEventListener("mouseover", () => button.dataset.hovered = "1");
button.addEventListener("mouseout", () => button.dataset.hovered = "0");
}
function reportHovers(elements) {
const hoveredIds = elements.filter(el => el.dataset.hovered === "1").map(el => el.id);
document.getElementById("out").value = `${Math.floor(new Date() / 1000)}\n${JSON.stringify(hoveredIds)}`;
}
var buttons = Array.from(document.querySelectorAll("button"));
buttons.forEach(button => addEventListeners(button));
setInterval(() => reportHovers(buttons), 100);
<button id="b1">Button 1</button>
<button id="b2">Button 2</button>
<button id="b3">Button 3</button>
<textarea id="out"></textarea>
I am aware Javascript is single-threaded. However I do not understand why the following code does not show/hide a spinner before/after a compute-intensive task.
Code outline:
showSpinner();
computeIntensiveTask();
hideSpinner();
The code (I am using the Bootstrap spinner)
const showSpinner = () => {
const $spinner = $('#spacewalk-spinner').find('.spinner-border');
$spinner.show();
};
const hideSpinner = () => {
const $spinner = $('#spacewalk-spinner').find('.spinner-border');
$spinner.hide();
};
The function computeIntensiveTask() does a ton of sqrt() and other 3D vector math.
When the code runs, the spinner never appears. Why is this happening?
UPDATE 0
As a test I tried simply updated the spinner elements color before/after:
before
document.getElementById('spacewalk-spinner').style.color = 'rgb(255,0,0)';
after
document.getElementById('spacewalk-spinner').style.color = 'rgb(0,255,0)';
and only the 'after' color change took place.
UPDATE 1
To clarify. If I remove the call to hideSpinner() and change showSpinner() to document.getElementById('spacewalk-spinner').style.display = 'block'. The spinner shows after computeIntensiveTask() completes. This despite the fact I have placed computeIntensiveTask() within call to window.setTimeout with a 500 ms wait.
You need to coordinate the updating of the UI by using a setTimeout function. Also you need to position the showSpinner and hideSpinner functions in relation to the updating of the UI. See this snippet as an example.
const showSpinner = () => {
const $spinner = $('#spacewalk-spinner').find('.spinner-border');
$spinner.show();
console.log('show');
};
const hideSpinner = () => {
const $spinner = $('#spacewalk-spinner').find('.spinner-border');
$spinner.hide();
console.log('hide');
};
const computeIntensiveTask = () => {
showSpinner();
// begin the calculations after the UI updates by using setTimeout
setTimeout(function() {
for (var start = 1; start < 1000; start++) {
// calculating...
console.log('calc');
}
hideSpinner();
}, 0);
};
computeIntensiveTask();
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="spacewalk-spinner">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
try this:
showSpinner();
setTimeout(() => {
computeIntensiveTask();
hideSpinner();
}, 300);
I've been trying to properly use setTimeOut but haven't been able to figure it out.
Expected Output:
if you click any button, a banner will slide into view, after 8 seconds it will disappear.
click anywhere else on the page and the banner disappear.
click the same button before the 8 seconds timer ends and a new 8 should start.
Actual output:
if you click any button a banner will slide into view, after 8 seconds it will disappear.
click anywhere on the page so the banner disappear.
click the same button before the 8 seconds timer ends, instead of starting a new 8-seconds the banner will finish off the remaining seconds of the first click then disappear.
Here is what I've tried and also a codesandbox:
[a link] https://codesandbox.io/s/n7zvwn11yj
const getGreetingBanner =(e)=>{
let query = document.querySelector(e)
query.style.right = '8px';
let timer = setTimeout(() => {
query.style.right = '-165px';
}, 8000);
clearTimeOut(timer)
}
document.addEventListener('click', (e) => {
let triggeredElement = e.target.className;
if (triggeredElement === 'container') {
document.querySelectorAll('.banishBanner').forEach(function(x) {
x.style.right = '-180px';
})
}
})
HTML:
<div class="container">
<button onclick="getGreetingBanner('.thankyou')" type="button" class="accept">Accept</button>
<button onclick="getGreetingBanner('.comeBackSoon')" type="button" class="cancel">Cancel</button>
<div class="banishBanner thankyou">Thank You!</div>
<div class="banishBanner comeBackSoon">Come back soon.</div>
</div>
You must clear timeout when you clicking again on same button.
var timeout;
const getGreetingBanner =(e)=>{
let query = document.querySelector(e)
query.style.right = '8px';
//clearing timeout
clearTimeout( timeout );
timeout = setTimeout(() => {
query.style.right = '-165px';
}, 8000);
}