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

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>

Related

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>

Horizontal scroll areas with buttons and gradients

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.

scrollTop or scrollLeft on horizontal website?

I have a website I am working on (here is a basic example), yesterday I got some help to implement active states on the radio-style button navigation, and I am now trying to link this up so that it also changes on page scroll/when in view as currently it's only onClick.
I roughly know how to achieve this as I've done something similar before, but then it occurred to me that because the page and scrollbar are rotated to accommodate the horizontal effect, I don't know if it would now be scrollTop or scrollLeft. I've never used scrollLeft before so I am unsure how to use it correctly. I am wondering if anyone has implemented something similar before and what the correct way would be? I've tried both and nothing seems to be working. This is what I'm roughly trying to achieve (but only one class active at a time).
I thought maybe using Waypoints could be another option, but again it's hard to find anything online which explains how this works when a site is rotated multiple times.
My JS knowledge is shaky (still learning!), I'm only trying to implement what I think would work so this is probably not even correct, any help understanding what I'm doing wrong would be appreciated!
Heres the latest thing I've tried.
// --- change span classes on click
const setIconState = (icon, state) => icon.className = state
? icon.className.replace('button-off', 'button-on')
: icon.className.replace('button-on', 'button-off')
const toggleIcon = element => {
const className = element.className;
element.className = className.indexOf('button-on') > -1
? setIconState(element, false)
: setIconState(element, true);
}
const setIconActiveState = (icon, state) => icon.className = state
? icon.className = `${icon.className} active`
: icon.className = icon.className.replace('active', '')
document.querySelectorAll('.bottomnav span.icon')
.forEach(icon => {
icon.onclick = (e) => {
const {
target: clickedSpan
} = e;
const siblings = [...clickedSpan.parentElement.parentElement.querySelectorAll('span.icon')]
.filter(sibling => sibling != clickedSpan);
siblings.forEach(icon => {
setIconState(icon, false);
setIconActiveState(icon, false);
});
setIconState(clickedSpan, true);
setIconActiveState(clickedSpan, true);
};
});
// --- change span classes on scroll test
function onScroll(event){
var scrollPos = $(document).scrollTop();
$('.bottomnav a').each(function () {
var currLink = $(this);
var refElement = $(currLink.attr("href"));
if (refElement.position().top <= scrollPos && refElement.position().top + refElement.height() > scrollPos) {
$('.bottomnav a span').removeClass("active");
currLink.addClass("active");
}
else{
currLink.removeClass("active");
}
});
}
* {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html,
body {
color: #000;
font-family: 'IBM Plex Sans', sans-serif;
font-weight: 100;
font-size: 7px;
text-rendering: optimizeLegibility;
overflow-x: hidden;
scroll-behavior: smooth;
}
.bottomnav {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
overflow: hidden;
position: fixed;
bottom: 0px;
width: 100%;
z-index: 2;
}
.bottomnav span {
float: left;
display: block;
color: #888;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 26px;
}
.bottomnav span:hover {
color: #fac123;
}
.bottomnav span.active {
color: #fac123;
}
#container {
overflow-y: scroll;
overflow-x: hidden;
transform: rotate(270deg) translateX(-100vh);
transform-origin: top left;
position: absolute;
width: 100vh;
height: 100vw;
white-space: nowrap;
scroll-snap-type: y mandatory;
}
#container .card {
width: 100vw;
height: 100vh;
display: inline-flex;
position: relative;
scroll-snap-align: start;
}
#player {
transform: rotate(90deg) translateY(-100vh);
transform-origin: top left;
font-size: 0;
width: 100vh;
height: 100vh;
display: flex;
/* position: absolute;*/
}
#player section > object {
width: 100vw;
overflow-x: hidden;
}
section object > div {
white-space: normal;
}
.container::-webkit-scrollbar {
display: none;
}
section {
padding: 5%;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
position: relative;
transition: .5s ease;
}
.cardwhite {
color: white;
background-color: black;
}
.cardblack {
color: black;
background-color: white;
}
h2 {
font-size: 40px;
font-weight: 700;
font-family: 'IBM Plex Serif', sans-serif;
}
p {
font-size: 10px;
margin-bottom: 15px;
font-weight: 100;
font-family: 'IBM Plex Sans', sans-serif;
}
<link href="https://unpkg.com/ionicons#4.5.5/dist/css/ionicons.min.css" rel="stylesheet">
<div class="bottomnav" id="bottomnav">
<span class="icon ion-ios-radio-button-on active"></span>
<span class="icon ion-ios-radio-button-off"></span>
<span class="icon ion-ios-radio-button-off"></span>
</div>
<div class="container" id="container">
<div id="player">
<section class="card cardwhite" id="1">
<object>
<h2>Section 1</h2>
<p>Description</p>
</object>
</section>
<section class="card cardblack" id="2">
<object>
<h2>Section 2</h2>
<p>Description</p>
</object>
</section>
<section class="card cardwhite" id="3">
<object>
<h2>Section 3</h2>
<p>Description</p>
</object>
</section>
</div>
</div>
For horizontal scrolling I would go the following route, simplifying your HTML and whaty you listen for. Since touch devices can easily just swipe to scroll, all you need to do is make it accessible for people with scroll wheels. You could also add an animation, but it makes this snippet too long.
const main = document.querySelector( 'main' );
const nav = document.querySelector( 'nav' );
let scrollend;
function onwheel(){
/* When using the scrollwheel, translate Y direction scrolls to X direction. This way scrollwheel users get the benefit of scrolling down to go right, while touch and other users get default behaviour. */
event.preventDefault();
event.stopImmediatePropagation();
main.scrollLeft += event.wheelDeltaY;
}
function onscroll(){
/* When scrolling, find the nearest element to the center of the screen. Then find the link in the nav that links to it and activate it while deactivating all others. */
const current = Array.from( main.children ).find(child => {
return child.offsetLeft >= main.scrollLeft - innerWidth / 2;
});
const link = Array.from( nav.children ).reduce((find, child) => {
child.classList.remove( 'selected' );
return find || (child.href.indexOf( current.id ) >= 0 ? child : find);
}, false);
if( link ) link.classList.add( 'selected' );
clearTimeout( scrollend );
scrollend = setTimeout( onscrollend, 100 );
}
function onscrollend(){
/* After scrolling ends, snap the appropriate element. This could be done with an animation. */
clearTimeout( scrollend );
const current = Array.from( main.children ).find(child => {
return child.offsetLeft >= main.scrollLeft - innerWidth / 2;
});
main.scrollLeft = current.offsetLeft;
}
/* Bind and initial call */
main.addEventListener( 'wheel', onwheel );
main.addEventListener( 'scroll', onscroll );
onscroll();
html,
body,
main {
height: 100%;
}
body {
padding: 0;
margin: 0;
}
main {
display: flex;
overflow: auto;
width: 100%;
height: 100%;
scroll-snap-type: x proximity;
}
main section {
width: 100%;
height: 100%;
flex: 0 0 100%;
}
nav {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}
nav a {
width: 1em;
height: 1em;
margin: 1em;
display: block;
overflow: hidden;
color: transparent;
border: 1px solid black;
border-radius: 50%;
}
nav a.selected {
background: black;
}
.bland { background: gray; }
.dark { background: darkgray; color: white; }
.bright { background: yellow; }
<nav>
Section 1
Section 2
Section 3
</nav>
<main>
<section class="bright" id="section-1">
<h2>Section 1</h2>
</section>
<section class="dark" id="section-2">
<h2>Section 2</h2>
</section>
<section class="bland" id="section-3">
<h2>Section 3</h2>
</section>
</main>
As mentioned, I would also prefer a design that does not flip the X and Y axis.
Doing so might bite us in the future, when we try to include non-trivial content on our pages.
Also if we don't do that axis flip, we have no need at all to do positional calculations.
So both the HTML structure and the CSS will be simpler.
AFAIK, it's not possible to do the scrolling purely in non-hacky CSS.
/**
* Change icon state on click.
*/
const icons = Array.from( document.querySelectorAll( '.icon' ));
const toggleIcon = icon => {
icon.classList.toggle( 'ion-ios-radio-button-on' );
icon.classList.toggle( 'ion-ios-radio-button-off' );
};
const clickIcon = event => {
// toggle previous active state
toggleIcon( document.querySelector( 'i.ion-ios-radio-button-on' ));
// toggle own state
toggleIcon( event.target );
};
icons.forEach( icon => icon.addEventListener( 'click', clickIcon ));
/**
* Scroll horizontally on scroll wheel.
* The combination of "scroll-behavior: smooth;" and the "<a href=#>" anchor links,
* can be reused to do and endless snapping cycle on wheel event.
*/
let scroll_state = 0;
window.addEventListener( 'wheel', event => {
window.requestAnimationFrame(() => {
// cast to -1 or +1
const offset = event.deltaY / Math.abs( event.deltaY );
scroll_state += offset;
// Arrays are zero-based.
// So if the length matches our state, restart over from the first page.
if ( scroll_state === icons.length ) scroll_state = 0;
else if ( scroll_state < 0 ) scroll_state = icons.length - 1;
// scrolll_state will now always contain the next icon to click.
icons[ scroll_state ].click();
});
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
}
body {
overflow-x: scroll;
width: 100%;
}
main {
display: block;
height: 90vh;
width: 300vw;
}
nav {
background-color: orange;
display: block;
height: 10vh;
position: fixed;
width: 100%;
}
a {
text-decoration: none;
}
.page {
display: inline-block;
float: left;
height: 100%;
padding: 50px;
width: 100vw;
}
<link href="https://unpkg.com/ionicons#4.5.5/dist/css/ionicons.min.css" rel="stylesheet">
<body>
<main>
<section class="page" id="myapp_first">
<h1>First</h1>
<p>Lorem Ipsum</p>
</section>
<section class="page" id="myapp_second">
<h1>Second</h1>
<p>Lorem Ipsum</p>
</section>
<section class="page" id="myapp_third">
<h1>Third</h1>
<p>Lorem Ipsum</p>
</section>
</main>
<nav id="myapp_navigation">
<a href="#myapp_first">
<i class="icon ion-ios-radio-button-on active"></i>
</a>
<a href="#myapp_second">
<i class="icon ion-ios-radio-button-off"></i>
</a>
<a href="#myapp_third">
<i class="icon ion-ios-radio-button-off"></i>
</a>
</nav>
</body>
By leveraging the click event of the icons, we get the icons changing class and the transition for free. Adding more pages now just becomes adding the correct HTML and updating the width of the <main> element.
A last thing I would personally add, is a debounce function around the wheel event, so we don't try to scroll faster than we can render.
Without debouncing, we might want to merge the functions so we can include the class changing inside the animationFrame for hopefully less yanky visuals, but that would complicate the click events again, so i'd prefer debouncing the wheel handler.
/**
* Change icon state on click.
*/
const icons = Array.from( document.querySelectorAll( '.icon' ));
const toggleIcon = icon => {
icon.classList.toggle( 'ion-ios-radio-button-on' );
icon.classList.toggle( 'ion-ios-radio-button-off' );
icon.classList.toggle( 'active' );
};
const clickIcon = event => {
// toggle previous active state
toggleIcon( document.querySelector( '.ion-ios-radio-button-on' ));// toggle own state
toggleIcon( event.target );
};
icons.forEach( icon => icon.addEventListener( 'click', clickIcon ));
/**
* Scroll horizontally on scroll wheel.
* The combination of "scroll-behavior: smooth;" and the "<a href=#>" anchor links,
* can be reused to do and endless snapping cycle on wheel event.
*/
let scroll_state = 0;
window.addEventListener( 'wheel', event => {
// ANimation frame to smooth out the transition.
window.requestAnimationFrame(() => {
// cast to -1 or +1
const offset = event.deltaY / Math.abs( event.deltaY );
scroll_state += offset;
// Arrays are zero-based.
// So if the length matches our state, restart over from the first page.
if ( scroll_state === icons.length ) scroll_state = 0;
else if ( scroll_state < 0 ) scroll_state = icons.length - 1;
// scrolll_state will now always contain the next icon to click.
icons[ scroll_state ].click();
});
});
* {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
html,
body {
color: #000;
font-family: 'IBM Plex Sans', sans-serif;
font-weight: 100; /* EDIT: font-weight: 100 basically equals no font weight at all */
font-size: 7px; /* EDIT: Why so small ? */
text-rendering: optimizeLegibility;
overflow-x: scroll;
overflow-y: hidden;
scroll-behavior: smooth;
height: 100vh; /* EDIT: add height, so we can scale off this */
width: 100vw; /* EDIT: add width, so we can scale off this */
}
.bottomnav {
display: flex;
justify-content: center;
align-items: center;
flex-wrap: wrap;
/*
overflow: hidden;
*/
position: fixed;
/*bottom: 0px; EDIT: not needed after we place the nav at the bottom */
height: 15vh; /* EDIT: .bottomnav height + #container height = 100vh */
width: 100%;
z-index: 2;
background-color: black;
}
.bottomnav span {
/*float: left; /* why float when flex lets us position exactly? */
display: block;
color: #888;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-size: 26px;
}
.bottomnav span:hover {
color: #fac123;
}
.bottomnav span.active {
color: #fac123;
}
#container {
/*
overflow-y: scroll;
overflow-x: hidden;
transform: rotate(270deg) translateX(-100vh);
transform-origin: top left;
position: absolute;
*/
width: 300vw; /* EDIT: 300vw, 100 per page of 100vw */
height: 85vh; /* EDIT: .bottomnav height + #container height = 100vh */
/*scroll-snap-type: y mandatory; EDIT: only needed if we use snappoints */
}
/* EDIT: .card and section are the same elements, merged rule "container" here */
.card {
width: 100vw; /* EDIT: 100vw for each page of 100vw width */
height: 100%; /* EDIT: 100% so it scales with the container, not the screen */
display: inline-block; /* EDIT: block level, since we do not need to flex these */
float: left; /* EDIT: float left so our pages leave no space between them so 300vw = 100+100+100 . THis can be done with flexbox or grid as well, but is more complicated than needed */
/*position: relative; EDIT: not needed */
/* scroll-snap-align: start; EDIT: only needed if we use snappoints */
padding: 50px;
/* EDIT:
justify-content: center;
align-items: center;
flex-wrap: wrap;
position: relative;
*/
/* transition: .5s ease; EDIT: I would think that "scroll-behavior: smooth;" already does this */
}
/* EDIT: Since there's no use for the extra wrapper element, positioning it absolute + flex only harms us instead of helping
#player {
transform: rotate(90deg) translateY(-100vh);
transform-origin: top left;
font-size: 0;
width: 100vh;
height: 100vh;
display: flex;
position: absolute;
}
#player section > object {
width: 100vw;
overflow-x: hidden;
}
*/
/* EDIT: I don't see any <div>s inside the objects
section object > div {
white-space: normal;
}
*/
/* EDIT: ? Attempt to remove vertical scroll? Not needed
.container::-webkit-scrollbar {
display: none;
}
*/
.cardwhite {
color: white;
background-color: black;
}
.cardblack {
color: black;
background-color: white;
}
h2 {
font-size: 40px;
font-weight: 700;
font-family: 'IBM Plex Serif', sans-serif;
}
p {
font-size: 10px;
margin-bottom: 15px;
font-weight: 100;
font-family: 'IBM Plex Sans', sans-serif;
}
<link href="https://unpkg.com/ionicons#4.5.5/dist/css/ionicons.min.css" rel="stylesheet">
<div id="container">
<!-- the extra player <div> is useless since the cards fully overlap it.
so it can be removed -->
<section class="card cardwhite" id="1">
<object>
<h2>Section 1</h2>
<p>Description</p>
</object>
</section>
<section class="card cardblack" id="2">
<object>
<h2>Section 2</h2>
<p>Description</p>
</object>
</section>
<section class="card cardwhite" id="3">
<object>
<h2>Section 3</h2>
<p>Description</p>
</object>
</section>
</div>
<!-- EDIT: Put the nav at the bottom so we do not have position issues -->
<div class="bottomnav" id="bottomnav">
<a href="#1">
<span class="icon ion-ios-radio-button-on active"></span>
</a>
<a href="#2">
<span class="icon ion-ios-radio-button-off"></span>
</a>
<a href="#3">
<span class="icon ion-ios-radio-button-off"></span>
</a>
</div>

How to make a div go over another div when clicking on it?

I have these 2 divs and when I click on div 1 I want it to go over the second div, and if I click on Div 1 again I want it to go back to its original position (I want Div 1 to increase its width so it goes over the second Div). Here is my code where I have my 2 divs next to each other. Can anyone point me in the right direction on how to accomplish this? Thanks a lot in advance!
NOTE:
- No jQuery please. I'm trying to accomplish this with javascript and css.
#parent {
display: flex;
}
#narrow {
width: 200px;
background: lightblue;
}
#wide {
flex: 1;
background: lightgreen;
}
<div id="parent">
<div id="wide">Div 1</div>
<div id="narrow">Div 2</div>
</div>
If you're willing to ditch flex, you can use a combination of float , postion:absolute and transition so that the main div "slides over" the other div
document.querySelector("#wide").onclick = toggleWidth;
function toggleWidth() {
this.classList.toggle("active");
}
#parent {
position: relative;
}
#narrow {
width: 200px;
background: lightblue;
float: right;
}
#wide {
position: absolute;
background: lightgreen;
width: calc(100% - 200px);
transition: width 2s;
}
#wide.active {
width: 100%;
opacity: 0.9;
}
<div id="parent">
<div id="wide">Div 1</div>
<div id="narrow">Div 2</div>
</div>
Note: Changing the opacity is purely optional, I've only done it to further illustrate the "slide over" effect.
Try this
#parent {
display: flex;
}
#narrow {
width: 20vw;
position: absolute;
left: calc(80vw - 10px);
background: lightblue;
z-index: 1;
margin: 0;
}
#wide {
width: calc(80vw - 10px);
background: lightgreen;
transition: all 0.3s ease-out;
}
.wider {
width: 100vw!important;
z-index: 2;
}
<div id="parent">
<div id="wide" onclick="myFunction()">Div 1</div>
<div id="narrow">Div 2</div>
</div>
<script>
function myFunction() {
var element = document.getElementById("wide");
element.classList.toggle("wider");
}
</script>
You can try it using JavaScript.
First, you prepare your CSS:
#narrow {
width: 200px;
transition: 0.32s;
overflow: hidden;
}
#wide.fullwidth ~ #narrow {
width: 0;
opacity: 0;
}
Then, the JavaScript, like this:
document.querySelector("#wide").onclick = changeDivWidth;
var wideFull = false;
function changeDivWidth () {
if (!wideFull) {
this.classList.add("fullwidth");
wideFull = true;
return; // if variable wideFull is false, function stops here
}
wideFull = false;
this.classList.remove("fullwidth");
}
Shorter approach using toggle();
document.querySelector("#wide").onclick = changeDivWidth;
function changeDivWidth () {
this.classList.toggle("fullwidth");
}
Are you looking for something like this : JSFiddle ?
JavaScript (Pure) :
function HideDivOne(){
var wide = document.getElementById("wide");
var narrow = document.getElementById("narrow");
if (wide.style.width == "70%"){
wide.style.width = "100%";
narrow.style.width = "0%";
narrow.style.opacity = "0";
}
else{
wide.style.width = "70%";
narrow.style.width = "30%";
narrow.style.opacity = "1";
}
}
CSS
#parent {
display: flex;
}
#narrow {
width: 30%;
background: lightblue;
height: 20px;
transition: 0.2s;
}
#wide {
width: 70%;
flex: 1;
background: lightgreen;
height: 20px;
transition: 0.2s;
}
HTML
<div id="parent">
<div id="wide" onclick="HideDivOne()">Div1</div>
<div id="narrow" onclick="HideDivTwo()">Div2</div>
</div>
You can change the z-index of the divs based on your desired effect. My suggestion is using jQuery. On click on div 1 add a class to the div that modify the zindex, that is, if the class is not already added, if so, remove it.

