Mouseout not triggered when child element is removed - javascript

I have a dropdown menu with mouseover/mouseout handlers for tooltips. The problem is, if the menu is closed while the mouse is over it, mouseout handler does not fire. I can reproduce it with the following example:
const d1 = document.getElementById("d1");
const d2 = document.getElementById("d2");
let d3 = null;
function toggle() {
if (!d3) {
d3 = document.createElement("div");
d3.id = "d3";
d1.appendChild(d3);
} else {
d1.removeChild(d3);
d3 = null;
}
}
d1.addEventListener("mouseover", () => d1.classList.add("hover"));
d1.addEventListener("mouseout", () => d1.classList.remove("hover"));
d1.addEventListener("click", () => toggle());
#d1 { background-color: green; display: inline-block; position: relative; }
#d1.hover { background-color: red; }
#d2 { width: 400px; height: 50px; }
#d3 { position: absolute; width: 400px; height: 300px; background: green; top: 100%; }
<div id="d1">
<div id="d2"></div>
</div>
Clicking on the menu to close it causes the dropdown to get stuck in the red "hover" state.
Is there a workaround for this that doesn't involve checking mouse position when the dropdown closes?

The DOM isn't resizing d1 when d3 is removed, so it still thinks d1 is being hovered over. You need to manually remove the hover class from d1 when d3 is removed, then the DOM will recognize that d1 has shrunk. Add this to the end of the else statement:
d1.classList.remove("hover");
const d1 = document.getElementById("d1");
const d2 = document.getElementById("d2");
let d3 = null;
function toggle() {
if (!d3) {
d3 = document.createElement("div");
d3.id = "d3";
d1.appendChild(d3);
} else {
d1.removeChild(d3);
d3 = null;
d1.classList.remove("hover");
}
}
d1.addEventListener("mouseover", () => d1.classList.add("hover"));
d1.addEventListener("mouseout", () => d1.classList.remove("hover"));
d1.addEventListener("click", () => toggle());
#d1 { background-color: green; display: inline-block; position: relative; }
#d1.hover { background-color: red; }
#d2 { width: 400px; height: 50px; }
#d3 { position: absolute; width: 400px; height: 300px; background: green; top: 100%; }
<div id="d1">
<div id="d2"></div>
</div>

Related

How to update input[type="range"] "value" attribute on clicking a div?

