Height 100% of aspect-ratio-trick parent - css

I have a parent/wrapper container where the heigth is dependent on the width, so it uses the useful aspect-ratio trick often used for iframes and videos; that is, setting i.e. height: 0, padding-top: 53.3% for 16:9.
My problem is now that I have a child inside that wrapper which needs to have height: 100%. But it doesn't take the padding into account, resulting in a heightless child with overflow.
This code snippet illustrates my problem:
let
box = document.querySelector('.aspect-ratio')
;
document.querySelector('[name="toggle"]').onclick = function(){
let
padding_trick = 'height-0'
;
if (box.classList.contains(padding_trick)) {
box.classList.remove(padding_trick);
}ย else {
box.classList.add(padding_trick);
}
};
* {
box-sizing: border-box;
}
.controller {
margin-bottom: 15px;
}
.wrapper {
position: relative;
width: 400px;
}
.aspect-ratio {
background-color: beige;
}
.aspect-ratio.height-0 {
height: 0;
padding-top: 53.3%;
}
.inner {
background-color: tomato;
height: 100%;
border: 1px solid #222;
}
<div class="root">
<div class="controller">
<button name="toggle">Toggle padding-trick</button>
</div>
<div class="wrapper">
<div class="aspect-ratio height-0">
<div class="inner">
<p>Bordered content height is not 1oo%.</p>
</div>
</div>
</div>
</div>
What I would want is that the text appears inside the box, and that the red background-color fills the entire height of the box.

What I would want is that the text appears inside the box, and that the red background-color fills the entire height of the box.
That sound like you want the inner box absolutely positioned over the padding-trick box. If that's the case, this should be possible by just adding in the position attributes.
let
box = document.querySelector('.aspect-ratio')
;
document.querySelector('[name="toggle"]').onclick = function(){
let
padding_trick = 'height-0'
;
if (box.classList.contains(padding_trick)) {
box.classList.remove(padding_trick);
}ย else {
box.classList.add(padding_trick);
}
};
* {
box-sizing: border-box;
}
.controller {
margin-bottom: 15px;
}
.wrapper {
position: relative;
width: 400px;
}
.aspect-ratio {
background-color: beige;
}
.aspect-ratio.height-0 {
height: 0;
padding-top: 53.3%;
position: relative;
}
.inner {
background-color: tomato;
height: 100%;
border: 1px solid #222;
}
.height-0 .inner {
position: absolute;
top: 0;
}
<div class="root">
<div class="controller">
<button name="toggle">Toggle padding-trick</button>
</div>
<div class="wrapper">
<div class="aspect-ratio height-0">
<div class="inner">
<p>Bordered content height is not 1oo%.</p>
</div>
</div>
</div>
</div>

Related

How to render subchild above parent overlay

