Toggling Between a CSS class with pure javascript on 'n' elements - javascript

Working on creating functionality where when the user clicks on one of the products (each of the elements have the same assigned ID card-reveal) it adds a CSS class to the container specifically clicked (active state) to show information for that specific item and then finally, when the user clicks the cancel button the CSS class is removed (activate state gone).
Unfortunately I have run to a few hiccups where when I click on the 1st element it adds the class to that element but the other elements I click do not add the class, as well the close button does not function at all. I would like to finish the solution in Pure Javascript. Also if you see a few classie() methods, I am using Classie.js to help with CSS class toggling.
Any help will be appreciated! Thank You!
Html
<a id="card-reveal" class="card-view" href="javascript:void(0)"><h3 class='hover-title'>View More</h3></a>
<div class="card-cover">
<span class="card-exit"></span>
<a class="card-follow" href="javascript:void(0)">Follow {{object.product_website_name}}.com</a>
<a class="card-buy" target="_blank" href="{{object.product_slug_url}}">Buy {{object.product_name }}</a>
<a id="card-close" class="card-info" href="javascript:void(0)"><span class="icon-indie_web-03"></span></a>
<ul class="card-social">
<label>Share</label>
<li><span class="icon-indie_web-04"></span></li>
<li><span class="icon-indie_web-05"></span></li>
</ul>
</div>
CSS
.card-cover {
width:100%;
height: 100%;
background: none repeat scroll 0% 0% rgba(255, 91, 36, 0.9);
color: #FFF;
display: block;
position: absolute;
opacity: 0;
z-index:200;
overflow: hidden;
-webkit-transform:translate3d(0, 400px, 0);
transform:translate3d(0, 400px, 0);
-webkit-backface-visibility:hidden;
backface-visibility: hidden;
-webkit-transition-property:opacity, transform;
transition-property:opacity, transform;
-webkit-transition-duration:0.2s;
transition-duration:0.2s;
-webkit-transition-timing-function:cubic-bezier(0.165, 0.84, 0.44, 1);
transition-timing-function:cubic-bezier(0.165, 0.84, 0.44, 1);
-webkit-transition-delay: 0s;
transition-delay: 0s;
}
.card-cover.card--active {
opacity: 1;
-webkit-transform:translate3d(0, 0, 0);
transform:translate3d(0, 0px, 0);
}
JS below:
var cardContainer = document.querySelector('.card-cover'),
cardTargets = Array.prototype.slice.call( document.querySelectorAll( '#card-reveal' ) ),
eventType = mobilecheck() ? 'touchstart' : 'click',
cardClose = document.getElementById('card-close'),
resetMenu = function() {
classie.remove( cardContainer, 'card--active' );
},
resetMenuClick = function( ) {
cardCloseaddEventListener(globalMenuEventType, function() {
resetMenu();
document.removeEventListener(eventType, resetMenuClick);
}, false);
};
cardTargets.forEach(function (element, index) {
if( element.target ) {
element.addEventListener(eventType, function( event ) {
event.preventDefault();
event.stopPropagation();
classie.add(cardContainer, 'card--active');
document.addEventListener(eventType, resetMenuClick);
} ,false);
}
});