I am working on custom range slider for my website. Everything is working fine but I am stuck in to make it possible that on clicking specific box slider(input[type="range"]) gets updated according to the data-label value of that specific div, so that we do not have to drag slider's thumb to update the custom bar? .Attached is the source code, Where I had Shared what I had achieved so far and What is left, For the convivence I had set opacity of range input to 0.1 so you can get the idea of what's going backend. Any suggestions or help would be appreciated. Thank you!
const rangeSlider = document.querySelector('#price_slider');
rangeSlider.addEventListener("input", rangeScript);
const customProgress = document.querySelector('#customProgress');
for(let i = 0; i < rangeSlider.max - rangeSlider.min ; i++){
const step = document.createElement('div');
step.classList.add('step');
step.setAttribute('data-label', +rangeSlider.min + i + 1);
customProgress.appendChild(step);
}
customProgress.querySelector(`.step[data-label="${rangeSlider.value}"]`)
.classList.add('current')
function rangeScript(value){
const target = document.getElementById('progress');
let newValue = parseInt(this.value);
const currentStep = customProgress.querySelector(`.step.current`);
if (currentStep) {
currentStep.classList.remove('current');
}
nextStep = customProgress.querySelector(`.step[data-label="${newValue}"]`);
if (nextStep) {
nextStep.classList.add('current')
}
}
#customProgress {
display: flex;
width: 100%;
height: 25px;
background: transparent;
}
.step {
position: relative;
background: yellow;
height: 100%;
width: 100%;
border-right: 1px solid black;
}
.step::after {
content: attr(data-label);
position: absolute;
top: -1em;
right: -.25em;
}
.step ~ .current,
.step.current {
background: blue;
}
<h1>what I have achieved</h1>
<div id="customProgress">
</div>
<div id="progress" style=" width: 100%;" >
<input id="price_slider" type="range" min="4" max="9" value="" style="opacity: 0.1; width: 100%; height: 50px" />
</div>
The key is to add an event listener to the step divs. It 'hears' a click, get's it's own value from the data-label dataset, applies that value to the range element, then finally triggers the rangeScript event handler. The rangeScript handler needed to be adjusted to work with event (e). Event listeners pass one argument in their functions, event and the element that triggered the listener is always event.target.
step.addEventListener('click', (e) => {
let val = e.target.dataset.label;
document.querySelector('#price_slider').value = val
rangeScript({target: rangeSlider}) // trigger the event using an adhoc representation of the rangeSlider event object
})
const rangeSlider = document.querySelector('#price_slider');
rangeSlider.addEventListener("input", rangeScript);
const customProgress = document.querySelector('#customProgress');
for (let i = 0; i < rangeSlider.max - rangeSlider.min; i++) {
const step = document.createElement('div');
step.classList.add('step');
step.setAttribute('data-label', +rangeSlider.min + i + 1);
step.addEventListener('click', (e) => {
let val = e.target.dataset.label;
document.querySelector('#price_slider').value = val
rangeScript({
target: rangeSlider
})
})
customProgress.appendChild(step);
}
customProgress.querySelector(`.step[data-label="${rangeSlider.value}"]`)
.classList.add('current')
function rangeScript(e) {
const target = document.getElementById('progress');
let newValue = parseInt(e.target.value);
const currentStep = customProgress.querySelector(`.step.current`);
if (currentStep) {
currentStep.classList.remove('current');
}
nextStep = customProgress.querySelector(`.step[data-label="${newValue}"]`);
if (nextStep) {
nextStep.classList.add('current')
}
}
#customProgress {
display: flex;
width: 100%;
height: 25px;
background: transparent;
}
.step {
position: relative;
background: yellow;
height: 100%;
width: 100%;
border-right: 1px solid black;
}
.step::after {
content: attr(data-label);
position: absolute;
top: -1em;
right: -.25em;
}
.step~.current,
.step.current {
background: blue;
}
<h1>what I have achieved</h1>
<div id="customProgress">
</div>
<div id="progress" style=" width: 100%;">
<input id="price_slider" type="range" min="4" max="9" value="" style="opacity: 0.1; width: 100%; height: 50px" />
</div>
Solution using method forEach().
const rangeSlider = document.querySelector("#price_slider");
rangeSlider.addEventListener("input", rangeScript);
const customProgress = document.querySelector("#customProgress");
for (let i = 0; i < rangeSlider.max - rangeSlider.min; i++) {
const step = document.createElement("div");
step.classList.add("step");
step.setAttribute("data-label", +rangeSlider.min + i + 1);
customProgress.appendChild(step);
}
customProgress.querySelector(`.step[data-label="${rangeSlider.value}"]`).classList.add("current");
function rangeScript(value) {
const target = document.getElementById("progress");
let newValue = parseInt(this.value);
const currentStep = customProgress.querySelector(`.step.current`);
if (currentStep) {
currentStep.classList.remove("current");
}
nextStep = customProgress.querySelector(`.step[data-label="${newValue}"]`);
if (nextStep) {
nextStep.classList.add("current");
}
}
const sections = document.querySelectorAll("#customProgress .step");
sections.forEach(function (section) {
section.addEventListener("click", function () {
sections.forEach(function (section) {
section.classList.remove("current");
});
this.classList.add("current");
rangeSlider.value = this.getAttribute("data-label");
});
});
#customProgress {
display: flex;
width: 100%;
height: 25px;
background: transparent;
}
.step {
position: relative;
background: yellow;
height: 100%;
width: 100%;
border-right: 1px solid black;
}
.step::after {
content: attr(data-label);
position: absolute;
top: -1em;
right: -0.25em;
}
.step ~ .current,
.step.current {
background: blue;
}
<h1>what I have achieved</h1>
<div id="customProgress"></div>
<div id="progress" style="width: 100%;">
<input id="price_slider" type="range" min="4" max="9" value="" style="opacity: 0.1; width: 100%; height: 50px;" />
</div>