I want a gray overlay above all children except for the selected one. Given the following structure:
<div class="parent">
<!-- I have this subparent which is absolute. I cannot remove it... -->
<div class="subParent1">
<div class="subParent2">
<!-- This child I want to be above the OVERLAY, aka not greyed out -->
<div class="child selected">child</div>
<div class="child">child</div>
<div class="child">child</div>
</div>
</div>
<!-- This component is underneat subParent in the tree structure -->
<div class="grayOverlay"></div>
</div>
Here's an exact fiddle. Maybe, I could use a pseudo-element instead?
PS: I updated the children to be a bit more nested to align with my actual code.
You can take the reference from below code. I have altered the CSS a bit. I have added z-index wherever required you can optimise that. Also, removed position: absolute; from subParent1 and added top: 0; left: 0; on the grayOverlay. You can optimise it or change it as per you preference.
.parent {
width: 300px;
height: 300px;
position: relative;
background-color: gray;
}
.grayOverlay {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: rgb(107 114 128 / 0.8);
z-index: 11000;
}
.subParent1 {
display: flex;
flex-direction: column;
width: 100%;
z-index: 12000;
}
.child {
color: black;
width: 50px;
height: 20px;
background-color: white;
margin: 10px;
z-index: 10000;
}
.childIWantOverOverlay {
background-color: red;
z-index: 12000;
}
<div class="parent">
<div class="subParent1">
<div class="child childIWantOverOverlay">child</div>
<div class="child">child</div>
<div class="child">child</div>
</div>
<!-- This component is underneat subParent in the tree structure -->
<div class="grayOverlay"></div>
</div>
UPDATE 2
Perhaps also consider use a pseudo-element for this, if it is acceptable in the actual use case.
This approach is more isolated, so it might be less likely to have conflict with other existing elements in the actual project.
Example with pseudo-element:
const btn = document.querySelector("button");
const divs = document.querySelectorAll("div.child");
let i = 0;
btn.addEventListener("click", () => {
divs[i].classList.toggle("selected");
if (i < 2) {
divs[i + 1].classList.toggle("selected")
i++;
return;
};
if (i >= 2) {
i = 0;
divs[i].classList.toggle("selected");
}
});
/* Can Change */
.parent {
width: 300px;
height: 300px;
position: relative;
}
/* ๐Ÿ‘‡ Add this */
.parent::after {
content: "";
position: absolute;
inset: 0;
background-color: rgb(107 114 128 / 0.5);
z-index: 50;
}
/* ๐Ÿ‘‡ Disabled for now
.grayOverlay {
position: absolute;
width: 100%;
height: 100%;
background-color: rgb(107 114 128 / 0.5);
z-index: 50;
}
*/
/* CANNOT CHANGE */
.subParent1 {
position: absolute;
display: flex;
flex-direction: column;
width: 100%;
}
/* Can Change */
.child {
color: black;
width: 50px;
height: 20px;
background-color: pink;
margin: 10px;
z-index: 25;
position: relative;
}
/* Can Change */
.selected {
background-color: red;
/* ๐Ÿ‘‡ Add z-index */
z-index: 100;
}
button {
margin-bottom: 1em;
padding: 6px;
}
<button>Toggle</button>
<div class="parent">
<div class="subParent1">
<div class="subParent2">
<div class="child selected">child</div>
<div class="child">child</div>
<div class="child">child</div>
</div>
</div>
<!-- This component is underneat subParent in the tree structure. I cannot move this into subParent1 -->
<!-- <div class="grayOverlay"></div> -->
</div>
Update: also added position: relative on child.
It seems that this can be achieved by removing the z-index on grayOverlay and subParent1 (the grayOverlay is still stacked on top due to natural placement), and add some z-index on selected.
Example:
const btn = document.querySelector("button");
const divs = document.querySelectorAll("div.child");
let i = 0;
btn.addEventListener("click", () => {
divs[i].classList.toggle("selected");
if (i < 2) {
divs[i + 1].classList.toggle("selected")
i++;
return;
};
if (i >= 2) {
i = 0;
divs[i].classList.toggle("selected");
}
});
/* Can Change */
.parent {
width: 300px;
height: 300px;
position: relative;
}
/* Can Change */
.grayOverlay {
position: absolute;
width: 100%;
height: 100%;
background-color: rgb(107 114 128 / 0.5);
/* Removed z-index */
}
/* CANNOT CHANGE */
.subParent1 {
position: absolute;
display: flex;
flex-direction: column;
width: 100%;
/* Removed z-index */
}
/* Can Change */
.child {
color: black;
width: 50px;
height: 20px;
background-color: pink;
margin: 10px;
/* ๐Ÿ‘‡ Add position */
position: relative;
}
/* Can Change */
.selected {
background-color: red;
/* ๐Ÿ‘‡ Add z-index */
z-index: 100;
}
button {
margin-bottom: 1em;
padding: 6px;
}
<button>Toggle</button>
<div class="parent">
<div class="subParent1">
<div class="subParent2">
<div class="child selected">child</div>
<div class="child">child</div>
<div class="child">child</div>
</div>
</div>
<!-- This component is underneat subParent in the tree structure. I cannot move this into subParent1 -->
<div class="grayOverlay"></div>
</div>

How to make an absolute positioned element end just before the other element while changing the width of the screen?

