Targeting an element backwards in DOM? [duplicate] - javascript

This question already has answers here:
Is there a "previous sibling" selector?
(30 answers)
Closed 1 year ago.
I'll keep it short:
I'm kinda new to JS and CSS. So I essentially I'm trying to change the background of DIV1 whenever someone hovers on DIV2. This is what I have right now:
#DIV2:hover ~ #DIV1 {
background-color: black;
transition: all ease-in-out 0.5s;
}
div {
width: 100px;
height: 50px;
}
#DIV2 {
background-color: red;
}
<div id="DIV1"></div>
<div id="DIV2"></div>
The issue is this works when DIV2 comes before DIV1. I want it to work when DIV 1 comes before DIV2 because that's how my site is to be structured.
Now I understand that I can't really target backwards in DOM (at least that's how I understand css works.) So is there any way I can make this work? Would appreciate any response/feedback. Thanks!

Here is a little CSS-only trick by using ability set order of children in flex/grid container:
.container
{
display: grid;
}
.div1{order: 1}
.div2{order: 2}
.div3{order: 3}
.div4{order: 4}
.div5{order: 5}
.container > div
{
border: 1px solid black;
background-color: lightgreen;
}
.div5:hover
{
background-color: green;
}
.div1:hover
{
background-color: red;
}
.div5:hover ~ .div4,
.div4:hover ~ .div3,
.div3:hover ~ .div2,
.div2:hover ~ .div1
{
background-color: pink;
}
<div class="container">
<div class="div5">DIV5</div>
<div class="div4">DIV4</div>
<div class="div3">DIV3</div>
<div class="div2">DIV2</div>
<div class="div1">DIV1</div>
</div>

You can just set the events with Javascript with the callback mouseover for hover and mouseout for the action when mouse not hovering.
document.querySelector('#DIV2').addEventListener('mouseover', function() {
var d1 = document.querySelector('#DIV1');
d1.style.backgroundColor = "black";
});
document.querySelector('#DIV2').addEventListener('mouseout', function() {
var d1 = document.querySelector('#DIV1');
d1.style.backgroundColor = "white";
});
div {
text-align: center;
width: 500px;
height: 50px;
}
<div id="DIV1">1</div>
<div id="DIV2">2</div>

If you need to do this on multiple pairs of divs (using class of course) that do not have a common parent element - without changing your HTML
document.querySelectorAll('.DIV1').forEach(d1 => {
const d2 = d1.parentElement.querySelector('.DIV1 ~ .DIV2');
if (d2) {
d2.addEventListener('mouseover', () => d1.classList.add('phover'));
d2.addEventListener('mouseout', () => d1.classList.remove('phover'));
}
});
.phover {
background-color: black;
color: white;
}
.DIV2 {
cursor: default;
}
<div>
<div class="DIV1">ONE</div>
<div class="DIV2">TWO</div>
</div>
<div>
<div class="DIV1">ONE</div>
<div class="DIV2">TWO</div>
</div>
<div>
<div class="DIV1">ONE</div>
<div class="DIV2">TWO</div>
</div>
<div>
If you want to do it on multiple pairs of divs with the same parent element - without changing your HTML
document.querySelectorAll('.DIV1').forEach(d1 => {
for (let d2 = d1.nextElementSibling; d2; d2 = d2.nextElementSibling) {
if (d2.classList.contains('DIV2')) {
d2.addEventListener('mouseover', () => d1.classList.add('phover'));
d2.addEventListener('mouseout', () => d1.classList.remove('phover'));
break;
}
}
});
.phover {
background-color: black;
color:white;
}
.DIV2 {
cursor:default;
}
<div class="DIV1">ONE</div>
<div class="DIV2">TWO</div>
<div class="DIV1">ONE</div>
<div class="DIV2">TWO</div>
<div class="DIV1">ONE</div>
<div class="DIV2">TWO</div>
Multiple pairs of DIVS, change the HTML
document.querySelectorAll('.DIV2').forEach(d2 => {
const d1 = document.querySelector(d2.dataset.for);
if (d1) {
d2.addEventListener('mouseover', () => d1.classList.add('phover'));
d2.addEventListener('mouseout', () => d1.classList.remove('phover'));
}
});
.phover {
background-color: black;
color: white;
}
.DIV2 {
cursor: default;
}
<div id="d1" class="DIV1">ONE</div>
<div class="DIV2" data-for="#d1">TWO</div>
<div id="d2" class="DIV1">ONE</div>
<div class="DIV2" data-for="#d2">TWO</div>
<div id="d3" class="DIV1">ONE</div>
<div class="DIV2" data-for="#d3">TWO</div>