Vanilla JS Hide/show DIV toggler

I'm trying to implement a way to display / hide a div element with vanilla JavaScript triggered by a click event. The hide function works well but I seem to be missing something important when it comes to displaying the div's again. I've verified that the toggler function is working.
Simple sandbox here:
https://codepen.io/pen/eYmOzVe
(function() {
"use strict";
// HTML References
var flags = document.querySelector(".flags");
// Toogle
var toogle = true;
// Flag object
var flagObject = {
init: function(part1, part2, part3, part4, part5) {
this.part1 = part1;
this.part2 = part2;
this.part3 = part3;
this.part4 = part4;
this.part5 = part5;
},
draw: function() {
flags.innerHTML += `
<div id="${this.part1}">
<div class="${this.part2}">
<div class="${this.part3}"></div>
<div class="${this.part4}"></div>
<div class="${this.part5}"></div>
</div>
</div>
`;
},
toogler: function(arg) {
toogle ? flagObject.remove(arg) : flagObject.show(arg);
toogle = !toogle;
},
remove: function(arg) {
if (arg == "1") {
flag1Element.style.visibility = "hidden";
}
if (arg == "2") {
flag2Element.style.visibility = "hidden";
}
},
show: function(arg) {
if (arg == "1") {
flag1Element.style.visibility = "visible";
}
if (arg == "2") {
flag2Element.style.visibility = "visible";
}
}
};
// Create instances of the object
var swedishFlag = Object.create(flagObject);
var japaneseFlag = Object.create(flagObject);
// Init
swedishFlag.init(
"flag1",
"flag-sweden",
"cross-one-sweden",
"cross-two-sweden"
);
japaneseFlag.init("flag2", "flag-japan", "circle-japan");
// Array containing all flags
var allObjects = [swedishFlag, japaneseFlag];
// Draws flags
for (let i = 0; i < allObjects.length; i++) {
allObjects[i].draw();
}
// HTML element refrences
var flag1Element = document.querySelector("#flag1");
var flag2Element = document.querySelector("#flag2");
// Add eventlisteners to remove flags on click
flag1Element.addEventListener("click", function() {
flagObject.toogler(1);
});
flag2Element.addEventListener("click", function() {
flagObject.toogler(2);
});
})();
h1 {
text-align: center;
}
h3 {
color: green;
}
.content {
border: 1px solid black;
background-color: #eee;
padding: 2em;
margin: 0 auto;
height: 1000px;
width: 800px;
border-radius: 30px;
text-align: center;
}
.flags {
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
height: 1000px;
}
.flag-sweden {
position: relative;
background-color: #006aa7;
height: 200px;
width: 320px;
margin-bottom: 2em;
}
.cross-one-sweden {
background-color: #fecc00;
position: absolute;
width: 40px;
height: 200px;
top: 0;
left: 100px;
}
.cross-two-sweden {
background-color: #fecc00;
position: absolute;
width: 320px;
height: 40px;
top: 80px;
left: 0;
}
.flag-japan {
position: relative;
height: 200px;
width: 320px;
background-color: white;
margin-bottom: 2em;
}
.circle-japan {
background-color: #bd0029;
height: 125px;
width: 125px;
border-radius: 50%;
position: absolute;
top: 50%;
left: 50%;
margin: -62.5px 0 0 -62.5px;
}
<h1>Sandbox</h1>
<div id="content" class="content">
<div class="flags"></div>
</div>
As Pavlin Petkov said in the comments, the image is not clickable when you hide it, so you can't toggle it back on. A simple solution to this that achieves the same result is to change the opacity instead of the visibility:
remove: function(arg) {
if (arg == "1") {
flag1Element.style.opacity = 0;
}
if (arg == "2") {
flag2Element.style.opacity = 0;
}
},
show: function(arg) {
if (arg == "1") {
flag1Element.style.opacity = 1;
}
if (arg == "2") {
flag2Element.style.opacity = 1;
}
}
This will display/hide a div with a click effect, and it will continue to occupy space on the page, as in your codepen. If you need to use visibility for some reason, I'd recommend a container div beneath the now hidden div which can trigger the show function; however, for the question at hand, this is sufficient.