There are two simple ways I can think of doing something like this.
First, if you can't designate ID's for each card (which it sounds like you can't), you're going to have to go by class names. Like it was mentioned in the comments, you really don't want to use the same ID for multiple elements.
Part of the reason for this is, as you can see from my examples below, that the .getElementById() method is only meant to return one element, where the other methods like .getElementsByClassName() will return an array of elements.
The problem we're trying to solve is that the sub-content you want to display/hide has to be attached to the element you click somehow. Since we're not using ID's and you can't really rely on class names to be unique between elements, I'm putting the div with the information inside a container with the element that toggles it.
Inside a container div, are two divs for content. One is the main content that's always visible, the other is the sub-content that only becomes visible if the main content is clicked (and becomes invisible when clicked again).
The benefit of this method is that since there are no ID's to worry about, you can copy/paste the cards and they'll each show the same behaviour.
var maincontent = document.getElementsByClassName("main-content");
// Note: getElemenstByClassName will return an array of elements (even if there's only one).
for (var i = 0; i < maincontent.length; i++) {
//For each element in the maincontent array, add an onclick event.
maincontent[i].onclick = function(event) {
//What this does is gets the first item, from an array of elements that have the class 'sub-content', from the parent node of the element that was clicked:
var info = event.target.parentNode.getElementsByClassName("sub-content")[0];
if (info.className.indexOf("show") > -1) { // If the 'sub-content' also contains the class 'show', remove the class.
info.className = info.className.replace(/(?:^|\s)show(?!\S)/g, '');
} else { // Otherwise add the class.
info.className = info.className + " show";
}
}
}
.container {
border: 1px solid black;
width: 200px;
margin: 5px;
}
.main-content {
margin: 5px;
cursor: pointer;
}
.sub-content {
display: none;
margin: 5px;
}
.show {
/* The class to toggle */
display: block;
background: #ccc;
}
<div class="container">
<div class="main-content">Here is the main content that's always visible.</div>
<div class="sub-content">Here is the sub content that's only visible when the main content is clicked.</div>
</div>
<div class="container">
<div class="main-content">Here is the main content that's always visible.</div>
<div class="sub-content">Here is the sub content that's only visible when the main content is clicked.</div>
</div>
<div class="container">
<div class="main-content">Here is the main content that's always visible.</div>
<div class="sub-content">Here is the sub content that's only visible when the main content is clicked.</div>
</div>
The second method, would be to use one div for the content that you want to show/hide, and clicking on an element will toggle both its visibility and it's content.
I'll use the previous example as a base, but ideally you would have some kind of MVVM framework like react, knockout, or angular to help you with filling in the content. For the sake of this example, I'm just going to use the text from the div of sub-content.
var info = document.getElementById("Info");
var maincontent = document.getElementsByClassName("main-content");
for (var i = 0; i < maincontent.length; i++) { //getElemenstByClassName will return an array of elements (even if there's only one).
maincontent[i].onclick = function(event) { //For each element in the maincontent array, add an onclick event.
//This does the same as before, but I'm getting the text to insert into the info card.
var text = event.target.parentNode.getElementsByClassName("sub-content")[0].innerHTML;
info.innerHTML = text; // Set the text of the info card.
info.style.display = "block"; //Make the info card visible.
}
}
info.onclick = function(event) {
info.style.display = "none"; // If the info card is ever clicked, hide it.
}
.container {
border: 1px solid black;
width: 200px;
margin: 5px;
padding: 5px;
}
.main-content {
margin: 5px;
cursor: pointer;
}
.sub-content {
display: none;
margin: 5px;
}
#Info {
cursor: pointer;
display: none;
}
<div id="Info" class="container">Here is some test information.</div>
<div class="container">
<div class="main-content">Link 1.</div>
<div class="sub-content">You clicked link 1.</div>
</div>
<div class="container">
<div class="main-content">Link 2.</div>
<div class="sub-content">You clicked link 2.</div>
</div>
<div class="container">
<div class="main-content">Link 3.</div>
<div class="sub-content">You clicked link 3.</div>
</div>

Related

How to toggle div properties by clicking it?

I want to make a window that expands when clicked and closes when clicked again. I am using flask to display all lines of data but that should not be a problem and can actually be ignored. Right now I have it set that when you click the div it expands but once you let go the div closes again. Is there any way I can turn this div into a toggle of some kind many using python or javascript or even CSS?
HTML/Python flask:
<div class="container">
{%for i, value in verb_data.items() %}
<div class="indevidual_verbs">{{ i }} . {{ value }}</div><br>
{%endfor%}
</div>
CSS:
.indevidual_verbs {
cursor: pointer;
}
.indevidual_verbs:active {
padding-bottom: 300px;
}
Depending on what you want to do, you could even use the details html element, that automatically implements that functionality.
If you can use javascript, there is a way to easily toggle a class:
// Get a reference to the container
const container = document.getElementById("container");
// When we click on the container...
container.addEventListener("click", function (e) {
// we can toggle the "open" class
this.classList.toggle("open");
});
/* Just a way to show the container */
#container {
padding: 20px;
border: solid 1px #000;
}
/* Hide the content (you can do it in many different ways) */
#container .inner {
display: none;
}
/* Show the content when the "open" class is added to the container */
#container.open .inner {
display: block;
}
<div id="container">
<div class="inner">
This is just some example text
</div>
</div>

Collapsible div in flex container opens the other divs as well

