Stick element inside absolute positioned container - javascript

Considering an element ".sticky-element" (orange) that is inside an absolute positioned container ".tab" (green, blue, fuchsia).
I wish to make it "sticky", so when the the parent ".container" (grey) scrolls, the ".sticky-element" stays always on the top.
I've tried without success getting the offset position of the sticky elements and control the "top" accordingly with the scroll position of the container. Is there any pure CSS solution? If not, how to accomplish this with JS?
Here it is a Codepen with the HTML/CSS of the concept.

Not 100% if I got what you're looking for, but the orange headers stick to the top when scrolling. I had to move them outside of the containers you had them in so they would display like your photo and added a min-width and height to your sticky class. And finally, set a z-index value on the sticky element.
body {
padding: 0;
margin: 0;
}
.wrap {
height: 100vh;
overflow: hidden;
display: flex;
justify-content: center;
}
.container {
width: 200px;
overflow-y: scroll;
display: flex;
flex-direction: column;
row-gap: 50px;
scroll-snap-type: y mandatory;
}
.group {
display: flex;
position: relative;
}
.tab {
width: 20px;
height: 100%;
background: red;
opacity: 0.9;
position: absolute;
overflow: hidden;
transition: 0.5s width;
}
.tab:hover {
width: 100%;
}
.sticky-element {
width: 50px;
height: 50px;
min-width: 50px;
min-height: 50px;
left: 20px;
top: 20px;
background: orange;
position: sticky;
z-index:1;
}
.child-handler {
display: flex;
flex: 1 1 auto;
flex-direction: column;
row-gap: 20px;
}
.child {
height: 200px;
scroll-snap-align: start;
}
.group[groupid="1"] .child {
background: green;
}
.group[groupid="2"] .child {
background: blue;
}
<body>
<div class="wrap">
<div class="container">
<div class="sticky-element">Sticky 1</div>
<div class="group" groupid="1">
<div class="tab">
</div>
<div class="child-handler">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
</div>
<div class="sticky-element">More Sticky</div>
<div class="group" groupid="2">
<div class="tab">
</div>
<div class="child-handler">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
</div>
</div>
</div>
</body>

