I have several identical divs and each of them contains a button that is hidden. I want to make button visible when you hover on the parent div. I wrote this code:
const cardElements = document.querySelectorAll('.middle_section__president_section');
const learnButtons = document.querySelectorAll('.president_section__button');
cardElements.forEach((cardElement) => {
cardElement.addEventListener('mouseover', () => {
learnButtons.forEach((learnButton) => {
learnButton.style.height = "50px";
learnButton.style.opacity = "1";
learnButton.style.border = "3px solid rgb(129, 129, 129)";
});
});
cardElement.addEventListener('mouseout', () => {
learnButtons.forEach((learnButton) => {
learnButton.style.height = "0px";
learnButton.style.opacity = "0";
learnButton.style.border = "0px solid rgb(129, 129, 129)";
});
});
})
carElements is parent, learnButtons - child.
but with this code when i hover on one div buttons appears in every similiar div. How can i make button appear only on hovered div?
Use the Event object
cardElement.addEventListener('mouseover', () => {
learnButtons.forEach((learnButton) => {
convert this to
cardElement.addEventListener('mouseover', (e) => {
var learnButton = e.target;
There's no need to use JS for this. As Mister Jojo/traktor pointed out in their comments you can use the CSS :hover pseudo-class instead.
The key CSS line is .box:hover button { visibility: visible;} which means "when you hover over the parent container make its button visible".
.box { width: 50%; display: flex; flex-direction: column; border: 1px solid lightgray; margin: 0.25em; padding: 0.25em;}
button { visibility: hidden; margin: 0.25em 0; border-radius: 5px; background-color: lightgreen; }
.box:hover button { visibility: visible;}
.box:hover, button:hover { cursor: pointer; }
<section class="box">
Some text
<button>Click for a surprise!</button>
</section>
<section class="box">
Some text
<button>Click for a surprise!</button>
</section>
<section class="box">
Some text
<button>Click for a surprise!</button>
</section>
It is bad practice to iterate over all elements and give each an event, as you can add 1 event handler to the parent and when the event happens you can check the affected element by the event parameter in the handler call back
parent.addEVentListener('mouseover', (e) => {
if(e.target.classList.contains('middle_section__president_section')) {
// Do
}
});
Related
When the box is clicked on the insides for each of the boxes are shown, I only want one to show up at a time.
function select() {
const outside = document.querySelectorAll('.box')
const insides = document.querySelectorAll('.insides')
insides.forEach(insides => {
outside.forEach(box => {
box.addEventListener('mouseenter', (e) => {
box.setAttribute("id", "selected")
box.addEventListener('click', (e) => {
box.classList.add('hover')
if (document.getElementById('selected')) {
insides.classList.add('insidesHover')
}
})
})
box.addEventListener('mouseleave', (e) => {
box.classList.remove('hover')
box.setAttribute('id', 'testBox')
insides.classList.remove('insidesHover')
})
})
})
}
function newOption() {
var optionRow = document.createElement("div");
optionRow.setAttribute("class", "answers");
optionRow.setAttribute("id", "optionRow");
var option = document.createElement("input");
option.setAttribute("type", "radio");
option.setAttribute("name", "options");
option.setAttribute("id", "options");
var optionBox = document.createElement("div");
optionBox.setAttribute("class", "answerContainer")
optionBox.setAttribute("id", "optionBox")
var text = document.createElement("input");
text.setAttribute("type", "text");
text.setAttribute("name", "option");
text.setAttribute("id", "option");
text.setAttribute("placeholder", "Enter Option");
optionBox.append(optionRow);
optionRow.append(option);
optionRow.append(text);
document.getElementById("selected").append(optionRow);
array()
}
.testContainer {
width: 50%;
margin-left: 25%;
margin-top: 1%;
padding: 1%;
background-color: #333;
height: auto;
color: white;
}
.box {
background-color: white;
color: black;
padding: 25px;
border: 5px blue solid;
}
.hover {
border: #780119 5px solid;
}
.insides {
display: none;
}
.insidesHover {
display: flex;
}
.buttons {
display: none;
}
.buttonsHover {
display: flex;
height: 25px;
width: 25px;
border: 1px solid black;
border-radius: 100%;
}
<div class="testContainer">
<div class="box">
<div class="insides" id="testBox">
<input type="text" class="insidesHover">
<button onclick="newOption()" class="buttonsHover"> </button>
</div>
</div>
</div>
<div class="testContainer">
<div class="box">
<div class="insides" id="testBox">
<input type="text" class="insidesHover">
<button onclick="newOption()" class="buttonsHover"> </button>
</div>
</div>
</div>
<div class="testContainer">
<div class="box">
<div class="insides" id="testBox">
<input type="text" class="insidesHover">
<button onclick="newOption()" class="buttonsHover"> </button>
</div>
</div>
</div>
So the problem I am having is that I want to use a querySelectorAll() for the class of .box, which on click changes the outline to show it is being selected. Which is something that is fully functional and works. However, I also want it to show the inside pieces on click as well but only for one box at a time, which on the event listener of leave will disappear again. Once the inside of adding new options goes away, I need the options that were put in to stay. I have tried putting everything in one div class where the opacity is set to 0, but it makes it so the new options don't stay visible. I have also tried rearranging the variables so that the insides are affected first, which had no effect on the actual functionality. I believe the true issue lies in the fact that when the id, selected, is active it triggers all boxes to be active instead of individual ones. I am not entirely sure how to go about rectifying this issue and would like some advice on moving forward. If you have any questions or if something needs clarification please let me know! Thank you for your time and wish you all a good day!
I just started school, and this is my first question ever asked on Stackoverflow, so I apologize up front regarding both formatting and wording of this question.
I want to change the border color of my div to a style I have already declared when I click on it. To show that this has been selected.
I have three divs with id="red/green/pink".
Now, is there a way to change this function to grab information from the div I clicked, so I dont have to write 3 (almost) identical functions?
.chosenBorder{
border: 3px solid gold;
}
<div id="red" class="mainDivs" onclick="newColor('red')">Red?</div>
<div id="green" class="mainDivs" onclick="newColor('green')">Green?</div>
<div id="pink" class="mainDivs" onclick="newColor('pink')">Pink?</div>
<div class="mainDivs" onclick="whatNow(changeBig)">Choose!</div>
<script>
let changeBig = "";
let chosenDiv = document.getElementById("body");
function newColor(thisColor) {
changeBig = thisColor;
// something that make this part dynamic.classList.toggle("chosenBorder");
}
function whatNow(changeBig) {
document.body.style.backgroundColor = changeBig;
}
</script>
Since you already have an id contains the name of color; get the advantage of it: and keep track of the selected color in your variable changeBig.
let changeBig = "";
function newColor(div) {
// initial all divs to black
initialDivs();
div.style.borderColor = div.id;
changeBig = div.id;
}
function initialDivs() {
[...document.querySelectorAll('.mainDivs')].forEach(div => {
div.style.borderColor = 'black'
});
}
function whatNow() {
document.body.style.backgroundColor = changeBig;
}
.mainDivs {
padding: 10px;
margin: 10px;
border: 3px solid;
outline: 3px solid;
width: fit-content;
cursor: pointer;
}
<div id="red" class="mainDivs" onclick="newColor(this)">Red?</div>
<div id="green" class="mainDivs" onclick="newColor(this)">Green?</div>
<div id="pink" class="mainDivs" onclick="newColor(this)">Pink?</div>
<div class="mainDivs" onclick="whatNow()">Choose!</div>
There are a few (modern) modifications you can make to simplify things.
Remove the inline JS.
Use CSS to store the style information.
Use data attributes to store the colour rather than the id.
Wrap the div elements (I've called them boxes here) in a containing element. This way you can use a technique called event delegation. By attaching one listener to the container you can have that listen to events from its child elements as they "bubble up" the DOM. When an event is caught it calls a function that 1) checks that the event is from a box element 2) retrieves the color from the element's dataset, and adds it to its classList along with an active class.
// Cache the elements
const boxes = document.querySelectorAll('.box');
const container = document.querySelector('.boxes');
const button = document.querySelector('button');
// Add a listener to the container which calls
// `handleClick` when it catches an event fired from one of
// its child elements, and a listener to the button to change
// the background
container.addEventListener('click', handleClick);
button.addEventListener('click', handleBackground);
function handleClick(e) {
// Check to see if the child element that fired
// the event has a box class
if (e.target.matches('.box')) {
// Remove the color and active classes from
// all the boxes
boxes.forEach(box => box.className = 'box');
// Destructure the color from its dataset, and
// add that to the class list of the clicked box
// along with an active class
const { color } = e.target.dataset;
e.target.classList.add(color, 'active');
}
}
function handleBackground() {
// Get the active box, get its color, and then assign
// that color to the body background
const active = document.querySelector('.box.active');
const { color } = active.dataset;
document.body.style.backgroundColor = color;
}
.boxes { display: flex; flex-direction: row; background-color: white; padding: 0.4em;}
.box { display: flex; justify-content: center; align-items: center; width: 50px; height: 50px; border: 2px solid #dfdfdf; margin-right: 0.25em; }
button { margin-top: 1em; }
button:hover { cursor: pointer; }
.box:hover { cursor: pointer; }
.red { border: 2px solid red; }
.green { border: 2px solid green; }
.pink { border: 2px solid pink; }
<div class="boxes">
<div class="box" data-color="red">Red</div>
<div class="box" data-color="green">Green</div>
<div class="box" data-color="pink">Pink</div>
</div>
<button>Change background</button>
I would like to open and close overlay using single button, so when the button is clicked an additional class is added, when closed the class is removed and overlay is closed.
So far I wrote the code that opens overlay and add/remove the class to the button.
Also I've created the method to close the overlay but I'm struggling to create a proper event to actually close it, so I would be happy if anyone can guide me a bit.
I think there should be an 'if' statement within the events() checking if the button have added class, if so, the overlay will be closed using this function element.classList.contains("active");
Also the button is animated, so when class is added 3 bars (hamburger icon) becomes X and this is the main reason I don't want to have separate buttons to open and close, I already achieved that but this is not what I'm looking for.
class OverlayNav {
constructor() {
this.injectHTML()
this.hamburgerIcon = document.querySelector(".menu-icon")
this.events()
}
events() {
this.hamburgerIcon.addEventListener("click", () => this.overlayOpen())
}
overlayOpen() {
document.getElementById("myNav").style.width = "100%";
this.hamburgerIcon.classList.toggle("menu-icon--close-x")
}
overlayClose() {
document.getElementById("myNav").style.width = "0%";
}
injectHTML() {
document.body.insertAdjacentHTML('beforeend', `
<div id="myNav" class="overlay">
<p>My Overlay</p>
</div>
`)
}
}
export default OverlayNav
You can make a function with a if statement handle Opening and closing the overlay
Here is your code edited
class OverlayNav {
constructor() {
this.injectHTML();
this.hamburgerIcon = document.querySelector(".menu-icon");
this.events();
}
events() {
this.hamburgerIcon.addEventListener("click", () => this.overlayHandle());
}
overlayOpen() {
document.getElementById("myNav").style.width = "100%";
this.hamburgerIcon.classList.toggle("menu-icon--close-x");
}
overlayClose() {
document.getElementById("myNav").style.width = "0%";
}
overlayHandle() {
if (element.classList.contains("active")) {
this.overlayClose();
} else {
this.overlayOpen();
}
}
injectHTML() {
document.body.insertAdjacentHTML(
"beforeend",
`
<div id="myNav" class="overlay">
<p>My Overlay</p>
</div>
`
);
}
}
export default OverlayNav;
You can add a property that keeps track of the state of the nav bar.
constructor() {
this.injectHTML()
this.hamburgerIcon = document.querySelector(".menu-icon")
this.events()
this.overlayVisible=true;
}
Then add a method that toggles the state and calls the right open/close-method:
toggleOverlay() {
if (this.overlayVisible)
this.overlayOpen();
else
this.overlayClose();
this.overlayVisible=!this.overlayVisible;
}
Finally make the events method call toggleOverlay() instead of overlayOpen().
events() {
this.hamburgerIcon.addEventListener("click", () => this.toggleOverlay())
}
Alternativly, a pure HTML + CSS solution, using only the details element and the [open] CSS attribute selector.
.overlay > p {
padding: 1rem;
margin: 0;
display: flex;
flex-direction: column;
width: 25vw
}
.overlay summary {
padding: 1rem 0.5rem;
cursor: pointer;
max-height: 90vh;
overflow: auto;
font-size: 4em;
list-style: none;
}
.overlay[open] summary {
background: black;
color: white;
padding: 0.5rem;
font-size: 1em;
}
.overlay[open] {
position: fixed;
/* top: calc(50% - 25vw); */
left: calc(50% - 15vw);
outline: 5000px #00000090 solid;
border: 5px red solid;
border-radius: 0.5rem;
font-size: 1em
}
.overlay[open] summary::after {
content: '❌';
float: right;
}
<details class="overlay">
<summary>☰</summary>
<p>
Hello world!
</p>
</details>
I created a sidebar toggle and also gave background opacity but when I click another area or when closed the sidebar the background opacity didn't close. when I click the button the dropdown-content show and the background-opacity show but when I click again on the button the dropdown content closed but the background opacity does not close. How I did it. Please help me. I gave the code below. If someone can help me it will be very helpful for me. I try so many times but in the end, I can't do it. 😥
function toggleDropDown(id) {
document.querySelectorAll('.dropdown-content').forEach(el => el.id === id ? el.classList.toggle('show') : el.classList.remove("show"));
document.body.style.backgroundColor = "rgba(0,0,0,0.4)";
}
document.addEventListener('click', (e) => {
// cases where we want to close the dropdown
if (e.target.closest(".dropdown") === null) {
document.querySelectorAll('.dropdown-content').forEach(el => el.classList.remove("show"));
}
});
*{margin:0;padding:0;}
.dropdown-content{
position: fixed;
width: 30%;
height:100%;
background-color: rgb(255,0,0);
margin-left: 20%;
top:0;
display:none;
z-index:100;
}
.dropbtn{width:20%}
.show{display:block;}
<div class="dropdown">
<button class="dropbtn" onclick="toggleDropDown('openContent')">open</button>
<div class="dropdown-content" id="openContent">Hello, Div</div>
</div>
<h1>Hello World, Heading 1</h1>
First, I would suggest to create a variable that keeps track of the current state of dropdown – which is either true or false.
Plus, I don’t see the benefit in using document.querySelectorAll and having to loop over every element, when you could just directly get the element by addressing its id (openContent)... unless you would want to reuse the function for other cases?
As you are also listening on a click event on the document, we have to watch out for event bubbling by calling e.stopPropagation. In the case of the button for example, this means that we only fire the event for the button but not for the document. Since the button is a child of the document, it would otherwise detect a click event for both and fire twice.
const dropdownContent = document.getElementById("openContent");
let dropDownVisible = false;
function toggleDropDown(e) {
// we need this to prevent the event bubbling from the dropdown button to the document
e.stopPropagation();
// set the dropDownVisible state to the opposite it has been before
dropDownVisible = !dropDownVisible;
if (dropDownVisible) {
dropdownContent.classList.add("show");
document.body.classList.add("bgcolor");
} else {
dropdownContent.classList.remove("show");
document.body.classList.remove("bgcolor");
}
}
// we need this to prevent the event bubbling from the dropdown to the document
dropdownContent.addEventListener("click", (e) => {
e.stopPropagation();
});
// listen for click events on the document
document.addEventListener("click", (e) => {
// -> if the dropdown is visible, toggle its state
dropDownVisible && toggleDropDown(e);
});
* {
margin: 0;
padding: 0;
}
.dropdown-content {
position: fixed;
width: 30%;
height: 100%;
background-color: rgb(255, 0, 0);
margin-left: 20%;
top: 0;
display: none;
z-index: 100;
}
.dropbtn {
width: 20%;
}
.show {
display: block;
}
.bgcolor {
background-color: rgba(0, 0, 0, 0.4);
}
<body>
<div class="dropdown">
<button class="dropbtn" onclick="toggleDropDown(event)">
open
</button>
<div class="dropdown-content" id="openContent">Hello, Div</div>
</div>
<h1>Hello World, Heading 1</h1>
</body>
You will have to remove also the "body" background.
document.body.style.backgroundColor = "initial";
function toggleDropDown(id) {
document.querySelectorAll('.dropdown-content').forEach(el => el.id === id ? el.classList.toggle('show') : el.classList.remove("show"));
document.body.style.backgroundColor = "rgba(0,0,0,0.4)";
}
document.addEventListener('click', (e) => {
// cases where we want to close the dropdown
if (e.target.closest(".dropdown") === null) {
document.querySelectorAll('.dropdown-content').forEach(el => el.classList.remove("show"));
document.body.style.backgroundColor = "initial";
}
});
*{margin:0;padding:0;}
.dropdown-content{
position: fixed;
width: 30%;
height:100%;
background-color: rgb(255,0,0);
margin-left: 20%;
top:0;
display:none;
z-index:100;
}
.dropbtn{width:20%}
.show{display:block;}
<div class="dropdown">
<button class="dropbtn" onclick="toggleDropDown('openContent')">open</button>
<div class="dropdown-content" id="openContent">Hello, Div</div>
</div>
<h1>Hello World, Heading 1</h1>
Add class to body document.body.classList.add('bgcolor'); and remove after close the dropdown
function toggleDropDown(id) {
document.querySelectorAll('.dropdown-content').forEach(el => el.id === id ? el.classList.toggle('show') : el.classList.remove("show"));
document.body.classList.add('bgcolor');
}
document.addEventListener('click', (e) => {
// cases where we want to close the dropdown
if (e.target.closest(".dropdown") === null) {
document.querySelectorAll('.dropdown-content').forEach(el => el.classList.remove("show"));
document.body.classList.remove('bgcolor');
}
});
*{margin:0;padding:0;}
.dropdown-content{
position: fixed;
width: 30%;
height:100%;
background-color: rgb(255,0,0);
margin-left: 20%;
top:0;
display:none;
z-index:100;
}
.dropbtn{width:20%}
.show{display:block;}
.bgcolor{
background-color:rgba(0,0,0,0.4);
}
<div class="dropdown">
<button class="dropbtn" onclick="toggleDropDown('openContent')">open</button>
<div class="dropdown-content" id="openContent">Hello, Div</div>
</div>
<h1>Hello World, Heading 1</h1>
I have code written to allow an HTML element to be dragged after the mouse has been down over that element for a certain period of time.
The problem is that when I am using native HTML drag and drop, and I enable the draggable property when this timeout is up (the mouse has been down on that element for that period of time), if the mouse had been moved while it was down before that timeout was up, HTML will not trigger a dragstart event or even start dragging the element.
There is an example below.
var t;
function startDelayedDrag() {
clearTimeout(t);
document.getElementById('dragtarget').draggable = false;
console.log('mousedown')
t = setTimeout(function() {
console.log('dragging enabled')
document.getElementById('dragtarget').draggable = true;
}, 1000);
}
.droptarget {
float: left;
width: 100px;
height: 35px;
margin: 15px;
padding: 10px;
border: 1px solid #aaaaaa;
user-select: none;
}
<div class="droptarget">
<p onmousedown="startDelayedDrag()" id="dragtarget">Drag me!</p>
</div>
<div class="droptarget"></div>
This one is tricky and it might be different from what you had in mind, but here is goes an idea how to solve your issue:
Start the drag event
Hide the drag object by setting an image using setDragImage
Clone the drag element node, hide the clone and add it to the document (since it's not possible to change the image set by setDragImage)
Start the timeout to change the visibility of the ghost element
This could be yet improved in many ways, but I think you can get the mechanics of how it works as it is. As a reference see the following snippet:
const [$drag] = document.getElementsByClassName('drag')
const [$pixel] = document.getElementsByClassName('pixel')
let $ghost = null
$drag.addEventListener("dragstart", e => {
// set the current draged element invisible
e.dataTransfer.setDragImage($pixel, 0, 0)
// create a ghost element
$ghost = $drag.cloneNode(true)
$ghost.style.position = "absolute"
$ghost.style.display = "none"
document.body.appendChild($ghost)
setTimeout(() => {
$ghost.style.display = 'block'
}, 1000)
})
$drag.addEventListener("drag", e => {
// keep the ghost position to follow the mouse while dragging
$ghost.style.left = `${e.clientX}px`
$ghost.style.top = `${e.clientY}px`
}, false);
$drag.addEventListener("dragend", e => {
// remove the ghost
if ($ghost.parentNode) $ghost.parentNode.removeChild($ghost)
}, false)
.content {
display: flex;
}
.box {
width: 100px;
height: 35px;
padding: 10px;
margin: 10px;
border: 1px solid #aaaaaa;
}
.drop {
user-select: none;
}
.drag {
text-align: center;
}
.pixel {
width: 1px;
height: 1px;
background-color: white;
}
<div class="content">
<div draggable="true" class="drag box">Drag</div>
<div class="drop box"></div>
<div class="pixel"></div>
</div>