Let's assume we have a 'parent' block which consists of plenty of element including a button. By pressing this button the user should see a 'slide' element that will cover the parent block not the full width but only till the button inself(blocks 1 and 2 as you will see on the pen). So the slide should forever ends just before the button. Even when the width of the screen is changed. How could it be implemented?
See on Code Pen
<div class='parent'>
<div class='first'>1</div>
<div class='second'>2</div>
<div class='btn'>Button</div>
<div class='third'>3</div>
<div class='slide'>
Hello
</div>
</div>
.parent {
display: flex;
}
.first, .second, .third {
padding: 30px;
}
.first {
background-color: salmon;
flex-basis: 120px;
}
.second {
background-color: yellow;
flex-grow: 1
}
.third {
background-color: #56D7F7;
flex-grow: 1
}
.btn {
background-color: red;
cursor: pointer;
}
.slide {
position: absolute;
left: 0
}
You can achieve this with a bit of Javascript applying CSS styles. To open the slide, you scale it up by a calculated amount; to close, you just scale it back to its original size.
You might not want to scale the slide element since if there's any content in it, that will appear skewed a bit, but there's not too much work to change this solution to use translations instead.
I don't think there's a pure CSS solution since you have to calculate the position of the button, but of course I could be wrong!
let open = false;
slide.style.setProperty('transform-origin', 'left top');
slide.style.setProperty('transition', 'linear .4s all');
button.addEventListener('click', (event) => {
if (open) {
slide.style.setProperty('transform', `scaleX(1)`);
open = false;
} else {
const first = slide.offsetWidth;
const last = button.offsetLeft;
const scale = last / first;
slide.style.setProperty('transform', `scaleX(${scale})`);
open = true;
}
})
.parent {
display: flex;
position: relative;
}
.first,
.second,
.third {
padding: 30px;
}
.first {
background-color: salmon;
flex-basis: 120px;
}
.second {
background-color: yellow;
flex-grow: 1
}
.third {
background-color: #56D7F7;
flex-grow: 1
}
.btn {
background-color: red;
cursor: pointer;
}
.slide {
position: absolute;
background-color: green;
width: 40px;
left: 0;
top: 0;
bottom: 0;
}
<div class='parent'>
<div class='first'>1</div>
<div class='second'>2</div>
<div id="button" class='btn'>Button</div>
<div class='third'>3</div>
<div id="slide" class='slide'>
<div class='hello'>
</div>
</div>
</div>

DIV moved with position: relative create gap between that div and next one

I have 2 div's, one after the other. When i move first div with postion: relative and top: -60px it creates gap between them.
Here is example: https://codepen.io/dusannis/pen/oNgBpoK
As you can see there is gap between red and yellow div. Is there some css property that I can add to parent div that can remove this gap, or something simillar?
This is HTML:
<body>
<div class="container">
<div class="div-1">
<p>something here</p>
</div>
<div class="div-2"></div>
</div>
</body>
This is CSS:
body {
background: blue;
padding: 60px
}
.div-1 {
padding: 60px;
position: relative;
top: -50px;
background: red;
}
.div-2 {
height: 50px;
background: yellow;
}
Use negative margin instead of relative positioning.
body {
background: blue;
padding: 60px
}
.div-1 {
padding: 60px;
/* position: relative; --> not required */
margin-top: -50px;
/* change this */
background: red;
}
.div-2 {
height: 50px;
background: yellow;
}
<div class="container">
<div class="div-1">
<p>something here</p>
</div>
<div class="div-2"></div>
</div>
Codepen Demo of the effects of various methods of "moving" elements:
"Relative Position vs Margin vs Transform".
You can try add same top/position to the second div:
.div-1 {
padding: 60px;
position: relative;
top: -60px;
background: red;
}
.div-2 {
position: relative;
top: -60px;
height: 50px;
background: yellow;
}
Alternatively you can add internal div and use padding for that one, then get rid of padding for the parent and the body (or adjust to the real value if you want it):
<body>
<div class="container">
<div class="div-1">
<div class="div-1-inside">
something here
</div>
</div>
<div class="div-2"></div>
</div>
</body>
body {
background: blue;
padding: 10px;
}
.div-1 {
position: relative;
background: red;
}
.div-1-inside {
padding: 60px;
background: red;
}
.div-2 {
height: 50px;
background: yellow;
}

CSS sticky to left only [duplicate]