How do I check whether the right/left edge of an element is overlapping the side of it's container?

I'm trying to display a right / left navigation arrow within a container (the arrows replace the existence of a scrollbar) when the corresponding edge of the content overlaps the container's sides.
Also, when the content is scrolled all the way to the end and can't scroll any further, the arrow should disappear.
My problem is, I'm confused as to how I write the function to check whether the element's contents are overlapping one edge or the other to hide one arrow or the other.
I started writing logic like this:
function setArrows(elem){
if (elem.scrollLeft() > 0) { //scroll position is greater than zero
// show left arrow
}
if () { //scroll position is less than zero
//show right arrow
}
}
but that doesn't seem to be the right logic. It sounded simpler in my head before I went to actually write the function.
How do I check whether the right/left edge of an element is overlapping the side of it's container?
Here's a Stack Snippet:
$('#wrapper').scroll(function(){
//check edges
});
div {
padding: 0px;
margin: 0px;
}
#wrapper {
width: 500px;
height: 100px;
background-color: blue;
overflow-x: scroll;
overflow-y:hidden;
}
#content {
width: 1000px;
height: 100px;
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div id="wrapper">
<div id="content">
</div>
</div>
You need to check if the content width minus the scrollLeft is greater than the wrapper width. If it is show the right scroller..
Something like this
$(function() {
var content = $('#content'),
arrows = $('.arrow'),
wrapper = $('#wrapper').scroll(function() {
//check edges
// handle left arrow
if (this.scrollLeft > 0) {
arrows.filter('.left').addClass('visible');
} else {
arrows.filter('.left').removeClass('visible');
};
// handle right arrow
if (content.outerWidth() - this.scrollLeft > wrapper.width()) {
arrows.filter('.right').addClass('visible');
} else {
arrows.filter('.right').removeClass('visible');
};
});
arrows.on('click', function() {
if ($(this).is('.left')) {
wrapper[0].scrollLeft -= 100;
} else {
wrapper[0].scrollLeft += 100;
}
return false;
});
// initialize
wrapper.trigger('scroll');
});
div {
padding: 0px;
margin: 0px;
}
#wrapper {
width: 500px;
height: 100px;
background-color: blue;
overflow-x: hidden;
overflow-y: hidden;
position: relative;
}
#content {
width: 1000px;
height: 100px;
background: url('http://lorempixel.com/1000/100/abstract/2') 0 0 no-repeat;
}
#full-container {
position: relative;
display: inline-block;
}
.arrow {
position: absolute;
top: 0;
bottom: 0;
width: 40px;
background-color: black;
display: none;
z-index: 100;
cursor: pointer;
color: #fff;
text-align: center;
line-height: 100px;
}
.arrow.visible {
display: block;
}
.arrow.left {
left: 0
}
.arrow.right {
right: 0
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div id="full-container">
<div class="arrow left"><</div>
<div id="wrapper">
<div id="content"></div>
</div>
<div class="arrow right">></div>
</div>

Categories

Resources