For the simple case where DIV1 and DIV2 are within the same container you can set the color on hover of the container, putting back DIV1's color if DIV1 was the part that was being hovered.
Of course, if there are other elements DIV3 etc you'd have to ensure their colors were set back on hover in the same way so it's a bit of a messy solution if you have a complex set-up.
.container {
display: inline-block;
}
#DIV1,
#DIV2 {
width: 40vmin;
height: 40vmin;
}
#DIV2 {
background-color: cyan;
}
.container:hover #DIV1 {
background-color: black;
transition: all ease-in-out 0.5s;
}
.container #DIV1,
.container #DIV1:hover {
background-color: magenta;
}
<div class="container">
<div id="DIV1">DIV1</div>
<div id="DIV2">DIV2</div>
</div>

Related

Simple javascript click to add class not working

I am trying to add / remove a class on an element that is clicked liek this
function myFunction() {
this.classList.add("myclass");
}
#first {
height: 100px;
width: 100px;
background: yellow;
color: black;
}
.myclass {
background: red;
color: white;
}
<div id="first" onclick="myFunction(this)">
Click
<div class="second">
</div>
<div class="third">
</div>
</div>
Why is this not working?
You need to pass reference this into function too like:
function myFunction(el) {
el.classList.add("myclass");
}
#first {
height: 100px;
width: 100px;
background: yellow;
color: black;
}
.myclass {
background: red!important;
color: white!important;
}
<div id="first" onclick="myFunction(this)">
Click
<div class="second">
</div>
<div class="third">
</div>
</div>
PS. add !important into css
Pass the this to the defined function too and check the existence of the class. Try this.
function myFunction(el) {
if(!el.classList.contains("myclass")) {
el.classList.add("myclass");
console.log("added");
} else {
el.classList.remove("myclass");
console.log("removed");
}
}
#first {
height: 100px;
width: 100px;
background: yellow;
color: black;
}
.myclass {
background: red;
color: white;
}
<div id="first" onclick="myFunction(this)">
Click
<div class="second">
</div>
<div class="third">
</div>
</div>
It doesn't work because this for inline handlers works differently. You can use .call, and that works... but that's still not good.
function myFunction() {
this.classList.add("myclass");
}
#first {
height: 100px;
width: 100px;
background: yellow;
color: black;
}
.myclass {
background: red !important;
color: white;
}
<div id="first" onclick="myFunction.call(this)">
Click
<div class="second">
</div>
<div class="third">
</div>
</div>
You should avoid inline script altogether and also avoid id selectors in CSS.
Change your id selector to a class selector and change your inline handler to an event listener.
Also, stray strings like "content" are a real pain as your project grows in size. Wrap them in a <span>
const myButton = document.querySelector(".first");
myButton.addEventListener("click", ({
target
}) => target.classList.add("myclass"))
.first {
height: 100px;
width: 100px;
background: yellow;
color: black;
}
.myclass {
background: red;
color: white;
}
<div class="first">
<span>Click</span>
<div class="second"></div>
<div class="third"></div>
</div>
You are facing 2 issues :
you pass this as a parameter to the onclick event, but don't get it in the function definition (), so you can fix it like below ;
id selector is more specific than class selector, so it always take precedence. You can use a class instead of an id for first div, and then your css rule works :)
function myFunction(el) {
el.classList.add("myclass");
}
.first {
height: 100px;
width: 100px;
background: yellow;
color: black;
}
.myclass {
background-color: red;
color: white;
}
<div class="first" onclick="myFunction(this);">
Click
<div class="second">
</div>
<div class="third">
</div>
</div>

Elements traversing across parent, z-index