How to induce some function when onmouseleave and also click the left mouse button?

I have got code like below:
Now I would like to add functionality that increase the counter not only when I leave the box/square, BUT ALSO click the left mouse button outside the box/square. So the counter will be increasing only when the mouse leave the box/square and also click outside of the box/square.
var counter = 0;
function myLeaveFunction() {
document.getElementById("demo").innerHTML = counter += 1;
}
div {
width: 100px;
height: 100px;
border: 1px solid black;
margin: 10px;
float: left;
padding: 30px;
text-align: center;
background-color: lightgray;
}
<div onmouseleave="myLeaveFunction()">
<p>onmouseleave: <br> <span id="demo">Mouse over and leave me AND CLICK OUTSIDE THE BOX!</span></p>
</div>
Add a boolean to check if mouse had left the div and bind a click handler to the document:
var counter = 0;
var mouseLeft = false;
function myLeaveFunction() {
document.getElementById("demo").innerHTML = counter+=1;
mouseLeft = true;
}
function clickBody() {
if (mouseLeft == true)
document.getElementById("demo").innerHTML = counter+=1;
mouseLeft = false;
}
document.onclick = clickBody;
This example will add an event listener to the body when the mouse leaves the box and will increase the counter when a click event occurs. The new event listener is then removed:
var counter = 0;
function myLeaveFunction() {
document.body.addEventListener("click", myClickHandler);
function myClickHandler() {
document.getElementById("demo").innerHTML = counter += 1;
document.body.removeEventListener("click", myClickHandler);
}
}
body {
margin: 0;
height: 100vh;
width: 100%;
}
div {
width: 100px;
height: 100px;
border: 1px solid black;
margin: 10px;
float: left;
padding: 30px;
text-align: center;
background-color: lightgray;
}
<body>
<div onmouseleave="myLeaveFunction()" onclick="event.stopPropagation()">
<p>onmouseleave: <br> <span id="demo">Mouse over and leave me AND CLICK OUTSIDE THE BOX!</span></p>
</div>
</body>
JSFiddle if this doesn't work
Like this?
Once you left the box AND you click outside of it. The counter will be increased.
var showPopup = function (id) {
document.getElementById(id).style.display = "block";
}
var hidePopup = function (id) {
document.getElementById(id).style.display = "none";
}
// Button Click
document.getElementById('myButton').onclick = (ev) => {
showPopup('myPopup');
ev.stopPropagation(); // prevent immediate close (because button is "outside" too)
}
// Click outside to close popup
document.getElementsByTagName('body')[0].onclick = (ev) => {
let popup = document.getElementById('myPopup');
if( ev.path.indexOf(popup) == -1 ) { hidePopup('myPopup') }
};
hidePopup('myPopup');
body {
width: 100vw;
height: 100vh;
}
div {
width: 100px;
height: 100px;
border: 1px solid black;
padding: 30px;
text-align: center;
background-color: lightgray;
position: absolute;
margin: 10px;
display: none;
}
<div id='myPopup'>
<p>onmouseleave: <br> <span id="demo">Mouse over and leave me AND CLICK OUTSIDE THE BOX!</span></p>
</div>
<button id='myButton'>Show!</button>
Take a look
This may be helful too Bootstrap Modal

Prevent inside contents inside parent container from making the parent disappear