Why my nested sticky element with left: 0 does not stick while the nested element with top: 0 sticks normally?
.scroll {
width: 200px;
height: 200px;
border: 1px solid;
overflow: auto;
}
.container {
width: 600px;
height: 1000px;
}
.sticky-left {
position: sticky;
left: 0;
}
.sticky-top {
position: sticky;
top: 0;
}
<div class="scroll">
<div class="sticky-top">sticky-top</div>
<div class="sticky-left">sticky-left</div>
<div class="container">
<div class="sticky-top">sticky-top-nested</div>
<div class="sticky-left">sticky-left-nested</div>
</div>
</div>
Let's add some border and we will clearly see what is happening:
.scroll {
width: 200px;
height: 200px;
border: 1px solid;
overflow: auto;
}
.scroll > div {
border:2px solid green;
}
.container {
width: 600px;
height: 1000px;
border:2px solid red!important;
}
.container > div {
border:2px solid green;
}
.sticky-left {
position: sticky;
left: 0;
}
.sticky-top {
position: sticky;
top: 0;
}
<div class="scroll">
<div class="sticky-top">sticky-top</div>
<div class="sticky-left">sticky-left</div>
<div class="container">
<div class="sticky-top">sticky-top-nested</div>
<div class="sticky-left">sticky-left-nested</div>
</div>
</div>
As you can see, the nested sticky elements are both having their width equal to parent width (since they are block element) so there is no room for the left-sticky to have any sticky behavior1 since it has width:100% unlike the top one that can still stick because its height is less that the parent height.
For the non-nested elements I think it's clear.
Make the element inline-block or reduce the width and you will have a sticky behavior:
.scroll {
width: 200px;
height: 200px;
border: 1px solid;
overflow: auto;
}
.scroll > div {
border:2px solid green;
}
.container {
width: 600px;
height: 1000px;
border:2px solid red!important;
}
.container > div {
border:2px solid green;
width:150px;
}
.sticky-left {
position: sticky;
left: 0;
}
.sticky-top {
position: sticky;
top: 0;
}
<div class="scroll">
<div class="sticky-top">sticky-top</div>
<div class="sticky-left">sticky-left</div>
<div class="container">
<div class="sticky-top">sticky-top-nested</div>
<div class="sticky-left">sticky-left-nested</div>
</div>
</div>
1 A stickily positioned element is an element whose computed position value is sticky. It's treated as relatively positioned until its containing block crosses a specified threshold (such as setting top to value other than auto) within its flow root (or the container it scrolls within), at which point it is treated as "stuck" until meeting the opposite edge of its containing block.ref
In your case you were always meeting the opposite edge.
As per the MDN documentation on position: sticky, the top, right, bottom, and left properties determine the final location of positioned elements. My guess is that in order for it to be stickied from the top, it needs to also contain top: 0. The snippet I added seems to work.
.scroll {
width: 200px;
height: 200px;
border: 1px solid;
overflow: auto;
}
.container {
width: 600px;
height: 1000px;
}
.sticky-left {
position: sticky;
left: 0;
top: 0; // Add this so it sticks to top
}
.sticky-top {
position: sticky;
top: 0;
}
<div class="scroll">
<div class="sticky-top">sticky-top</div>
<div class="sticky-left">sticky-left</div>
<div class="container">
<div class="sticky-top">sticky-top-nested</div>
<div class="sticky-left">sticky-left-nested</div>
</div>
</div>

Fixed to left on horizontal scroll

I have navigation bar that is fixed to the top of the page when user scroll down.
I also need it to be fixed to the top left corner of the right block when user scroll horizontally. How to do this? Thanks for any advice.
CSS:
#box {
margin: 0 auto;
}
#content {
height: 500px;
width: 1000px;
}
#left {
max-width: 20%;
display: inline-block;
background: #aaa;
}
#right {
max-width: 80%;
display: inline-block;
background: red;
height: 100%;
width: 100%;
}
nav {
position: fixed;
top: 0;
height: 50px;
background: #666;
}
HTML:
<div id="box">
<div id="content">
<div id="left">LEFT</div>
<div id="right">
<nav>
Some text
</nav>
RIGHT
</div>
</div>
</div>
jsfiddle here: https://jsfiddle.net/deguac8y/
You can use jQuery to detect scrolling horizontally and by using class you can switch between having a fixed position nav to absolute position nav when horizontal scrolling is detected without losing the current scroll position.
JSFiddle
$(document).ready(function (){
var detectScroll = 0;
$(window).scroll(function () {
var documentScrollLeft = $(document).scrollLeft();
if (detectScroll != documentScrollLeft) {
detectScroll = documentScrollLeft;
$('nav').addClass('notFixed');
var scrollTop = $(window).scrollTop();
$('nav').css('top',scrollTop);
} else {
$('nav').removeClass('notFixed');
$('nav').css('top','auto');
}
});
});
body {
margin:0
}
#box {
margin: 0 auto;
}
#content {
height: 500px;
width: 1000px;
}
#left {
max-width: 20%;
display: inline-block;
background: #aaa;
}
#right {
max-width: 80%;
display: inline-block;
background: red;
height: 100%;
width: 100%;
}
nav {
position: fixed;
height: 50px;
background: #666;
}
nav.notFixed {
position:absolute;
top:auto;
left:auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="box">
<div id="content">
<div id="left">LEFT</div>
<div id="right">
<nav>Some text</nav>RIGHT</div>
</div>
</div>

Resources