For any element that is clicked in the stack, the target element should be assigned a higher z-index than all of those that come before it in the DOM. Then it should translateY() pixels up to the first element.
The desired result is for the target element to sit on top of the first element in the parent. Yet there is a bug where it makes it appear that some elements get a lower z-index . If this could be refactored better as well how could it be improved?
const divs = document.querySelectorAll("div");
divs.forEach((div) => {
div.addEventListener("click", (event) => {
let i = 0;
let previous_sibling = event.target.previousElementSibling;
while (previous_sibling != null) {
i++;
previous_sibling = previous_sibling.previousElementSibling;
}
event.target.style.cssText = `
position: relative;
z-index: ${i + 1};
transform: translateY(-${i * 45}px);
`;
})
})
.box {
width: 300px;
height: 45px;
/* border: 1px solid; */
margin: auto;
}
section {
margin-top: 50px;
}
div {
transition: transform .75s linear;
}
#box1 {
background: red;
}
#box2 {
background: blue;
}
#box3 {
background: green;
}
#box4 {
background: orange;
}
#box5 {
background: purple;
}
#box6 {
background: teal;
}
<section>
<div class="box" id="box1">1</div>
<div class="box" id="box2">2</div>
<div class="box" id="box3">3</div>
<div class="box" id="box4">4</div>
<div class="box" id="box5">5</div>
<div class="box" id="box6">6</div>
<div class="box" id="box1">7</div>
<div class="box" id="box2">8</div>
<div class="box" id="box3">9</div>
<div class="box" id="box4">10</div>
<div class="box" id="box5">11</div>
<div class="box" id="box6">12</div>
</section>
Currently the z-index gets calculated by the elements DOM position due to the use of previousElementSibling and the simple counter. Since translateY does not change the position of the elements in the DOM they can never be out of order. For example numer #3 can never be on top of #4 because it never is in the DOM. To change this behaviour you could simply store the highest z-index (here in the dataset of the section) and reuse / add to it.
const divs = document.querySelectorAll("div");
divs.forEach((div) => {
div.addEventListener("click", (event) => {
//REM: Get the highest z-index
let tCurrentZ = ~~div.parentNode.dataset.zindex;
let i = 0;
let previous_sibling = event.target.previousElementSibling;
while (previous_sibling != null) {
i++;
previous_sibling = previous_sibling.previousElementSibling;
}
event.target.style.cssText = `
position: relative;
z-index: ${tCurrentZ};
transform: translateY(-${i * 45}px);
`;
//REM: Increase the highest z-index
div.parentNode.dataset.zindex = tCurrentZ + 1
})
})
.box {
width: 300px;
height: 45px;
/* border: 1px solid; */
margin: auto;
}
section {
margin-top: 50px;
}
div {
transition: transform .75s linear;
}
#box1 {
background: red;
}
#box2 {
background: blue;
}
#box3 {
background: green;
}
#box4 {
background: orange;
}
#box5 {
background: purple;
}
#box6 {
background: teal;
}
<section>
<div class="box" id="box1">1</div>
<div class="box" id="box2">2</div>
<div class="box" id="box3">3</div>
<div class="box" id="box4">4</div>
<div class="box" id="box5">5</div>
<div class="box" id="box6">6</div>
<div class="box" id="box1">7</div>
<div class="box" id="box2">8</div>
<div class="box" id="box3">9</div>
<div class="box" id="box4">10</div>
<div class="box" id="box5">11</div>
<div class="box" id="box6">12</div>
</section>

CSS Expand / Contract Animation to Show/Hide Content

I am trying to create a box that can expand and collapse with a simple slide out animation. If you run the example below, the idea is that it starts with one red line and when you click the button it separates into two read lines and gently expands to reveal the content like pulling a draw out of a table.
I've tried both transform, animation, relative: positioning with top, and i'm unable to get the desired effect.
The containing box should expand in size
function expandContract() {
const el = document.getElementById("expand-contract")
el.classList.toggle('expanded')
el.classList.toggle('collapsed')
}
#container {
border: 1px solid black;
padding: 15px;
}
#top-section {
border-bottom: 1px solid red;
}
#expand-contract {
border-bottom: 1px solid red;
}
.expand-contract {
transform: translateY(-100%)
overflow: hidden;
}
#keyframes slide-in {
100% {
transform: translateY(0%)
}
}
.expanded {
background-color: green;
animation-name: slide-in;
animation-duration: 1s;
}
.collapsed {
background-color: red;
transform: translateY(-100%)
}
<div id="container">
<div id="top-section">
This is always displayed
</div>
<div id="expand-contract" class="expanded">
This section expands and contracts
<table>
<tr><td>test1</td></tr>
<tr><td>test2</td></tr>
<tr><td>test3</td></tr>
<tr><td>test4</td></tr>
</table>
</div>
<div id="bottom-section">
This section is always displayed
</div>
</div>
<button onclick="expandContract()">Expand/Contract</button>
You can achieve this using the CSS transition along with toggled styles. Initially you may think to transition the height (from 0 to initial so that it expands dynamically based on height) but unfortunately CSS transition doesn't properly handle this.
Instead, you can wrap it in a container of its own with overflow: hidden and then use a margin-top: -100% to hide it, and 0 to show it.
Here is your code with this modification:
function expandContract() {
const el = document.getElementById("expand-contract")
el.classList.toggle('expanded')
el.classList.toggle('collapsed')
}
#container {
border: 1px solid black;
padding: 15px;
}
#top-section {
border-bottom: 1px solid red;
}
#expand-container {
overflow: hidden;
}
#expand-contract {
border-bottom: 1px solid red;
margin-top: -100%;
transition: all 1s;
}
#expand-contract.expanded {
background-color: green;
margin-top: 0;
}
<div id="container">
<div id="top-section">
This is always displayed
</div>
<div id="expand-container">
<div id="expand-contract" class="expanded">
This section expands and contracts
<table>
<tr><td>test1</td></tr>
<tr><td>test2</td></tr>
<tr><td>test3</td></tr>
<tr><td>test4</td></tr>
</table>
</div>
</div>
<div id="bottom-section">
This section is always displayed
</div>
</div>
<button onclick="expandContract()">Expand/Contract</button>
hope to help you
HTML
<div class="container">
<div id="top-section">
This is always displayed
</div>
<div id="expand-container">
<div class="expanded" id="expand-contract">
<table>
<tr><td>test1</td></tr>
<tr><td>test2</td></tr>
<tr><td>test3</td></tr>
<tr><td>test4</td></tr>
</table>
</div>
</div>
</div>
<button class="header" onclick="expandContract()">Expand/Contract</button>
css
.container {
width:100%;
border:1px solid #d3d3d3;
}
.container div {
width:100%;
}
.header {
background-color:#d3d3d3;
padding: 2px;
cursor: pointer;
font-weight: bold;
}
.container .expanded {
display: none;
padding : 5px;
}
js
function expandContract() {
$header = $(".header");
$content = $("#expand-contract")
$content.slideToggle(500, function () {
$header.text(function () {
return $content.is(":visible") ? "Collapse" : "Expand";
});
});
};
see here enter code here