I have this script that uses a toggle button to show or hide a targeted parent container. There is two correct ways that the parent class container hides by
pressing the toggle button the second time or by clicking outside the parent class container. So it works perfectly but any time I add any thing inside that
parent class container and I click those inside contents it makes the whole parent class container disappear how can I prevent that?
Here is my code
document.addEventListener('DOMContentLoaded', function(){
document.addEventListener('click',closeContainer);
function closeContainer(obj){
var containerVar = document.querySelector('.container');
if(obj.target.className != 'container') {
if (containerVar.style.display === 'flex') {
containerVar.style.display = 'none';
} else if(obj.target.id == 'toggle') {
containerVar.style.display = 'flex';
}
}
}
});
.container{
background-color: orange;
height: 350px;
width: 350px;
margin-left: auto;
margin-right: auto;
display: none;
justify-content: center;
}
.inner-container{
margin-top: 50px;
background-color: red;
height: 200px;
width: 200px;
display: inline-block;
}
<button id='toggle'>Toggle</button>
<div class='container'>
<div class='inner-container'></div>
</div>
You need to capture click events on your container and stop propagation, then there's no need to check the target in your document's event handler:
document.addEventListener('DOMContentLoaded', function() {
// if click event is inside container, then stop propagation
const container = document.querySelector('.container');
container.addEventListener('click', function(e) {
e.stopPropagation();
});
document.addEventListener('click', closeContainer);
function closeContainer(obj) {
var containerVar = document.querySelector('.container');
if (containerVar.style.display === 'flex') {
containerVar.style.display = 'none';
} else if (obj.target.id == 'toggle') {
containerVar.style.display = 'flex';
}
}
});
.container {
background-color: orange;
height: 350px;
width: 350px;
margin-left: auto;
margin-right: auto;
display: none;
justify-content: center;
}
.inner-container {
margin-top: 50px;
background-color: red;
height: 200px;
width: 200px;
display: inline-block;
}
<button id='toggle'>Toggle</button>
<div class='container'>
<div class='inner-container'></div>
</div>
Another solution would be to only have one event handler on document and check if the target or any of its ancestors have the container class (similar to jquery's closest method):
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('click', closeContainer);
function closeContainer(obj) {
if (closest(obj.target, '.container')) return;
var containerVar = document.querySelector('.container');
if (containerVar.style.display === 'flex') {
containerVar.style.display = 'none';
} else if (obj.target.id == 'toggle') {
containerVar.style.display = 'flex';
}
}
function closest(el, selector) {
const matchesSelector = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector;
while (el) {
if (matchesSelector.call(el, selector)) {
return el;
} else {
el = el.parentElement;
}
}
return null;
}
});
.container {
background-color: orange;
height: 350px;
width: 350px;
margin-left: auto;
margin-right: auto;
display: none;
justify-content: center;
}
.inner-container {
margin-top: 50px;
background-color: red;
height: 200px;
width: 200px;
display: inline-block;
}
<button id='toggle'>Toggle</button>
<div class='container'>
<div class='inner-container'></div>
</div>

Avoid mouseout when overlay is added on image tag

