Horizontal scroll areas with buttons and gradients - javascript

This is my code so far:
// Show and hide gradients
$(document).ready(function() {
$(".scroll-area").each(function(index) {
if ($(this)[0].scrollWidth <= $(this)[0].clientWidth) {
$(this).closest(".container").find(".left").css("display", "none");
$(this).closest(".container").find(".right").css("display", "none");
} else {
$(this).scroll(function() {
if ($(this)[0].scrollWidth > $(this)[0].clientWidth) {
if ($(this).scrollLeft() > 0) {
$(this).closest(".container").find(".left").css("display", "block");
}
if ($(this).scrollLeft() == 0) {
$(this).closest(".container").find(".left").css("display", "none");
}
var fullWidth = $(this)[0].scrollWidth - $(this)[0].offsetWidth - 1;
if ($(this).scrollLeft() >= fullWidth) {
$(this).closest(".container").find(".right").css("display", "none");
}
if ($(this).scrollLeft() < fullWidth) {
$(this).closest(".container").find(".right").css("display", "block");
}
}
});
}
});
});
// Scroll buttons
let interval;
$('.scroll-btn').on('mousedown', ({
target
}) => {
const type = $(target).attr('id');
interval = setInterval(() => {
var x = $('#a').scrollLeft();
$('#a').scrollLeft(type === 'left-arrow' ? x - 10 : x + 10);
}, 50);
});
$('.scroll-btn').on('mouseup', () => clearInterval(interval));
* {
margin: 0;
padding: 0;
font-family: sans-serif;
font-size: 16px;
}
.container {
width: 550px;
height: 80px;
background-color: grey;
position: relative;
margin-bottom: 20px;
}
.scroll-area {
white-space: nowrap;
overflow-x: auto;
height: 100%;
}
.left,
.right {
width: 50px;
height: 100%;
position: absolute;
pointer-events: none;
top: 0;
}
.left {
background: linear-gradient(90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
left: 0;
display: none;
}
.right {
background: linear-gradient(-90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
right: 0;
}
.arrow {
display: block;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
height: 100%;
width: 15px;
cursor: pointer;
}
.left-arrow {
left: 0;
}
.right-arrow {
right: 0;
}
.left-arrow div,
.right-arrow div {
font-size: 40px;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<div class="container">
<div id="x" class="left"></div>
<div class="right"></div>
<div class="arrow left-arrow">
<div class="scroll-btn" id="left-arrow">
<</div>
</div>
<div class="arrow right-arrow">
<div class="scroll-btn" id="right-arrow">></div>
</div>
<div id="a" class="scroll-area">
<div class="text">Scroll to right. The gradients and arrows should appear and disappear based on the scroll position. It should work with more than one container. Lorem ipsum.</div>
</div>
</div>
The needs are:
The arrows should appear and disappear in the same way like the gradients.
If there is not enough text to cause a scrollable area, there should be no gradient and now arrow.
There should be more than one container in the end.
Can somebody help me to do that? I would be super thankful!

You can put your arrows inside the left/right gradient divs. That way they will show/hide same way as the gradients.
EDIT
I cleaned up the code a bit since the original answer was kinda messy. (or 'weird' as mstephen19 put it :)).
// Show gradient and left/right arrows only if scrollable
$(".scroll-area").each((i, el) => {
$(el).parent().find(".right")[el.scrollWidth > el.clientWidth ? "show" : "hide"]();
});
// Show/hide gradient and arrows on scroll
$('.scroll-area').scroll((e) => {
const fullWidth = $(e.target)[0].scrollWidth - $(e.target)[0].offsetWidth - 1;
const left = $(e.target).scrollLeft()
$(e.target).parent().find(".left, .left-arrow")[left > 0 ? "show" : "hide"]();
$(e.target).parent().find(".right, .right-arrow")[left < fullWidth ? "show" : "hide"]();
});
// Scroll on left/right arrow mouse down
let intervalId;
$(".left-arrow, .right-arrow").on("mousedown", (e) => {
const scroll = $(e.target).closest(".container").find(".scroll-area");
intervalId = setInterval(() => {
const left = scroll.scrollLeft();
scroll.scrollLeft(e.target.classList.contains("left-arrow") ? left - 10 : left + 10);
}, 50);
}).on("mouseup mouseleave", () => {
clearInterval(intervalId);
});
* {
margin: 0;
padding: 0;
font-family: sans-serif;
font-size: 16px;
}
.container {
width: 550px;
height: 80px;
background-color: grey;
position: relative;
margin-bottom: 20px;
margin-left: 20px;
}
.scroll-area {
white-space: nowrap;
overflow-x: auto;
height: 100%;
}
.left,
.right {
width: 50px;
height: 100%;
position: absolute;
top: 0;
}
.left {
background: linear-gradient(90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
left: 0;
display: none;
}
.right {
background: linear-gradient(-90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
right: 0;
text-align: right;
}
.left-arrow,
.right-arrow {
margin: 0 10px;
position: absolute;
top: 50%;
-ms-transform: translateY(-50%);
transform: translateY(-50%);
cursor: pointer;
user-select: none;
font-size: 40px
}
.left-arrow {
display: none;
left: -25px;
}
.right-arrow {
right: -25px;
}
<html>
<head>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<div class="container">
<div class="left"></div>
<div class="right"></div>
<div class="left-arrow"><</div>
<div class="right-arrow">></div>
<div class="scroll-area">
<div class="text">Scroll to right. The gradients and arrows should appear and disappear based on the scroll position. It should work with more than one container. Lorem ipsum.</div>
</div>
</div>
<div class="container">
<div class="left"><span class="left-arrow"><</span></div>
<div class="right"><span class="right-arrow">></span></div>
<div class="scroll-area">
<div class="text">No scroll.</div>
</div>
</div>
</body>
</html>

Some things about your code:
Your original code would not work with multiple containers, because you had a hardcoded #a ID in the interval code. You should really only have IDs on one element ideally, anyways (they're unique identifiers, while classes can be placed on multiple elements). The .scroll-area element should be found based on the target clicked.
You should combine your gradient and arrow elements into one element. By that, I mean making the div in which the arrow lives should be a child of the gradient div. Why manage them both separately?
Use class adding/removing/toggling instead of directly setting the CSS. Remember - when you find yourself writing the same code multiple times, it usually means there is a way to condense it down and make your code more dry and easier to understand + read.
Don't use the literal < and > symbols, as it can confuse some browsers. Use < and > instead.
Rather than toggling display to none and block, it's better to use visibility in this specific case. In my example, we use opacity for a fun fading effect.
Don't forget to listen for both mouseup mouseout events :)
Here is the working solution. I've refactored the code a bit:
let interval;
$('.arrow').on('mousedown', ({ target }) => {
const type = target.classList[1];
const scrollArea = $(target).parent().find('.scroll-area');
interval = setInterval(() => {
const prev = scrollArea.scrollLeft();
scrollArea.scrollLeft(type === 'left-arrow' ? prev - 10 : prev + 10);
}, 50);
});
$('.arrow').on('mouseup mouseout', () => clearInterval(interval));
$('.scroll-area').on('scroll', ({ target }) => {
const left = $(target).parent().find('.left-arrow');
const right = $(target).parent().find('.right-arrow');
const scroll = $(target).scrollLeft();
const fullWidth = $(target)[0].scrollWidth - $(target)[0].offsetWidth;
if (scroll === 0) left.addClass('hide');
else left.removeClass('hide');
if (scroll > fullWidth) right.addClass('hide');
else right.removeClass('hide');
});
.container {
width: 550px;
height: 80px;
background: grey;
position: relative;
}
.right-arrow,
.left-arrow {
height: 100%;
width: 50px;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
cursor: pointer;
transition: all 0.2s linear;
}
.scroll-area {
white-space: nowrap;
overflow-x: scroll;
height: 100%;
}
.right-arrow {
background: linear-gradient(-90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
left: 500px;
}
.left-arrow {
background: linear-gradient(90deg, orange 0%, rgba(0, 0, 0, 0) 100%);
left: 0px;
}
.scroll-btn {
pointer-events: none;
}
.hide {
opacity: 0;
}
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<div class="container">
<div class="arrow left-arrow">
<div class="scroll-btn" id="left-arrow"><</div>
</div>
<div class="arrow right-arrow">
<div class="scroll-btn" id="right-arrow">></div>
</div>
<div class="scroll-area">
<div class="text">
Scroll to right. The gradients and arrows should appear and disappear based on the scroll position. It should work with more than one
container. Lorem ipsum.
</div>
</div>
</div>
PS: If you don't like the fade effect, remove the transition: all 0.2s linear; part of the CSS, and switch .hide's opacity: 0 to visibility: hidden.

Related

How to use javascript to open the modal when connecting the API so that the user can see the animation effect?

let btn = document.querySelector('.btn');
let modal = document.querySelector('.modal');
let wrap = document.querySelector('.wrap');
let photo = document.querySelector('.photo');
let svg = document.querySelector('svg');
let data = [{
title: "dog",
age: 10,
rank: [{
rankStatus: 'behind'
},
{
rankStatus: 'generally',
rankNum: '20'
},
{
rankStatus: 'excellent'
}
]
}]
let rankStatusArr = data[0].rank.map(item => item.rankStatus);
let rankNum = data[0].rank.find(item => item.rankNum).rankNum;
btn.addEventListener('click', function(e) {
svg.style.display = "block"
axios.get("https://dog.ceo/api/breeds/image/random")
.then((response) => {
// 设置处理程序以在加载图像后显示图像
photo.addEventListener('load', () => {
svg.style.display = "none"
modal.style.display = "flex";
});
// 添加“加载”处理程序后设置图像源
photo.src = response.data.message;
})
.catch((error) => console.log(error));
// 排名狀態在 popup 打開後再載入動畫
let progress = rankNum;
let dot = document.getElementById("dot");
var dotPosition = (progress / 30) * 100;
dot.style.left = dotPosition + "%";
let txt = document.querySelectorAll('.txt');
for (let i = 0; i < txt.length; i++) {
txt[i].innerHTML = rankStatusArr[i];
}
})
wrap.addEventListener('click', function(e) {
e.stopPropagation();
})
modal.addEventListener('click', function() {
modal.style.display = "none"
})
.modal {
width: 100%;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: none;
justify-content: center;
align-items: center;
}
.wrap {
width: 300px;
height: 300px;
padding: 20px;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 20px;
flex-direction: column;
}
.wrap .photo {
width: 100%;
height: 200px;
object-fit: cover;
margin-bottom: 20px;
}
.wrap #progress-bar-container {
width: 100%;
height: 5px;
background-color: #fff;
position: relative;
display: flex;
flex-wrap: nowrap;
}
.wrap #progress-bar-container .progress-bar {
width: 33.33%;
height: 5px;
background-color: #ccc;
margin-right: 8px;
border-radius: 20px;
}
.wrap #progress-bar-container .progress-bar .txt {
text-align: center;
color: #ccc;
margin-top: 20px;
}
#progress-bar-1 {
background-color: #ddd;
}
#progress-bar-2 {
background-color: #ddd;
}
#progress-bar-3 {
background-color: #ddd;
margin-right: 0;
}
#dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #333;
position: absolute;
top: -3px;
left: 0;
transition: left 0.2s ease-out;
}
svg {
display: none;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
svg .load {
stroke-dasharray: 0 500;
animation: rot 1s linear infinite;
}
#keyframes rot {
100% {
stroke-dasharray: 500 500;
}
}
.green {
color: yellowgreen;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.3.2/axios.min.js"></script>
<button class='btn'>open</button>
<div class="modal">
<div class="wrap">
<img class="photo" src="" alt="photo">
<div id="progress-bar-container">
<div class="progress-bar" id="progress-bar-1">
<p class="txt">1</p>
</div>
<div class="progress-bar" id="progress-bar-2">
<p class="txt">2</p>
</div>
<div class="progress-bar" id="progress-bar-3">
<p class="txt">3</p>
</div>
<div id="dot"></div>
</div>
</div>
</div>
<!-- load -->
<svg width="240px" height="240px" version="1.1" xmlns="http://www.w3.org/2000/svg">
<circle cx="110" cy="110" r="90" stroke-width="10" stroke="gainsboro" fill="none"></circle>
<circle cx="110" cy="110" r="90" stroke-width="10" stroke="darkturquoise" fill="none" class="load"></circle>
</svg>
I encountered a little difficulty in the following requirements. The first difficulty is that I hope that the popup will not be opened until the picture is fully loaded! But I hope that when the popup is opened, the dot can add animation and run to the specified progress position, but it seems that the animation I want to appear has already run before it is opened, and the user will not see the moving animation effect. The second problem is that I want to add a green color to the rankStatus font on the screen if the rankNum is included in the rankStatus object, but I am a novice in programming and don't know which section to add to achieve this. Hope to get your help, thank you.
To solve the first difficulty, you can add an event listener to the photo element which will be triggered when the image is loaded. Inside this event listener, you can set the display of the modal to 'flex' and the display of the svg element to 'none'.
To solve the second difficulty, you can use an if statement inside your rankStatusArr loop. This statement should check if the rankNum is included in the rankStatus object and if so, add a class to the txt element which adds the green color style.
let txt = document.querySelectorAll('.txt');
for (let i = 0; i < txt.length; i++) {
txt[i].innerHTML = rankStatusArr[i];
if (rankNum === rankStatusArr[i]) {
txt[i].classList.add('green');
}
}

Can I use requestAnimationFrame to smooth out scroll behaviour?

I have a small scroll effect which simulate that a logo will disappear if a lower div will scroll over it.
Currently I'm checking if two divs are intersecting. If this is true, then the height of the div of the logo will decrease with the scroll position of the div beneath.
Unfortunately, my demo is not foolproof and some fragments of the logo are still visible.
Is there a way to do this jank-free? Maybe with requestAnimationFrame?
function elementsOverlap(el1, el2) {
const domRect1 = el1.getBoundingClientRect();
const domRect2 = el2.getBoundingClientRect();
return !(
domRect1.top > domRect2.bottom ||
domRect1.right < domRect2.left ||
domRect1.bottom < domRect2.top ||
domRect1.left > domRect2.right
);
}
const el1 = document.querySelector(".logo");
const el2 = document.querySelector(".clickblocks");
let scrollPositionEl2;
let heightDifference;
const logoHeight = el1.offsetHeight;
document.addEventListener("DOMContentLoaded", () => {
var scrollDirectionDown;
scrollDirectionDown = true;
window.addEventListener("scroll", () => {
if (this.oldScroll > this.scrollY) {
scrollDirectionDown = false;
} else {
scrollDirectionDown = true;
}
this.oldScroll = this.scrollY;
// test
if (scrollDirectionDown) {
if (elementsOverlap(el1, el2) === true) {
scrollPositionEl2 = el2.getBoundingClientRect().top;
heightDifference = logoHeight - scrollPositionEl2 + 100;
//console.log(logoHeight - heightDifference);
el1.style.height = `${logoHeight - heightDifference}px`;
}
} else {
//scrolling up
scrollPositionEl2 = el2.getBoundingClientRect().top - 100;
el1.style.height = `${scrollPositionEl2}px`;
//console.log(logoHeight);
}
});
});
#import url("https://fonts.googleapis.com/css2?family=Inter:wght#900&display=swap");
.wrapper {
max-width: 100vw;
margin: 0 auto;
background-image: url("https://picsum.photos/1920/1080");
background-size: cover;
background-attachment: fixed;
height: 1200px;
position: relative;
&::after {
content: "";
position: absolute;
background: rgba(0, 0, 0, 0.3);
width: 100%;
height: 100%;
inset: 0;
}
}
body {
margin: 0;
}
main {
width: 100%;
height: 100vh;
position: relative;
z-index: 1;
}
.clickblocks {
width: 100%;
height: 200px;
display: grid;
grid-template-columns: repeat(12, (minmax(0, 1fr)));
}
.clickblock {
transition: all ease-in-out 0.2s;
backdrop-filter: blur(0px);
border: 1px solid #fff;
height: 100%;
grid-column: span 6 / span 6;
font-size: 54px;
font-weight: 700;
padding: 24px;
font-family: "Inter", sans-serif;
color: white;
text-transform: uppercase;
&:hover {
backdrop-filter: blur(10px);
}
}
.logo {
background: url("https://svgshare.com/i/ivR.svg");
width: 100%;
background-repeat: no-repeat;
background-position: top;
position: fixed;
top: 100px;
}
.logo-wrapper {
position: relative;
}
<div class="wrapper">
<main>
<div class="logo-wrapper" style="height: 390px">
<div class="logo" style="height: 300px">
</div>
</div>
<div class="clickblocks">
<div class="clickblock">
Some Content
</div>
</div>
</main>
</div>
Few things here to optimize your performance.
getBoundingClientRect() is a rather expensive calculation. If there are NO other options it's fine.
The Intersection Observer API is a lot more performant, and you can set the root element on the API. Then observe the element that is moving. This should be able to telly you if their are colliding.
Whenever you do scroll based logic, you should really try and throttle the logic so that the scroll any fires ever 16.6ms. That will reduce the number of times the calculations are made, and speed things up on the FE.
Learn how to use Google Chrome's performance tab. It can be overwhelming at first, but it gives you the ability to drill into the exact piece of code that's slowing your site down.
Learn about JS's event loop, and what's really going on under the hood. This video by Jake Archibald really help me understand it.
Hope this helped, sorry that I didn't give you an actual solution.

Hiding/Showing CSS elements does not work?

THE WHOLE CODE IN JSFIDDLE
I have been struggling to effectively remove the code and css created in the function Seifenblasen_blasen()
function Seifenblasen_blasen(){
btn1.style.display = 'none';
document.getElementById("text").innerHTML="Bubble"
const section = document.querySelector('section')
const createElement = document.createElement('spawn')
var size = Math.random() * 60;
createElement.style.width = 30 + size + 'px';
createElement.style.height = 30 + size + 'px';
createElement.style.left = Math.random() * innerWidth + "px";
section.appendChild(createElement);
setTimeout(() => {
createElement.remove()
},8000)
}
const Blaseninterval = setInterval(Seifenblasen_blasen, 100)
created CSS:
section {
width: 100%;
height: 100vh;
overflow: hidden;
background: #1F69FA;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
section.text{
font-size: 10em;
color: #333;
margin: 0 auto;
text-align: center;
font-family: consolas;
background-color:#1F69FA;
pointer-events: none;
border: none;
}
section spawn {
position: absolute;
bottom: -80px;
background: transparent;
border-radius: 50%;
pointer-events: none;
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
animation: animate 4s linear infinite;
}
section spawn:before {
content: '';
position: absolute;
width: 100%;
height: 100%;
transform: scale(0.25) translate(-70%, -70%);
background: radial-gradient(#fff, transparent);
opacity: 0.6;
border-radius: 50%;
}
#keyframes animate {
0% {
transform: translateY(0%);
opacity: 1;
}
99% {
opacity: 1;
}
100% {
transform: translateY(-2000%);
opacity: 0;
}
section span {
margin-top: 700px;
font-size: 1em;
color: #333;
margin: 0 auto;
font-family: consolas;
background-color: #1F69FA;
border: none;
position: absolute;
}
HTML:
<section id="section">
<div class="content">
<button id="btn"></button>
<button id="btn1"></button>
</div>
</section>
to then execute the next function function next(). This removal is needed because when I don't remove the elements from the first function the second wont work. I could just do document.head.innerHTML = "" but that would then also remove the css needed for the button appearing in the next function. So then I tried to make variables with const
const btn = document.getElementById('text');
const btn1 = document.getElementById('text1');
const section = document.querySelector('section')
// in function Seifenblasen_blasen()
btn1.style.display = 'none';
// in function next()
section.style.display = 'none';
btn.style.display = 'none';
btn1.style.display = 'block';
to hide and show only parts of the css without removing the css entirely to keep the styling intact, but now nothing works anymore.(the button on the next Screen doesn't show up at all and the first button does not contain any styling) My endgoal is that I can essentially switch between two screens one showing the bubbles and one the bouncy balls and when I click on the button it goes on. (for example start is bubbles. I click -> Bounce, click again -> back to Bubbles and so on)

Convert vertical scrolling Website to a functional horizontal scrolling website

The current code is a responsive vertical webpage with a vertical navigation
I want to convert It to a responsive horizontal webpage with left-right arrow control.
Instances
When scrolling up or down with the mouse, the website should go left when scrolling up and right when scrolling down.
When right or left arrow keys are pressed the website should scroll right or left depending on the key pressed.
My HTML code
<body>
<div class="section" id="home" data-label="Home">Home</div>
<div class="section" id="about" data-label="About Me">About</div>
<div class="section" id="contact" data-label="Say Hi">Contact</div>
<script>
function activateNavigation() {
const sections = document.querySelectorAll(".section");
const navContainer = document.createElement("nav");
const navItems = Array.from(sections).map((section) => {
return `
<div class="nav-item" data-for-section="${section.id}">
<span class="nav-label">${section.dataset.label}</span>
</div>
`;
});
navContainer.classList.add("nav");
navContainer.innerHTML = navItems.join("");
const observer = new IntersectionObserver(
(entries) => {
document.querySelectorAll(".nav-link").forEach((navLink) => {
navLink.classList.remove("nav-link-selected");
});
const visibleSection = entries.filter((entry) => entry.isIntersecting)[0];
document
.querySelector(
`.nav-item[data-for-section="${visibleSection.target.id}"] .nav-link`
)
.classList.add("nav-link-selected");
},
{ threshold: 0.5 }
);
sections.forEach((section) => observer.observe(section));
document.body.appendChild(navContainer);
}
activateNavigation();
</script>
</body>
My CSS
.section{
height: 100vh;
.nav{
--nav-gap : 15px;
padding: var(--nav-gap);
position: fixed;
right: 0;
top:50%;
transform: translateY(-50%);
}
.nav-item{
align-items: center;
display: flex;
flex-direction: row-reverse;
margin-bottom: var(--nav-gap);
}
.nav-link:hover ~ .nav-label{
opacity: 1;
}
.nav-label{
color: black;
font-weight: bold;
opacity: 0;
transition: opacity 0.1s;
}
.nav-link{
background: rgba(0,0,0,0.5);
border-radius: 50%;
height: var(--nav-gap);
margin-left: var(--nav-gap);
width: var(--nav-gap);
transition: transform 0.3s;
}
.nav-link-selected{
background: #000000;
transform: scale(1.4);
}
The idea is to make a container div which will contain all the sections, and then make it display flex to handle sections as rows not columns.
Make sure to that the width and height of the container match the body's.
and then disable overflow-x on the container.
Now all we have to do is to get the current scroll Y position and transform it into a X one.
Since the browser does the job for us, we don't really need to calculate anything... just get paste the same Y position as an X one and it will work like a charm.
There is the script and explanations... All you have to do next is to challenge yourself to get the proper height of the website and use it as min-height for the body
A quick hint on your challenge: The height you'll need could be equal to the width of your container.
Good luck!
Example with your layout: jsfiddle link
const container = document.querySelector(".container")
window.addEventListener("scroll", horizontalScroll)
window.addEventListener("keydown", horizontalScroll)
function horizontalScroll(e, keyboadScrollingSpeed=30) {
let y = window.scrollY || window.pageYOffset
if( e.type == "keydown" ) {
if( (e.key == 'ArrowLeft' || e.key == 'ArrowRight') ) {
const direction = e.key == 'ArrowLeft' ? -1 : 1
y += keyboadScrollingSpeed * direction
window.scrollTo({
top: y
})
}
else e.preventDefault()
}
container.scrollTo({
left: y,
})
}
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
min-height: 300vh;
}
.container {
background-color: yellow;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
position: sticky;
top: 0;
left: 0;
width: 100vw;
overflow-x: hidden;
}
.section {
display: block;
flex: 1;
max-width: 100vw;
min-width: 100vw;
height: 100vh;
background-color: green;
}
<div class="container">
<div class="section">
<div class="content">
<h1>Section 1</h1>
<p>This is some text</p>
</div>
</div>
<div class="section">
<div class="content">
<h1>Section 2</h1>
<p>This is some text</p>
</div>
</div>
</div>

3 column fluid layout with expanding middle div based on panel positions

Apologies for the not-super-descriptive title, I'm having a hard time verbalizing the problem, so let me try again in more detail.
I am using flexbox to create a 3 column layout, where I'm looking to have the middle column expand when either (or both) of the left / right columns (menu panels) are collapsed.
Here's a screen shot of the problem I'm experiencing.
https://www.dropbox.com/s/d9mcg7q71hwogog/example.jpg?dl=0
When the panels are open (expanded) all three columns fill the width of the screen. This is good. However, when either of the two sidepanels is animated (collapsed), the center column does not expand to fill in the additional space. This is not good.
I'm not sure if it has to do with the fact that I'm using translateX to toggle the left / right panels and this is a just a theory, but perhaps the center column doesn't realize that the left / right columns have shifted position, so it doesn't know there's more space to fill?
Code to follow:
CSS:
<style>
body {
background-color: lightslategrey;
}
.red {
background-color: lightcoral;
}
.green {
background-color: lightgreen;
}
.grey {
background-color: lightslategray;
}
* {
box-sizing: border-box;
}
.ls-toggle, .rs-toggle {
//position: fixed;
z-index: 1;
cursor: pointer;
transition: 0.5s;
}
#left-sidebar, #right-sidebar {
width: 250px;
height: 100vh;
transition: 0.5s;
}
#left-sidebar.is-closed {
transform: translateX(-80%);
}
#right-sidebar.is-closed {
transform: translateX(80%);
}
.ls-top, .rs-top {
display: flex;
width:100%;
height:35px;
align-items: center;
justify-content: flex-end;
background-color: lightslategrey;
}
.rs-top {
justify-content: flex-start;
}
#mw-content {
width: 100%;
height: 100vh;
}
</style>
HTML:
<div class="d-flex">
<div id="left-sidebar" class="col- red">
<div class='ls-top grey'>
<button class="ls-toggle"><i class="fas fa-angle-left fa-2x"></i></button>
</div>
</div>
<div id="mw-content" class="col green">
<h3> Main Window Content.</h3>
</div>
<div id="right-sidebar" class="col- red">
<div class='rs-top grey'>
<button class="rs-toggle"><i class="fas fa-angle-right fa-2x"></i></button>
</div>
</div>
</div>
JS:
$(document).ready(function () {
var lsToggleBtn = document.querySelector('.ls-toggle');
var lsSidebar = document.querySelector('#left-sidebar');
var rsToggleBtn = document.querySelector('.rs-toggle');
var rsSidebar = document.querySelector('#right-sidebar');
lsToggleBtn.addEventListener('click', function () {
lsToggleBtn.classList.toggle('is-closed');
lsSidebar.classList.toggle('is-closed');
});
rsToggleBtn.addEventListener('click', function () {
rsToggleBtn.classList.toggle('is-closed');
rsSidebar.classList.toggle('is-closed');
});
});
Things I've tried:
Adding flex: 1 1 auto to main content container (mw-content).
Adding flex: 0 to l/r sidebar containers & flex: 1 1 auto to main content container
Setting width to 100% on main content container
Attempting to use translateX on main content container & setting new width when clicking left sidebar
Obviously none of those ideas worked, and I freely admit I'm not the greatest with flexbox, so I'm sure I'm just missing something basic, but I'd appreciate any suggestions you fine folks might have. :)
You need to play with "position: absolute" and add two more classList.toggle functions
$(document).ready(function () {
var mwContent = document.querySelector('#mw-content'); // !!
var lsToggleBtn = document.querySelector('.ls-toggle');
var lsSidebar = document.querySelector('#left-sidebar');
var rsToggleBtn = document.querySelector('.rs-toggle');
var rsSidebar = document.querySelector('#right-sidebar');
lsToggleBtn.addEventListener('click', function () {
lsToggleBtn.classList.toggle('is-closed');
lsSidebar.classList.toggle('is-closed');
mwContent.classList.toggle('ls-pos'); // here
});
rsToggleBtn.addEventListener('click', function () {
rsToggleBtn.classList.toggle('is-closed');
rsSidebar.classList.toggle('is-closed');
mwContent.classList.toggle('rs-pos'); // here
});
});
body {
background-color: lightslategrey;
margin: 0;
overflow: hidden;
}
.red {
background-color: lightcoral;
}
.green {
background-color: lightgreen;
}
.grey {
background-color: lightslategray;
}
* {
box-sizing: border-box;
}
.d-flex {
display: flex;
position: relative;
}
.ls-toggle,
.rs-toggle {
cursor: pointer;
transition: 0.5s;
}
#left-sidebar {
position: absolute;
left: 0;
width: 250px;
height: 100vh;
transition: 0.5s;
}
#right-sidebar {
position: absolute;
right: 0;
width: 250px;
height: 100vh;
transition: 0.5s;
}
#left-sidebar.is-closed {
transform: translateX(-80%);
}
#right-sidebar.is-closed {
transform: translateX(80%);
}
.ls-top,
.rs-top {
display: flex;
width: 100%;
height: 35px;
align-items: center;
justify-content: flex-end;
background-color: lightslategrey;
}
.rs-top {
justify-content: flex-start;
}
#mw-content {
position: absolute;
right: 250px;
left: 250px;
height: 100vh;
transition: 0.5s;
}
.ls-pos {
left: 50px !important;
transition: 0.5s;
}
.rs-pos {
right: 50px !important;
transition: 0.5s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="d-flex">
<div id="left-sidebar" class="col red">
<div class='ls-top grey'>
<button class="ls-toggle"><i class="fas fa-angle-left fa-2x"></i></button>
</div>
</div>
<div id="mw-content" class="col green">
<h3> Main Window Content.</h3>
</div>
<div id="right-sidebar" class="col red">
<div class='rs-top grey'>
<button class="rs-toggle"><i class="fas fa-angle-right fa-2x"></i></button>
</div>
</div>
</div>
Here is an example of how you can achieve this using vanilla javascript.
Two functions, one to track the collapsing of section elements and resize the middle section. It checks click and adds the elements (sections) id to an array, a conditional checks the array and if the array includes the two outer sections id, we resize the middle section using style.transform => scale(). The second function resets the collapsible elements.
More info found in code snippit.
let clicked = [];
const middle = document.querySelector('#sect2')
let section = document.querySelectorAll('.sect')
let reset = document.querySelector('#reset')
// function
const resizeOnCollapse = (els) => {
// loop over the three sections
els.forEach(sect => {
// do not allow the middle section to be collapsed
if (sect.id !== 'sect2') {
// eventlistener to check a click on the outer sections
sect.addEventListener('click', (e) => {
// push the clicked elements into an array
clicked.push(e.target.id)
// set the event targets width to 0
// essentially collapsing it from view
e.target.style.width = '0px';
// conditional that checks if the array
// includes the two outer sections
if (clicked.includes('sect1') && clicked.includes('sect3')) {
// resize the middle section using
// transform: scale()
middle.style.transform = 'scale(1.2)'
// transition animation on the transform
middle.style.transition = 'transform .5s ease-out'
}
})
}
})
}
// function to reset the elements and reset the array
const resetEls = (el, section) => {
// eventlistener on click
el.addEventListener('click', () => {
// reset the array
clicked = [];
// loop over the sections
section.forEach((sect) => {
// reset the width essentially uncollapsing the sections
sect.style.width = '30vw';
// reset the middle transform rule, scale() to 1
sect.style.transform = 'scale(1)';
})
})
}
// call the functions
resetEls(reset, section)
resizeOnCollapse(section)
#main {
display: flex;
justify-content: space-around;
}
.sect {
width: 30vw;
height: 80vh;
}
#sect1 {
background-color: red;
}
#sect2 {
background-color: blue;
}
#sect3 {
background-color: green;
}
<button id="reset">reset</button>
<div id="main">
<div id="sect1" class="sect">
</div>
<div id="sect2" class="sect">
</div>
<div id="sect3" class="sect">
</div>
</div>

Categories

Resources