Sliding div off-screen

I’m having a little trouble with this template: basically, I’m trying to add functionality where if you click a box it will expand sliding the other ones off-screen, but instead sliding the div off-screen it’s disappearing completely.
Here is what I have so far: JSFiddle.
$(function() {
$(".box").click(function() {
var isopened = $(this).attr("isopen");
if (isopened == "true") {
$(this).css("position", "relative").css("width", $(this).attr("data-ow"));
$(this).attr("isopen", "false");
}
else {
$(this).attr("data-ow", $(this).css("width"));
$(this).css("position", "relative").css("width", "40%");
$(this).attr("isopen", "true");
}
});
});
* {
margin: 0;
padding: 0;
}
.container {
width: 100%;
max-width: 100%;
max-height: 600px;
overflow: hidden;
}
.box {
height: 600px;
display: block;
width: 13.33333333%;
border: 1px solid white;
background-color: black;
float: right;
position: relative;
}
.box:first-of-type {
width: 29.0%;
background-color: orange;
}
.box:last-of-type {
width: 29.0%;
background-color: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="container">
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
<div class="box"></div>
</div>
What I ultimately want is when one of the boxes is clicked it expands and instead of the entire div being hidden only the part which is off-screen is hidden:
I think you might like this flexbox solution as you can do what you want without usign any jQuery/JS. Pure CSS and HTML:
body {
background-color: black
}
#container {
display: flex;
width: 100%;
height: 50vh;
}
#container > div {
flex: 1;
min-width: 0;
transition:min-width 0.2s ease;
outline:0;
}
#container > div:focus {
min-width: 50vw;
}
<div id="container">
<div tabindex="0" style="background-color:blue"></div>
<div tabindex="0" style="background-color:orange"></div>
<div tabindex="0" style="background-color:green"></div>
<div tabindex="0" style="background-color:white"></div>
<div tabindex="0" style="background-color:blue"></div>
</div>
I used tabindex to give me the ability to use the :focus selector.

Resize divs related to parent divs with jQuery

So I have 4 divs. I want to change the size of the inner divs compared to parent divs.
I want to dynamically change the child div size related to parent's one.
Now I've added .top class, but I don't really know if its needed or if it will be useful.
Here is the fiddle I'm testing with
http://jsfiddle.net/y3597/171/
jQuery below
$(".top").each(function () {
$('.object').width($(".inner").parent().width());
});
CSS below:
.container1 { width: 200px; background: red; padding: 2px; }
.container2 { width: 225px; background: purple; padding: 2px; }
.container3 { width: 250px; background: blue; padding: 2px; }
.container4 { width: 275px; background: black; padding: 2px; }
/* top ? */
.inner { width: 150px; background: gray; }
.object { width: 100px; background: green; }
HTML below:
<div class="container1 top">
<div class="inner">
<div class="object">Text 1</div>
</div>
<div class="container2 top">
<div class="inner">
<div class="object">Text 2</div>
</div>
<div class="container3 top">
<div class="inner">
<div class="object">Text 3</div>
</div>
<div class="container4 top">
<div class="inner">
<div class="object">Text 4</div>
</div>
I think that you are trying to achieve this:
$(".top").each(function () {
$(this).find(".object").width($(this).width());
});
In your code jQuery will check for every element with .object class in DOM on each loop. When you use (this) you are refering to element that is currently "selected" in loop.
Better way to achive this is to set widths od children to 100%, so they will inherit the witdhs from parents.

Categories