Based on the solution of #Phaelax z I solve the problem with this approach:
$.each($(".group"), function() {
$(this).css("min-height", $(this).find(".child-handler").innerHeight());
});
function animate(el, width) {
el.stop().animate({
width: width
}, {
duration: 500,
step: function(currentWidth) {
const sticky = el.closest(".group").children(".sticky-element");
const left = parseInt(sticky.css("left"));
sticky.css("width", currentWidth - left);
}
});
}
$(".tab")
.mouseenter(function() {
animate($(this), "100%");
})
.mouseleave(function() {
animate($(this), 20);
});
body {
padding: 0;
margin: 0;
}
.wrap {
height: 100vh;
overflow: hidden;
display: flex;
justify-content: center;
}
.container {
width: 200px;
overflow-y: scroll;
display: flex;
flex-direction: column;
row-gap: 50px;
scroll-snap-type: y mandatory;
}
.group {
display: flex;
position: relative;
}
.sticky-element {
max-width: 50px;
height: 50px;
width: 0;
left: 20px;
top: 20px;
margin-top: 20px;
margin-bottom: 20px;
background: orange;
position: sticky;
z-index: 2;
overflow: hidden;
pointer-events: none;
}
.tab-child-wrap {
position: absolute;
width: 100%;
height: 100%;
}
.tab {
width: 20px;
height: 100%;
background: red;
opacity: 0.9;
position: absolute;
overflow: hidden;
z-index: 1;
}
.tab-content {
width: 100%;
}
.child-handler {
display: flex;
flex: 1 1 auto;
flex-direction: column;
row-gap: 20px;
}
.child {
height: 200px;
scroll-snap-align: start;
}
.group[groupid="1"] .child {
background: green;
}
.group[groupid="2"] .child {
background: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body>
<div class="wrap">
<div class="container">
<div class="group" groupid="1">
<div class="sticky-element">Sticky 1</div>
<div class="tab-child-wrap">
<div class="tab">
<div class=".tab-content">Content</div>
</div>
<div class="child-handler">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
</div>
</div>
<div class="group" groupid="2">
<div class="sticky-element">More Sticky</div>
<div class="tab-child-wrap">
<div class="tab">
<div class=".tab-content">Content</div>
</div>
<div class="child-handler">
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
<div class="child"></div>
</div>
</div>
</div>
</div>
</div>
</body>

Related

How to resolve the issue with responsive lines in css when the browser is resized

I have tried drawing 4 vertical equidistant lines as shown in the image. The lines come out of the container when the browser is resized. How can I make it stay within the container irrespective of the browser size? I am new to CSS.
Please let me know if more details are needed on this.
.container {
width: 100%;
position: relative;
display: flex;
padding-right: 100px;
box-sizing: border-box;
}
.inner-container {
display: flex;
flex: 1;
position: relative;
}
.bar {
flex-grow: .8;
display: flex;
height: 20px;
background-color: lightblue;
}
.blackline {
width: 10px;
height: 20px;
background-color: black;
position: absolute;
z-index: 1;
margin-left: 100%;
}
.line {
width: 1px;
height: 100%;
position: absolute;
background-color: #000000;
margin-top: 2%;
}
.line1 {
left: 0%;
}
.line2 {
left: 30%;
}
.line3 {
left: 60%;
}
.line4 {
left: 89%;
}
<div class="container">
<div class="line line1"></div>
<div class="line line2"></div>
<div class="line line3"></div>
<div class="line line4"></div>
<div class="inner-container">
<div class="bar"></div>
<div class="blackline"></div>
</div>
</div>
The expected result is to have the 1st line drawn at the container's start point, the last line at the end of the container and the two lines in between be equidistant from each other.
The desired result
Try to add parent div for lines and use Flex property for that. So, you get equal distance in all the responsive windows. Below is the sample code for your image.
<!DOCTYPE html>
<html>
<head>
<style>
.container{
width: 100%;
position: relative;
display: flex;
box-sizing: border-box;
}
.inner-container {
display: flex;
flex:1;
position: relative;
}
.bar {
flex-grow: .8;
display: flex;
height: 20px;
background-color: lightblue;
}
.blackline {
width: 10px;
height: 20px;
background-color: black;
position: absolute;
z-index: 1;
right: 0;
}
.line-container {
display: flex;
justify-content: space-between;
width: 100%;
position: absolute;
height: 100%;
top: 100%;
}
.line {
width: 1px;
height: 100%;
background-color: #000000;
}
</style>
</head>
<body>
<div class="container">
<div class="inner-container">
<div class="line-container">
<div class="line line1"></div>
<div class="line line2"></div>
<div class="line line3"></div>
<div class="line line4"></div>
</div>
<div class="bar"></div>
<div class="blackline"></div>
</div>
</div>
</body>
</html>
Here my attempt which utilizing the advantage of flex without position. The usage of position:absolute is actually breaking the flex position. So I restructure your container and css to have as minimal as it can for the syntax to achieve your desired result.
The magic for the equal sizing of each .line is the following:
flex-grow: 1;
Here a very good reference for understanding flex better: https://css-tricks.com/snippets/css/a-guide-to-flexbox/
Not sure is it what you want but hope it helps~
.container {
width: 100%;
display: flex;
flex-wrap: wrap;
}
.inner-container {
display: flex;
justify-content: space-between;
width: 100%;
}
.bar {
flex-grow: .8;
background-color: lightblue;
}
.blackline {
width: 10px;
height: 20px;
background-color: black;
}
.line {
height: 20px;
border-left: 1px solid black;
flex-grow: 1;
}
.line:last-child {
border-right: 1px solid black;
}
<div class="container">
<div class="inner-container">
<div class="bar"></div>
<div class="blackline"></div>
</div>
<div class="line line1"></div>
<div class="line line2"></div>
<div class="line line3"></div>
</div>
I would use margin on your container instead of padding and use space between on your inner container flex (instead of absolute positioning), then you can absolutely position your lines 1/3rd apart and use right 0 for your last line
.container {
width: calc(100% - 100px);
position: relative;
display: flex;
margin-right: 100px;
box-sizing: border-box;
}
.inner-container {
display: flex;
flex-grow: 1;
position: relative;
justify-content: space-between;
}
.bar {
flex-grow: 0.8;
display: flex;
height: 20px;
background-color: lightblue;
}
.blackline {
width: 10px;
height: 20px;
background-color: black;
}
.line {
width: 1px;
height: 100%;
position: absolute;
background-color: #000000;
margin-top: 2%;
}
.line1 {
left: 0;
}
.line2 {
left: 33.3333%;
}
.line3 {
left: 66.6667%;
}
.line4 {
right: 0;
}
<div class="container">
<div class="line line1"></div>
<div class="line line2"></div>
<div class="line line3"></div>
<div class="line line4"></div>
<div class="inner-container">
<div class="bar"></div>
<div class="blackline"></div>
</div>
</div>

How to make smooth block animation?

A slide opening has twitches, how can I get rid of them?
If with the last block everything is more or less normal, then with the rest are not even close. How do I make all the blocks, except the one that was clicked disappear and the remaining block is smoothly moved to the center of the screen and then opens.
$(document).ready(function() {
let status = false;
$(".block").on("click", function() {
if (status == false) {
$(".block").not($(this)).slideToggle();
$(this).animate({
maxWidth: "100%"
}, 500).find(".slide").css("display", "flex").animate({
width: "100%"
}, 500);
status = true;
} else {
$(this).animate({
maxWidth: "32%"
}, 500).find(".slide").css("display", "none").animate({
width: "0%"
}, 500);
$(".block").not($(this)).slideToggle();
status = false;
}
})
});
#import url('https://fonts.googleapis.com/css2?family=Montserrat:wght#100;200;300;400;500;600;700;800;900&display=swap');
html,
body {
font-size: 10px;
}
body {
background-color: #1c1c1e;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.wrapper {
width: 100%;
padding: 1rem 0;
}
.flex-block {
display: flex;
justify-content: center;
flex-wrap: wrap;
width: 100%;
max-width: 800px;
margin: 0 auto;
}
.block {
display: flex;
margin: 15px;
justify-content: space-between;
width: 100%;
max-width: 32%;
height: 200px;
background-color: #464649;
font-family: 'Montserrat', sans-serif;
overflow: hidden;
}
.block .slide,
.block .content {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
color: #d7d7df;
}
.block .slide {
background-color: #303033;
width: 0%;
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<section class="test">
<div class="flex-block">
<div class="block">
<div class="content">
<p>Block</p>
</div>
<div class="slide">
<p>Slide</p>
</div>
</div>
<div class="block">
<div class="content">
<p>Block</p>
</div>
<div class="slide">
<p>Slide</p>
</div>
</div>
<div class="block">
<div class="content">
<p>Block</p>
</div>
<div class="slide">
<p>Slide</p>
</div>
</div>
</div>
</section>
</div>
To start, don't use JQUERY, convert that code into vanilla JS. The best way is to use JS just to apply/remove classes and use CSS for all the transitions.
You have JS events that can tell you if a certain transition applied to a block has ended (look for "ontransitionend" event), this can help you to apply CSS or a class in the exact time you need.
Not sure what this is what you are after, but I would use the callback functions of your slide toggle and animation so on the first click, you hide the unwanted blocks first and then show the slide and then on your second click, you hide the slide and then show the hidden blocks:
$(document).ready(function() {
let status = false;
$(".block").on("click", function() {
const $currentBlock = $(this); // get current clicked block
if (status == false) {
// hide non clicked blocks
$(".block").not($currentBlock).slideToggle(function() {
// run this when slide toggle has finished
$currentBlock.animate({
maxWidth: "100%"
}, 500).find(".slide").css("display", "flex").animate({
width: "100%"
}, 500);
});
status = true;
} else {
$currentBlock.animate({
maxWidth: "32%"
}, 500).find(".slide").animate({
width: "0"
}, 500, function() {
// run this when slide has finished animating
$(this).css("display", "none"); // only hide the slide once the animation is finished
$(".block").not($currentBlock).slideToggle(); // show the other blocks
});
status = false;
}
})
});
#import url('https://fonts.googleapis.com/css2?family=Montserrat:wght#100;200;300;400;500;600;700;800;900&display=swap');
html,
body {
font-size: 10px;
}
body {
background-color: #1c1c1e;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.wrapper {
width: 100%;
padding: 1rem 0;
}
.flex-block {
display: flex;
justify-content: center;
flex-wrap: wrap;
width: 100%;
max-width: 800px;
margin: 0 auto;
}
.block {
display: flex;
margin: 15px;
justify-content: space-between;
width: 100%;
max-width: 32%;
height: 200px;
background-color: #464649;
font-family: 'Montserrat', sans-serif;
overflow: hidden;
}
.block .slide,
.block .content {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
color: #d7d7df;
}
.block .slide {
background-color: #303033;
width: 0%;
display: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
<section class="test">
<div class="flex-block">
<div class="block">
<div class="content">
<p>Block</p>
</div>
<div class="slide">
<p>Slide</p>
</div>
</div>
<div class="block">
<div class="content">
<p>Block</p>
</div>
<div class="slide">
<p>Slide</p>
</div>
</div>
<div class="block">
<div class="content">
<p>Block</p>
</div>
<div class="slide">
<p>Slide</p>
</div>
</div>
</div>
</section>
</div>

positioning div after it has been rotated

I would like to make a div with 3 texts, rotate it and place it on a fixed spot on the page. I would like it to perfectly fit the size of the screen,
So I created a div which height and width were the opposite of what I need then I rotated it. The thing is that I'm having problems positioning it correctly on the spot I want. (top:0 right:40px)
This is my code where I tried to move the div around.
.page {
width: 100%;
heigth: 100%;
background-color: #fefefe;
}
.green-band {
height: 90px;
width: 100vh;
padding: 0 30px;
background-color: green;
display: flex;
justify-content: space-between;
align-items: center;
transform: rotate(-90deg);
position: fixed;
top: 0;
right: 40px;
}
<div class="page"></div>
<div class="green-band">
<div class="text-inside">Text 1</div>
<div class="text-inside">Text 2</div>
<div class="text-inside">Text 3</div>
</div>
Can you help me figuring out how to correctly position the div?
.page {
width: 100%;
heigth: 100%;
background-color: #fefefe;
}
.green-band {
height: 90px;
width: 100vh;
padding: 0 30px;
background-color: green;
display: flex;
justify-content: space-between;
align-items: center;
transform: translateX(calc(100% - 40px)) rotate(-90deg);
transform-origin: bottom left;
position: fixed;
bottom: 0;
right: 0;
}
<div class="page"></div>
<div class="green-band">
<div class="text-inside">Text 1</div>
<div class="text-inside">Text 2</div>
<div class="text-inside">Text 3</div>
</div>

How to use scrollWidth value of parent element using css?

I want to set width and height for cover element same as scrollWidth and scrollHeight of container element. So during the scrolling it will fully cover container. At the moment it uses width and height of container element cropped by scroll.
The whole area should be covered by green color. Is it possible to do using only css?
.container {
width: 400px;
height: 500px;
border: solid 2px;
position: relative;
overflow: auto;
}
.row {
width: 500px;
height: 60px;
border: solid 4px blue;
margin: 10px;
}
.cover {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: green;
opacity: 0.5;
}
<div class="container">
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="cover"></div>
</div>
I don't know your the final goal is, but have you tried simply applying the background to the container?
.container {
width: 400px;
height: 500px;
border: solid 2px;
overflow: auto;
background-color: rgba(0,128,0,.5);
}
.row {
width: 500px;
height: 60px;
border: solid 4px blue;
margin: 10px;
}
<div class="container">
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
</div>
You can use JavaScript to get the scrollWidth and scrollHeight of the parent.
const container = document.querySelector('.container');
const cover = document.querySelector('.cover');
cover.style.height = container.scrollHeight + "px";
cover.style.width = container.scrollWidth + "px";
.container {
width: 400px;
height: 500px;
border: solid 2px;
position: relative;
overflow: auto;
}
.row {
width: 500px;
height: 60px;
border: solid 4px blue;
margin: 10px;
}
.cover {
position: absolute;
top: 0;
left: 0;
background-color: green;
opacity: 0.5;
}
<div class="container">
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="cover"></div>
</div>
Here's a Javascript code to do it.
Quick reminder: document.querySelector(".cover") here return just the first element met but if there are more use document.querySelectorAll()
EDIT : You can do it with only CSS too by wrapping the rows in the cover (It's why i put Javascript in comments)
/*const { scrollWidth, scrollHeight } = document.querySelector(".container");
const coverElement = document.querySelector(".cover");
coverElement.style.width = `${scrollWidth}px`;
coverElement.style.height = `${scrollHeight}px`; */
.container {
width: 400px;
height: 500px;
border: solid 2px;
position: relative;
overflow: auto;
}
.row {
width: 500px;
height: 60px;
border: solid 4px blue;
margin: 10px;
}
.cover {
position: absolute;
top: 0;
left: 0;
background-color: green;
opacity: 0.5;
}
<div class="container">
<div class="cover">
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
</div>
</div>
try this
.container {
width: 400px;
height: 500px;
border: solid 2px;
position: relative;
overflow: auto;
}
.cover {
display: flex;
flex-direction: column;
width: 100vw;
height: 100vh;
background-color: green;
opacity: 0.5;
}
.row {
width: 500px;
height: 60px;
border: solid 4px blue;
margin: 10px;
}
<div class="container cover">
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
<div class="row"></div>
</div>
</div>

How to struct a Gantt Chart using flex divs using HTML and CSS

I'm structuring a Gantt Chart component using CSS and HTML.
The below image illustrate the necessary user viewing interactions:
The red boundaries is the current user view. The desired scrolling behaviour is as follows:
a) The tasks are must be scrolled vertically and horizontally as data can be out of viewable area.
b) When scrolling horizontally, the scale must be scrolled in sync (the scale will have date/time tags).
c) When scrolling horizontally, the left title boxes must remain fixed on the left, not scrolling together.
d) When scrolling vertically, the scale must remain fixed at the top.
e) When scrolling vertically, the titles boxes scrolls together with taks, keeping in sync view.
Here is what've tried so far with no success.
.container {
height: 100vh;
width: 100vw;
background-color: #cdcdcd;
}
.areas {
display: flex;
flex-direction: row;
justify-content: space-between;
height: 100%;
}
.leftarea {
display: flex;
flex-direction: column;
background-color: #ababab;
height: 100%;
margin-top: 30px;
}
.rightarea {
display: flex;
flex-direction: column;
height: 100%;
}
.titlecard {
display: flex;
justify-content: center;
align-items: center;
width: 50px;
min-height: 30px;
background-color: #435543;
color: white;
margin: 10px;
}
.scale {
display: flex;
color: white;
background-color: #433777;
}
.scaletick {
display: flex;
justify-content: center;
align-items: center;
height: 30px;
width: 80px;
color: white;
border: 1px solid white;
}
.tasks {
display: flex;
flex-direction: column;
background-color: #393984;
height: 100%;
}
.taskcard {
display: flex;
justify-content: center;
align-items: center;
background-color: #fefefe;
border-radius: 4px;
padding: 5px;
height: 50px;
width: 100px;
margin: 10px;
}
.taskcard1 {
width: 100px;
margin-left: 80px;
}
.taskcard2 {
width: 550px;
margin-left: 230px;
}
.taskcard3 {
width: 400px;
margin-left: 40px;
}
.taskcard4 {
width: 300px;
margin-left: 130px;
}
.taskcard5 {
width: 350px;
margin-left: 400px;
}
.taskcard6 {
width: 90px;
margin-left: 90px;
}
.taskcard7 {
width: 70px;
margin-left: 230px;
}
.taskcard8 {
width: 150px;
margin-left: 10px;
}
.taskcard9 {
width: 60px;
margin-left: 120px;
}
.taskcard10 {
width: 50px;
margin-left: 45px;
}
<div class="container">
<div class="areas">
<div class="leftarea">
<div class="titlecard">
Title 1
</div>
<div class="titlecard">
Title 2
</div>
<div class="titlecard">
Title 3
</div>
<div class="titlecard">
Title 4
</div>
<div class="titlecard">
Title 5
</div>
<div class="titlecard">
Title 6
</div>
<div class="titlecard">
Title 7
</div>
<div class="titlecard">
Title 8
</div>
<div class="titlecard">
Title 9
</div>
<div class="titlecard">
Title 10
</div>
</div>
<div class="rightarea">
<div class="scale">
<div class="scaletick">
01/01/2000
</div>
<div class="scaletick">
02/01/2000
</div>
<div class="scaletick">
03/01/2000
</div>
<div class="scaletick">
04/01/2000
</div>
<div class="scaletick">
05/01/2000
</div>
<div class="scaletick">
06/01/2000
</div>
<div class="scaletick">
07/01/2000
</div>
<div class="scaletick">
08/01/2000
</div>
<div class="scaletick">
09/01/2000
</div>
<div class="scaletick">
10/01/2000
</div>
</div>
<div class="tasks">
<div class="taskcard taskcard1">
Task 1
</div>
<div class="taskcard taskcard2">
Task 2
</div>
<div class="taskcard taskcard3">
Task 3
</div>
<div class="taskcard taskcard4">
Task 4
</div>
<div class="taskcard taskcard5">
Task 5
</div>
<div class="taskcard taskcard5">
Task 6
</div>
<div class="taskcard taskcard7">
Task 7
</div>
<div class="taskcard taskcard8">
Task 8
</div>
<div class="taskcard taskcard9">
Task 9
</div>
<div class="taskcard taskcard10">
Task 10
</div>
</div>
</div>
</div>
</div>
How can I get the necessary behaviour?

Categories