I have a requirement where I have attached mouseOver and mouseOut event to image tag. Also on mouse over I am adding a overlay div on image tag.
So now even my mouse is on image, still mouseOut event is occurring because of overlay added. Below is my code:
document.getElementsByTagName('img')[0].addEventListener('mouseover', function(event){
let Wrapper = document.createElement('div');
Wrapper.classList.add('Wrapper');
Wrapper.id = 'wrapper';
let parentElement = event.currentTarget.parentElement;
let elementExists = document.getElementById('wrapper');
if (!elementExists) {
parentElement.appendChild(Wrapper);
}
});
document.getElementsByTagName('img')[0].addEventListener('mouseout', function(event){
if (document.getElementById('wrapper')) {
document.getElementById('wrapper').remove();
}
});
.col-md-6 {
width: 375px;
height: 211px;
margin: 20px;
position: relative;
}
.Wrapper {
display: table;
position: absolute;
background-color: rgba(0, 0, 0, 0.5);
height: 100% !important;
width: 100%;
text-align: center;
z-index: 1000;
font-family: arial;
color: #fff;
top: 0;
}
.redirectCenter {
display: table-cell;
vertical-align: middle;
}
<div class="col-md-6">
<img src="https://www.blog.google/static/blog/images/google-200x200.7714256da16f.png" />
</div>
As you can see in above code, that mouseout is happening and overlay is flickering, I tried a lot but not able to find out way to stop mouseout even after adding overlay.
NOTE:
I want to add mouseover event to image tag only.
Also, I want to do this in pure JavaScript.
EDIT:
document.getElementsByTagName('img')[0].addEventListener('mouseover', function(event){
let elementExists = document.getElementById('wrapper');
let Center = document.createElement('div');
let Text = document.createElement('div');
if (!elementExists) {
let Wrapper = document.createElement('div');
let parentElement = event.currentTarget.parentElement;
Wrapper.classList.add('Wrapper');
Wrapper.id = 'wrapper';
Center.classList.add('Center');
Text.innerHTML = "Sample text";
parentElement.appendChild(Wrapper);
Wrapper.appendChild(Center);
Center.appendChild(Text);
Wrapper.addEventListener('mouseout', function(event){
if (document.getElementById('wrapper')) {
document.getElementById('wrapper').remove();
}
});
}
});
.col-md-6 {
width: 375px;
height: 211px;
margin: 20px;
position: relative;
}
.Wrapper {
display: table;
position: absolute;
background-color: rgba(0, 0, 0, 0.5);
height: 100% !important;
width: 100%;
text-align: center;
z-index: 1000;
font-family: arial;
color: #fff;
top: 0;
}
.Center {
display: table-cell;
vertical-align: middle;
}
<div class="col-md-6">
<img src="https://www.blog.google/static/blog/images/google-200x200.7714256da16f.png" />
</div>
Thanks for the help! I have an updated code where again its flickering on mouse over reason is I am appending another div in wrapper div.
Please let me know possible solution. I am trying to control event object but not able to do it exactly.
I think u should track when your mouse out from the wrapper. Cause when u add wrapper all mouse move would be tracked as move out from image, cause wrapper overlaps image. Like this
document.getElementsByTagName('img')[0].addEventListener('mouseenter', function(event){
let elementExists = document.getElementById('wrapper');
let Center = document.createElement('div');
let Text = document.createElement('div');
if (!elementExists) {
let Wrapper = document.createElement('div');
let parentElement = event.currentTarget.parentElement;
Wrapper.classList.add('Wrapper');
Wrapper.id = 'wrapper';
Center.classList.add('Center');
Text.innerHTML = "Sample text";
parentElement.appendChild(Wrapper);
Wrapper.appendChild(Center);
Center.appendChild(Text);
Wrapper.addEventListener('mouseleave', function(event){
if (document.getElementById('wrapper')) {
document.getElementById('wrapper').remove();
}
});
}
});
.col-md-6 {
width: 375px;
height: 211px;
margin: 20px;
position: relative;
}
.Wrapper {
display: table;
position: absolute;
background-color: rgba(0, 0, 0, 0.5);
height: 100% !important;
width: 100%;
text-align: center;
z-index: 1000;
font-family: arial;
color: #fff;
top: 0;
}
.Center {
display: table-cell;
vertical-align: middle;
}
<div class="col-md-6">
<img src="https://www.blog.google/static/blog/images/google-200x200.7714256da16f.png" />
</div>
The "wrapper" is above the image and so the mouseout will occur right after insertion. Add the mouseout handler to the wrapper, not to the img.
document.getElementsByTagName('img')[0].addEventListener('mouseover', function(event){
let elementExists = document.getElementById('wrapper');
if (!elementExists) {
let Wrapper = document.createElement('div');
let parentElement = event.currentTarget.parentElement;
Wrapper.classList.add('Wrapper');
Wrapper.id = 'wrapper';
parentElement.appendChild(Wrapper);
Wrapper.addEventListener('mouseout', function(event){
if (document.getElementById('wrapper')) {
document.getElementById('wrapper').remove();
}
});
}
});
.col-md-6 {
width: 375px;
height: 211px;
margin: 20px;
position: relative;
}
.Wrapper {
display: table;
position: absolute;
background-color: rgba(0, 0, 0, 0.5);
height: 100% !important;
width: 100%;
text-align: center;
z-index: 1000;
font-family: arial;
color: #fff;
top: 0;
}
.redirectCenter {
display: table-cell;
vertical-align: middle;
}
<div class="col-md-6">
<img src="https://www.blog.google/static/blog/images/google-200x200.7714256da16f.png" />
</div>

Categories

Resources