I have a group of expandable divs inside a CSS flex container .
Codepen - https://codepen.io/vijayvmenon/pen/bGNRwvd
CSS:
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
When I click on any div, the other divs in the row also expand. How can I fix this by expanding only the div I clicked and not expanding the other divs in the row?
I am using the flex container because im building a responsive page and there are more components, which are not included here. Is it possible to do with a flex/grid container ? Please let me know.
Any other solution by which I can wrap the divs and make it responsive should be fine.
I created a similar issue where I am using the polymer framework - Collapsible div inside a CSS grid container expands full width
Since I didn't get any help here, thought of creating one in Vanilla Javascript. Please help.
Note: I don't know the actual number of items beforehand and it is rendered using a "dom-repeat" in polymer library.So I can only have a container div enclosing the complete set of items. I can't have enclosing divs for each column of items (unless there is a way to do that)
Here is the code below (vanilla JS as I don't use polymer). It is responsive and will adjust the amount of column based on your item width (which I assume you will change using media-query). The idea is:
Measure the item width you specified in CSS by creating a dummy item
Adjust the number of columns needed based on measured item width. It is necessary to wrap the items in columns because it shouldn't be possible to do what you want by wrapping everything in one single flex container.
Create the elements dynamically using vanilla JS (use polymer in your case)
Add items to the columns one by one
The logic for clicking is still the same as yours
Here is the full working example:
let container = document.querySelector('.container')
let columnAmount = (() => {
let containerWidth = parseInt(getComputedStyle(container).width)
let dummyItem = document.createElement('div')
let itemWidth
dummyItem.classList.add('item')
container.appendChild(dummyItem)
itemWidth = parseInt(getComputedStyle(dummyItem).width)
dummyItem.remove()
return Math.floor(containerWidth / itemWidth)
})()
let newColumns = []
for (let i = 0; i < columnAmount; i++) {
newColumns.push(document.createElement('div'))
newColumns[i].classList.add('item')
container.appendChild(newColumns[i])
}
let childAmount = 11 // Change this to your needs
let newChild
let newCollapsibleButton
let newContent
let newContentParagraph
for (let i = 0; i < childAmount; i++) {
newChild = document.createElement('div')
newCollapsibleButton = document.createElement('button')
newContent = document.createElement('div')
newContentParagraph = document.createElement('p')
newChild.classList.add('item--inner')
newCollapsibleButton.classList.add('collapsible')
newContent.classList.add('content')
newCollapsibleButton.appendChild(document.createTextNode(`Open Section ${i + 1}`))
newContentParagraph.appendChild(document.createTextNode(`Section ${i + 1} Details`))
newContent.appendChild(newContentParagraph)
newChild.appendChild(newCollapsibleButton)
newChild.appendChild(newContent)
newColumns[i % columnAmount].appendChild(newChild)
}
let collapsibleButtons = document.querySelectorAll(".collapsible");
for (let i = 0; i < collapsibleButtons.length; i++) {
collapsibleButtons[i].addEventListener('click', function() {
let content = this.nextElementSibling;
this.classList.toggle("active");
content.style.maxHeight = content.style.maxHeight ? null : `${content.scrollHeight}px`
});
}
.container {
display:flex;
justify-content:space-between;
flex-wrap:wrap;
}
.item {
width: 30%;
}
.item--inner {
width: 100%;
}
.collapsible {
background-color: #777;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
<div class = "container">
</div>
Note that it doesn't handle page resizing, i.e. the items will be at that fixed state on page load (handle page resizing whenever necessary). For convenience, adjust the screen size of this JSFiddle: here and see how it reacts to different screen sizes. Running on the above snippet only allows one column of items as on load, the container size is pretty limited. I have adjusted so that an item has item--inner and allows the item to always be 30% of the container size. If you don't want it to always be 30% of its container size, simply correct the CSS styling of item. For easier code adjustment, see JSFiddle here: here.
Note: Change item width back to 400px if you want to see the two-columned version and the vertically stacked version. Make sure to adjust the screen size before the page has loaded.
Well, this is my solution...
it create a new div on each column, and dispach all item inside. on window resize it create more or less columns, to respect responsive case
here is my code:
(function () {
const Container = document.querySelector('.container')
, All_item = [...document.querySelectorAll('.item')]
;
if ( All_item.length===0 ) return
const items_W = All_item[0].offsetWidth // get item width
, divGpr = document.createElement('div') // base col element
, flexCol = [] // array of ihm cols
;
divGpr.style.width = items_W+'px'; // perfect size
function SetNewCols() //
{
let nbCol = Math.max(1, Math.floor( Container.offsetWidth / items_W ) )
, len = flexCol.length;
if (nbCol===len) return // col unchanged
for(let k=0;k<nbCol;k++) // create new Col
{ flexCol.push( Container.appendChild(divGpr.cloneNode(true))) }
let k=0;
for(item of All_item)
{
flexCol[len+k].appendChild(item) // dispach item in new col
k = ++k % nbCol // future col of
}
for(i=0;i<len;i++)
{ Container.removeChild( flexCol.shift() ) } // remove old empty cols
if(k>0)
{ flexCol[( flexCol.length -1)].appendChild(item) }
}
SetNewCols(); // first Attempt
window.addEventListener('resize', SetNewCols);
document.querySelectorAll(".container button").forEach(Koll=>
{
Koll.onclick=e=>
{
let content = Koll.nextElementSibling;
content.style.maxHeight = Koll.classList.toggle("active")
? content.scrollHeight + "px"
: 0;
}
})
})();
.container {
display:flex;
justify-content:space-between;
flex-wrap:wrap;
}
.container .item {
width:400px;
}
.container .item > button {
background-color: #777;
color: white;
cursor: pointer;
padding: 18px;
width: 100%;
border: none;
text-align: left;
outline: none;
font-size: 15px;
}
.container .item .content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
}
<div class="container">
<div class="item">
<button>Open Section 1</button>
<div class="content">
<p>Section 1 details</p>
</div>
</div>
<div class="item">
<button>Open Section 2</button>
<div class="content">
<p>Section 2 details</p>
</div>
</div>
<div class="item">
<button>Open Section 3</button>
<div class="content">
<p>Section 3 details</p>
</div>
</div>
<div class="item">
<button>Open Section 4</button>
<div class="content">
<p>Section 4 details</p>
</div>
</div>
<div class="item">
<button>Open Section 5</button>
<div class="content">
<p>Section 5 details</p>
</div>
</div>
<div class="item">
<button>Open Section 6</button>
<div class="content">
<p>Section 6 details</p>
</div>
</div>
</div>
What you could do is to create a column-like structure, grouping all column elements into a div with class item:
<div class="container">
<div class="item">
<div class="item">
<button class="collapsible">Open Section 1</button>
<div class="content">
<p>Section 1 details</p>
</div>
</div>
<div class="item">
<button class="collapsible">Open Section 4</button>
<div class="content">
<p>Section 4 details</p>
</div>
</div>
</div>
...
</div>
I leave you the modified CodePen! Hope this is what you wanted to do!
In my approach, I'll use $(document).on('click', '.collapsible', function () {});
let $collBtn = '.item > .collapsible'; // all collapse
$(document).on('click', '.collapsible', function (e) {
e.preventDefault();
$($collBtn).removeClass('active');
$(this).addClass('active');
});
Once .collabsible has a class active it will expand the element like a togleClass(). Base on my experience when I have a multiple buttons and by using $(document).on('click', '.item', function () {}) It's equivalent of finding a class && buttons in the same elements. Hopefully, this can help. :)
#Vijay:- This is not an issue but a normal behaviour. When you click on any div, the other divs in the row are not expanding but the content is taking it's place and pushing other elements below that row to provide space for content to appear. You can only go with Position relative and absolute logic with the same HTML structure.
*{
box-sizing:border-box;
}
.item{
position:relative;
}
.content {
padding: 0 18px;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
background-color: #f1f1f1;
position:absolute;
top:100%;
left:0;
right:0;
}

How do i make a button click event happen when i click on another function?

Okay the question sound strange at first but its not that bad. I want a vertical menu with multiple submenus. Ive tried with css and checkboxes first because i want to click on the menu to display submenu and not hover. Didn't work out the right solution. Now I found something on w3 but it still not work as i intented it to work. Submenu opens on click but if I click on another button with same script (but different class and id) they both stay opened. (If you open the bottom first the other makes it close but it happens only with the bottom one..)
https://jsfiddle.net/8vsbe5w3/
HTML
<div class="dropdown">
<button onclick="myFunction()" class="dropbtn">Dropdown</button>
<div id="myDropdown" class="dropdown-content">
Home
About
Contact
</div>
</div>
<div class="dropdown2">
<button onclick="myFunction2()" class="dropbtn2">Dropdown</button>
<div id="myDropdown2" class="dropdown-content2">
Home
About
Contact
</div>
</div>
<script>
/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
function myFunction() {
document.getElementById("myDropdown").classList.toggle("show");
}
// Close the dropdown if the user clicks outside of it
window.onclick = function(event) {
if (!event.target.matches('.dropbtn')) {
var dropdowns = document.getElementsByClassName("dropdown-content");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
}
/* ---------------------------------------------------------- */
function myFunction2() {
document.getElementById("myDropdown2").classList.toggle("show");
}
window.onclick = function(event) {
if (!event.target.matches('.dropbtn2')) {
var dropdowns = document.getElementsByClassName("dropdown-content2");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
}
</script>
CSS
.dropdown-content, .dropdown-content2{
display: none;}
.show {display: block;}
My ideal menu would let you open only one submenu and allow to close it if you click on it again (uncheck the input).
You are setting the window.onclick property twice, so you are overwriting the first one. That's why when you open the first menu and then the second, the first one stays open - because the first window.onclick doesn't exist anymore.
My advise is to refactor the code, so that you have only one window.onclick handler that works universally for both menus.
Also it's a bad idea to overwrite this even handler, better use document.addEventListener().
Avoid on-event attribute
<button onclick="namedFunction()" ></button>
use on-event property or .addEventListener()
document.querySelector("button").onclick = namedFunction
document.querySelector("button").addEventListener("click", namedFunction)
I there's multiple targets that are identical (HTML layout, CSS styles, JavaScript behavior, etc.),
use class avoid id.
find an ancestor element that all targets have in common. Although Window and Document can be considered as a common ancestor, try finding the closest to target. Referencing Window or Document is commonly used when listening for key events.
Once an ancestor is found, register it to the event. The ancestor will not only listen for registered events for itself -- it will listen for events triggered on any of its descendant elements as well. No need to register each element to one event and the amount of descendants that's covered is virtually limitless.
To determine and isolate the element that was actually clicked, changed, hovered over, etc -- use the Event property event.target.
The programming pattern described is the main part of Event Delegation.
const main = document.querySelector('main');
main.onclick = ddAccordion;
function ddAccordion(e) {
const clicked = e.target;
const triggers = document.querySelectorAll('.trigger');
if (clicked.matches('.trigger')) {
if (clicked.matches('.show')) {
for (let t of triggers) {
t.classList.remove('show');
}
} else {
for (let t of triggers) {
t.classList.remove('show');
}
clicked.classList.add('show');
}
}
return false;
}
body {
overflow-y: scroll;
overflow-x: hidden;
}
.trigger {
background-color: #3498DB;
color: white;
padding: 16px;
font-size: 16px;
border: none;
cursor: pointer;
width: 100%;
}
.trigger:hover,
.trigger:focus {
background-color: #2980B9;
outline: 0;
}
.dropdown {
width: 100%;
}
.content {
background-color: #f1f1f1;
max-height: 0px;
overflow: hidden;
transition: max-height 0.75s ease 0s;
}
.show+.content {
max-height: 500px;
height: auto;
overflow: auto;
transition: max-height 1.15s ease 0s;
}
.content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
.content a:hover {
background-color: #ddd;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style></style>
</head>
<body>
<main>
<section class="dropdown">
<button class="trigger">Dropdown 1</button>
<address class="content">
Home
About
Contact
</address>
</section>
<section class="dropdown">
<button class="trigger">Dropdown 2</button>
<address class="content">
Home
About
Contact
</address>
</section>
<section class="dropdown">
<button class="trigger">Dropdown 3</button>
<address class="content">
Home
About
Contact
</address>
</section>
</main>
<script></script>
</body>
</html>

How To Change Div Style By Div ID

Edit: Added JS below, per user comments. Here is a great example of what I am trying to accomplish:
On a single blog page, I would like to have two buttons, “Audio” (div id=“audio”) and “Video” (div id=“video”).
By default Video would be enabled and display a video player within a div. If the user clicks Audio the browser changes the end of the URL to #audio which triggers a div style change. That would cause the Video player to be hidden and the Audio player to become visible. Likewise, if the user clicks Video again the end of the URL changes to #video which triggers a div style change and the Audio player to become hidden and the Video player to become visible.
Quite a bit of research has led me to being able to toggle a single div on and off, but I cannot figure out how to accomplish what I laid out above.
Here are my div statements.
When URL ends in #video
<div id="video" style="position: static; visibility: visible; overflow: visible; display: block;">MY VIDEO PLAYER</div>
<div id="audio" style="position: absolute; visibility: hidden; overflow: hidden; display: block;">MY AUDIO PLAYER</div>
When URL ends in #audio
<div id="video" style="position: absolute; visibility: hidden; overflow: hidden; display: block;">MY VIDEO PLAYER</div>
<div id="audio" style="position: static; visibility: visible; overflow: visible; display: block;">MY AUDIO PLAYER</div>
I am not even going to bother posting my javascript for this. It only works to toggle a single div on and off. I should mention, I am not a coder, but have come along ways on my journey and this site has always been a goto for me. First time question.
Thanks so much in advance for any assistance this outstanding site can provide.
Edit: Here is the js I used which only allows the toggle of a single div
<script>
function myFunction() {
var x = document.getElementById("video");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "none";
}
</script>
My first thought, though I’m unable to check currently, would be to use the following stylesheet rules:
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.wrapper {
width: 80vw;
margin: 1em auto;
border: 1px solid #000;
}
#audio,
#video {
/* hide both elements by default: */
visibility: hidden;
}
#audio:target,
#video:target {
/* show the element whose id is the
document's hash (‘at the end of
the URL’) */
visibility: visible;
}
<nav>
<ul>
<li>Video</li>
<li>Audio</li>
</ul>
</nav>
<div class="wrapper">
<div id="video">MY VIDEO PLAYER</div>
<div id="audio">MY AUDIO PLAYER</div>
</div>
JS Fiddle demo.
This does, of course, rely on CSS rather than JavaScript, though; so this may not be the answer you want.
Following the comment left by the OP:
Unfortunately, I need video to be visible by default. Then if the user clicks audio, video would become hidden.
The following code works:
/* aesthetics, irrelevant to the actual demo */
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.wrapper {
border: 1px solid #000;
width: 80vw;
margin: 1em auto;
}
.wrapper>div {
flex: 1 0 auto;
height: 5em;
}
/* using 'display: flex' to allow the use of the
'order' property on the chilren of this element: */
.wrapper {
display: flex;
}
/* hiding the child elements of the wrapper by default: */
.wrapper>div {
visibility: hidden;
}
/* selecting the child of the .wrapper element with
the class of 'defaultOnLoad', placing it first
in the visual order of its parent with the
'order: -1' property; and making it visible on
page-load: */
.wrapper>.defaultOnLoad {
order: -1;
visibility: visible;
}
/* selecting the '.defaultOnLoad' element that
is a general (later) sibling of an element
that is the ':target' (whose id appears following
the '#' in the URL), both of which are children of
'.wrapper' and hiding that element: */
.wrapper> :target~.defaultOnLoad {
visibility: hidden;
}
/* finding the child of the '.wrapper' element that
is the ':target' of the document and making it
visible: */
.wrapper>:target {
visibility: visible;
}
<nav>
<ul>
<li>Video</li>
<li>Audio</li>
</ul>
</nav>
<div class="wrapper">
<!-- in the CSS we'll be selecting/styling the '.defaultOnLoad' element based
on the state of another sibling being the ':target'; as CSS cannot select
previous-siblings I've moved the '#video' to be the last child of the
'.wrapper', but used CSS to maintain the visual order: -->
<div id="audio">MY AUDIO PLAYER</div>
<div id="video" class="defaultOnLoad">MY VIDEO PLAYER</div>
</div>
JS Fiddle demo.
As a demonstration for why I would recommend CSS over JavaScript, when CSS is able to perform the task, see the following, in which multiple other media options are added and no CSS changes are required:
/* aesthetics, irrelevant to the actual demo */
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.wrapper {
border: 1px solid #000;
width: 80vw;
margin: 1em auto;
}
.wrapper>div {
flex: 1 0 auto;
height: 5em;
}
/* using 'display: flex' to allow the use of the
'order' property on the chilren of this element: */
.wrapper {
display: flex;
}
/* hiding the child elements of the wrapper by default: */
.wrapper>div {
visibility: hidden;
}
/* selecting the child of the .wrapper element with
the class of 'defaultOnLoad', placing it first
in the visual order of its parent with the
'order: -1' property; and making it visible on
page-load: */
.wrapper>.defaultOnLoad {
order: -1;
visibility: visible;
}
/* selecting the '.defaultOnLoad' element that
is a general (later) sibling of an element
that is the ':target' (whose id appears following
the '#' in the URL), both of which are children of
'.wrapper' and hiding that element: */
.wrapper> :target~.defaultOnLoad {
visibility: hidden;
}
/* finding the child of the '.wrapper' element that
is the ':target' of the document and making it
visible: */
.wrapper>:target {
visibility: visible;
}
<nav>
<ul>
<li>Video</li>
<li>Audio</li>
<li>Print</li>
<li>Braille</li>
</ul>
</nav>
<div class="wrapper">
<!-- in the CSS we'll be selecting/styling the '.defaultOnLoad' element based
on the state of another sibling being the ':target'; as CSS cannot select
previous-siblings I've moved the '#video' to be the last child of the
'.wrapper', but used CSS to maintain the visual order: -->
<div id="audio">MY AUDIO PLAYER</div>
<div id="print">The 'print' option</div>
<div id="braille">The 'braille' option</div>
<div id="video" class="defaultOnLoad">MY VIDEO PLAYER</div>
</div>
JS Fiddle demo.
With regards to the comment, below, from the OP:
One issue I am running into is the ID obviously causes the browser to jump. Is there some syntax I am not aware of that would keep the browser in place? …I need each of those DIV elements (audio/video) to replace one another without jumping or moving one below the other when a user clicks either audio or video.
There doesn't seem to be a CSS means of preventing the page scrolling to the position of the targeted elements – the #video and #audio – this may prevent the use of <a> elements, unfortunately; there is an alternative, using <label> and <input> elements, but this has the complication of adding extra HTML elements for the functionality:
/* aesthetics, irrelevant to the actual demo */
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* giving an arbitrarily large margin-bottom to
demonstrate that there is no default scrolling
on clicking the <label>: */
nav {
margin-bottom: 800px;
}
/* a <label> element doesn't have default styling to imply
its interactive nature, so here we style the cursor to
depict that it can be clicked: */
nav label {
cursor: pointer;
}
.wrapper {
border: 1px solid #000;
width: 80vw;
margin: 1em auto;
}
/* hiding the <div> children contained within the .wrapper
element: */
.wrapper>div {
height: 5em;
visibility: hidden;
}
/* selecting the <div> elements that are the immediate sibling
of an <input> whose 'type' attribute is equal to 'radio' and
which matches the ':checked' pseudo-class, and setting their
visibility to 'visible': */
.wrapper input[type=radio]:checked + div {
visibility: visible;
}
/* hiding the <input> elements: */
.wrapper input[type=radio] {
display: none;
}
<nav>
<ul>
<!-- using <label> elements instead of <a>; using the 'for'
(HTMLLabelElement.HTMLFor property) to associate the
<label> with the relevant <input> (the 'for' attribute
must be equal to the 'id' attribute/property of the
<input>: -->
<li><label for="video">Video</label></li>
<li><label for="audio">Audio</label></li>
</ul>
</nav>
<div class="wrapper">
<!-- we place the <input> as the previous sibling of the relevant
<div> element (although this is a convenience in order to
simplify the selector): -->
<input type="radio" name="mediaChoice" id="video" checked="checked" />
<div id="video">MY VIDEO PLAYER</div>
<input type="radio" name="mediaChoice" id="audio" />
<div id="audio">MY AUDIO PLAYER</div>
</div>
JS Fiddle demo.
A further revision of the above, this time to use JavaScript:
let nav = document.querySelector('nav'),
mediaContainer = document.querySelector('div.wrapper'),
// because one option needs to be shown on page-load, and
// the user's ability to choose the media is determined
// via the click event, here we have to create a click
// event (a new MouseEvent), which can bubble through
// the DOM to be detected by an ancestor:
clickEvent = new MouseEvent('click', {
'bubbles': true
});
// named function to handle events; the EventObject
// ('event') is passed automagically from the
// EventTarget.addEventListener() method:
function mediaToggle(event) {
// preventing the default behaviour of the
// HTMLAnchorElement (which prevents the link
// being 'followed' and prevents page-jumping):
event.preventDefault();
// here we retrieve the hash (the '#identifier'
// fragment) of the clicked (event.target) <a>
// element:
let selector = event.target.hash;
// here we retrieve the NodeList of all '.media'
// elements in the document; and use
// NodeList.forEach() to iterate over that collection:
document.querySelectorAll('.media').forEach(
// we're using an Arrow function here; 'elem' is a
// reference to the current element-node of the NodeList
// over which we're iterating:
(elem) => {
// here we perform this function for all nodes;
// using the Element.classList API to toggle the
// 'active' class; the switch which follows determines
// whether the class-name is added, retained, removed or
// or left off. The 'switch' is a condition which evaluates
// to a true/false (or truthy/falsey) result:
elem.classList.toggle('active',
// here we use Element.matches(CSSSelector) to test whether
// the current element-node matches the supplied CSS selector;
// if it does the class-name is added (or retained), if not
// the class-name is removed (or not-added):
elem.matches(selector));
});
}
// adding the mediaToggle() function - note the deliberate lack of
// parentheses - as the event-handler for the 'click' event:
nav.addEventListener('click', mediaToggle);
// using Element.querySelector() to find the first/only element
// that matches the supplied CSS selector
nav.querySelector('.defaultOnLoad')
// firing the created MouseEvent on that element via
// the EventTarget.dispatchEvent() method:
.dispatchEvent(clickEvent);
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.wrapper {
border: 1px solid #000;
width: 80vw;
margin: 1em auto;
}
.media {
visibility: hidden;
}
.media.active {
visibility: visible;
}
<nav>
<ul>
<!-- using the 'defaultOnLoad' class-name to identify which media
should be the default on page-load: -->
<li>Video</li>
<li>Audio</li>
<li>Print</li>
<li>Braille</li>
</ul>
</nav>
<div class="wrapper">
<div id="video" class="media">Video</div>
<div id="audio" class="media">Audio</div>
<div id="print" class="media">Print</div>
<div id="braille" class="media">Braille</div>
</div>
JS Fiddle demo.
References:
CSS:
Adjacent-sibling (+) combinator.
:checked.
:target.
JavaScript:
Arrow functions.
document.querySelector().
document.querySelectorAll().
Element.classList.
Element.matches().
Event.preventDefault().
EventTarget.addEventListener().
EventTarget.dispatchEvent().
MouseEvent() constructor.
NodeList.prototype.forEach().
Here is a plain js way to do it. I am making a few assumptions here (about your markup).
function doMagic() {
let urlType = window.location.href,
audio = document.getElementById('audio'),
video = document.getElementById('video');
if (urlType.split('#')[1] === 'audio'{ //turn it into an array of two, split at the hash
audio.style.display = 'block';
video.style.display = 'none';
}
if (urlType.split('#')[1] === 'video'{
audio.style.display = 'none';
video.style.display = 'block';
}
}
// assuming you have a button element for this
let videoBtn = document.getElementById('vidBtn'),
audBtn = document.getElementById('audBtn');
vidBtn.addEventListener('click', function() { doMagic(); });
audBtn.addEventListener('click', function() { doMagic(); });
Both event listeners calls the same function. If your buttons have the same class then you can just simplify this to one event listener.
// assuming your buttons have the class name "myBtn"
let theBtn = document.getElementByClass('myBtn');
theBtn.addEventListener('click', function() { doMagic(); });
The answer that I have found was not done with plain javascript but with a framework called jquery. You could learn more about jquery from their website, or from w3schools.
Below is the code.
$("#audio").click(function(){
$("#audio").toggleClass("active");
$("#video").toggleClass("active");
});
$("#video").click(function(){
$("#video").toggleClass("active");
$("#audio").toggleClass("active");
});
.active{
background-color: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html>
<head>
</head>
<body>
<div id="video" class = "active" style="position: static; visibility: visible; overflow: visible; display: block;">MY VIDEO PLAYER</div>
<br>
<div id="audio" style="position: static; overflow: hidden; display: block;">MY AUDIO PLAYER</div>
First, we check when the element with id audio is clicked then we toggle between the class of active and inactive for the video id and audio id.
Similarly, when we check for the id video to be clicked we toggle the active class between the video id and audio id.
I hope this helped :)

Trouble overlaying one div with another with JavaScript & CSS

Here is my goal:
When the user hovers a div of class "item" another div of class "menu" should appear overlaying the "item" div.
The position of the "menu" div should be relative to the "item" div.
When the user unhovers the item "div" the menu div should disappear.
When the user mouses over the "menu" div the "menu" div the "menu" div should not disappear so that user can click a button in it.
I am looking for a JavaScript and CSS solution. If you can help but you can only post a JQuery solution I will still appreciate it but I will have to translate it to straight JavaScript.
So far I have tried:
To make the "hover" div an absolutely positioned child of the document.body. This works for positioning, but hovering the "hover" div unhovers the "item" div and I don't know how to figure out that the new hovered div is the "hover" div.
To make the "hover" div a absolutely or fixed positioned child of the "item" div. This places the "hover" div underneath the "item" div and style.top seems to have no effect on the "hover" div".
To make the "hover" div a relatively positioned child of the "item" div. This places the "hover" div within the "item" div and increases the size of the "hover" div, which I don't want.
Thank you for your help with this!
Here is a JSFiddle that is a starting point for a solution https://jsfiddle.net/ypn5f1ng/
HTML
<div id=content>
content
<div class=item>item 1</div>
<div class=item>item 2</div>
more content
</div>
CSS
body { background:green; }
#content { z-index:100; width:500px; position:absolute; left:0px; right:0px; margin-left:auto; margin-right:auto; background:white; margin-top:10px; background:lightblue; padding:5px; }
div.item { background:pink; margin:5px}
div.hover { background:yellow; height:15px; width:100px; z-index:101; position:fixed }
JavaScript
function getElem(event) {
if (!event) {
event = window.event;
}
var elem = null;
if (event.target) {
elem = event.target;
} else if (event.srcElement) {
elem = event.srcElement;
}
if (elem && elem.nodeType == 3) {
elem = elem.parentNode;
}
return elem;
}
var hoverDiv = null;
function onItemMouseOver(event) {
var elem = getElem(event);
if (!hoverDiv) {
hoverDiv = document.createElement('DIV');
hoverDiv.className = "hover";
document.body.appendChild(hoverDiv);
//elem.appendChild(hoverDiv);
hoverDiv.style.right=100;
hoverDiv.style.top=-100;
}
}
function onItemMouseOut(event) {
if(hoverDiv) {
hoverDiv.parentNode.removeChild(hoverDiv);
hoverDiv = null;
}
}
var items = document.getElementsByClassName("item");
for(var i = 0; i < items.length; ++i) {
var item = items[i];
item.onmouseover = onItemMouseOver;
item.onmouseout = onItemMouseOut;
}
fiddle
HTML
<div class='a'>
<div class="b">
asfdwa
</div>
</div>
CSS
.a {
position: relative;
width: 300px;
height: 300px;
background: lightgray;
}
.b {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 80px;
background: pink;
opacity: 0;
transition: .2s opacity ease-in-out;
}
.b a {
display: block;
margin: 1rem;
}
.a:hover .b {
opacity: 1;
}
The best approach is to use CSS only if possible (no JS).
In this situation, I would recommend to put the div you would like to display on hover into the div that is the trigger.
<div class="parent">
<div class="child">
...
</div>
...
</div>
Than the CSS would look like this:
div.child {
display: none;
}
div.parent:hover div.child {
display: block;
}
With this technique you can position the child even to get outside the parent and it will not disappear if you get out of the parent if you are still on the child since the child is technically (not visually) in the parent. You just need to make sure that the parent at least touches the displayed child since the child will disappear if you travel over the gap between them with your cursor (the cursor won't be on the parent nor on the child)

Categories