jQuery detect div location on scroll - javascript

I'm new to jQuery and am struggling with making the jQuery detect the location of div .stage-O so that when scrolling down the .header doesn't disappear until the bottom of that .stage-O hits the top of the page?
jQuery(document).ready(function () {
var lastFixPos = 0,
threshold = 100, //sensitivity on scrolling
$header = $('.header');
$(window).on('scroll', function() {
var st = $(this).scrollTop();
var diff = Math.abs($(window).scrollTop() - lastFixPos);
if (diff > threshold || st < 100) {
if (st < lastFixPos) {
// scroll up
$header.removeClass('hide').addClass('color headerBGchange headerLIchange');
}
lastFixPos = st;
} else if (st > lastFixPos) {
//scroll down
$header.addClass('hide').removeClass('color');
}
});
$(window).scroll(function(e) {
var sw = $('.header'),
pg = $('.stage-2'),
diff = pg[0].offsetbottom - window.pageYOffset;
sw.css('background-color', diff < 100 ? 'white' : '');
});
});
.header {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
height: 80px;
-webkit-transition: top 250ms ease;
transition: top 250ms ease;
position: fixed;
width: 100%;
top: 0;
background-color: transparent;
overflow: hidden;
}
.header ul {
margin: 20px;
padding: 0;
}
.header ul li {
display: inline-block;
margin-right: 20px;
color: green;
}
.header ul li:last-child {
margin-right: 0;
}
.hide {
top: -80px;
}
.headerBGchange{
Background: white;
}
.headerLIchange{
color: Blue;
}
.stage {
color: #fff;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-webkit-justify-content: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-webkit-align-items: center;
-ms-flex-align: center;
align-items: center;
height: 100vh;
background-color: bisque;
font-size: 48px;
}
.stage-0 {
background: black;
}
.stage-1 {
background: #030202;
}
.stage-2 {
background: #060505;
}
.stage-3 {
background: #080707;
}
.stage-4 {
background: #0b0a09;
}
.stage-5 {
background: #0e0c0b;
}
.stage-6 {
background: #110e0e;
}
.stage-7 {
background: #141110;
}
.stage-8 {
background: #161312;
}
.stage-9 {
background: #191515;
}
.stage-10 {
background: #1c1817;
}
.stage-11 {
background: #1f1a19;
}
.stage-12 {
background: #221d1c;
}
.stage-13 {
background: #241f1e;
}
.stage-14 {
background: #272120;
}
.stage-15 {
background: #2a2422;
}
.stage-16 {
background: #2d2625;
}
.stage-17 {
background: #302827;
}
.stage-18 {
background: #322b29;
}
.stage-19 {
background: #352d2c;
}
.stage-20 {
background: #38302e;
}
.stage-21 {
background: #3b3230;
}
.stage-22 {
background: #3e3432;
}
.stage-23 {
background: #413735;
}
.stage-24 {
background: #433937;
}
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<div class="header">
<ul>
<li>Home</li>
<li>About</li>
<li>Contact</li>
</ul>
</div>
<div class="stage stage-0">1</div>
<div class="stage stage-2">3</div>
<div class="stage stage-4">5</div>
<div class="stage stage-6">7</div>
<div class="stage stage-8">9</div>
<div class="stage stage-10">11</div>
<div class="stage stage-12">13</div>
<div class="stage stage-14">15</div>
<div class="stage stage-16">17</div>
<div class="stage stage-18">19</div>
<div class="stage stage-20">21</div>
<div class="stage stage-22">23</div>

Is this what you are looking for? I changed the code quite a bit because it seemed like yours was a little overly complicated. Not sure why you were attaching two scroll events. Also I just added a red border to the stage class so you could clearly see when we were passing the bottom of it.
Fiddle: http://jsfiddle.net/AtheistP3ace/4hs7n0Lq/
var lastScrollTop = 0;
$(window).on('scroll', function() {
var header = $('.header');
var stage0 = $('.stage-0');
var scrollTop = $(window).scrollTop();
if (scrollTop > lastScrollTop) {
// down scroll
if (scrollTop > stage0.offset().top + stage0.height()) {
header.addClass('hide').removeClass('color');
}
} else {
// up scroll
if (scrollTop <= stage0.offset().top + stage0.height()) {
header.removeClass('color headerBGchange headerLIchange');
} else {
header.removeClass('hide').addClass('color headerBGchange headerLIchange');
}
}
lastScrollTop = scrollTop;
});
It simply tracks the lastScroll to determine if we are going up or down. If we are going down lets check if we have passed the stage0 div by getting its offset plus its height (the bottom of it). If we are scrolling up lets see if we are above the bottom of the stage0 div, if not we are scrolling up but have not reached it yet.
As to your question about the text color its not working because you set the color on the header which would cascade down but you also have this:
.header ul li {
display: inline-block;
margin-right: 20px;
color: green;
}
Which is a more specific selector so it overrides the higher one. So instead of
.headerLIchange {
color: Blue;
}
do
.header.headerLIchange ul li {
color: Blue;
}
Fiddle: http://jsfiddle.net/AtheistP3ace/4hs7n0Lq/1/

This might help you:
<body onload="document.getElementById('scrollBox').scrollTop = document.getElementById('scrollPosition').value;">
<input type="hidden" id="scrollPosition" />
<div id="scrollBox" style="overflow:scroll;height:100px;width:150px;" onscroll="javascript:document.getElementById('scrollPosition').value = this.scrollTop">
...content goes here...
...more content...
...link goes here...
</div>
</body>
Ref: http://www.quackit.com/html/codes/div_scroll_position.cfm

Related

Why does a bit of my background gradient show at certain screen sizes?

I can't figure out why I'm getting this little bit of green when the window is an odd number of pixels wide. I think it has something to do with sub-pixel rendering, but I'm just not sure where the green is coming from. It's just the 2nd div too which is weird.
I have some script that is animating the BG of this div. I'm sure this is part of the issue, but I can't figure out why it's only happening to my 2nd div.
I tried to manually set the width of this div, but I was hoping it would be responsive and scale with the window size.
let currentStage = 1
function performAction(selectedStage) {
currentStage = selectedStage
let stages = document.body.getElementsByClassName('stage-flow-item')
let stageLines = document.body.getElementsByClassName('stage-flow-line')
console.log("selectedStage: " + selectedStage)
for (let stage of stages) {
if (stage.id > currentStage) {
stage.classList.remove('completed')
stage.classList.add('active')
} else {
stage.classList.remove('active')
stage.classList.add('completed')
}
}
for (let stageLine of stageLines) {
if (stageLine.id > currentStage) {
stageLine.classList.remove('lineCompleted')
stageLine.classList.add('lineActive')
} else {
stageLine.classList.remove('lineActive')
stageLine.classList.add('lineCompleted')
}
}
}
.stage-flow-container {
display: flex;
justify-content: space-between;
align-items: center;
height: 70px;
padding: 0 30px;
}
.stage-flow-item {
width: 70px;
height: 70px;
min-width: 70px;
border-radius: 50%;
background-color: #ddd;
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
color: #fff;
cursor: pointer;
}
.stage-flow-item.active {
background-color: #ddd;
}
.stage-flow-item.completed {
background-color: #6ab04c;
}
.stage-flow-line {
width: calc(100vw);
height: 6px;
background-color: #ddd;
/* default color */
background: linear-gradient(to left, #ddd 50%, #6ab04c 50%) right;
position: relative;
background-size: 200%;
transition: .5s ease-out;
}
.stage-flow-line.lineCompleted {
background-position: left;
background-color: #6ab04c;
}
.stage-flow-line.lineActive {
background-position: right;
background-color: #ddd;
}
<div class="stage-flow-container">
<div id=1 class="stage-flow-item" onclick="performAction(1)">1</div>
<div id=1 class="stage-flow-line"></div>
<div id=2 class="stage-flow-item" onclick="performAction(2)">2</div>
<div id=2 class="stage-flow-line"></div>
<div id=3 class="stage-flow-item" onclick="performAction(3)">3</div>
</div>
I'm not sure if this is on the right track, but I'd eliminate the odd 100vw width on the connectors and instead make them flex. I'd then remove the 200% background size multiplier. By setting the gradient points to 100% the problem is gone. I really don't know if this covers your use case, though.
I converted from background gradient to a pseudo-element solution for the color transition. I think it's simpler. You'd probably have to use CSS animations (as opposed to simple transitions) to make it work otherwise. Of course, you could apply the same principle to the stage items as well, implementing a delay to crate a consistent animation across the item and the line.
Note that duplicated ID values are invalid in HTML. They must be unique. I've refactored to use data attributes instead and an event listener instead of inline JavaScript.
const stageEls = document.querySelectorAll('.stage-flow-item')
const lineEls = document.querySelectorAll('.stage-flow-line')
let currentStage = 1
stageEls.forEach(el => {
el.addEventListener('click', () => {
performAction(el.dataset.stage)
})
})
function performAction(selectedStage) {
currentStage = selectedStage
for (let el of stageEls) {
if (el.dataset.stage > currentStage) {
el.classList.remove('completed')
el.classList.add('active')
} else {
el.classList.remove('active')
el.classList.add('completed')
}
}
for (let el of lineEls) {
if (el.dataset.stage > currentStage) {
el.classList.remove('lineCompleted')
el.classList.add('lineActive')
} else {
el.classList.remove('lineActive')
el.classList.add('lineCompleted')
}
}
}
.stage-flow-container {
display: flex;
align-items: center;
height: 70px;
padding: 0 30px;
}
.stage-flow-item {
width: 70px;
height: 70px;
min-width: 70px;
border-radius: 50%;
background-color: #ddd;
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
color: #fff;
cursor: pointer;
}
.stage-flow-item.active {
background-color: #ddd;
}
.stage-flow-item.completed {
background-color: #6ab04c;
}
.stage-flow-line {
flex: 1;
height: 6px;
background: #ddd;
position: relative;
}
.stage-flow-line::after {
position: absolute;
content: '';
top: 0;
left: 0;
width: 0;
height: 100%;
background: #6ab04c;
transition: all 0.5s ease-out;
}
.stage-flow-line.lineCompleted::after {
width: 100%;
}
<div class="stage-flow-container">
<div data-stage=1 class="stage-flow-item">1</div>
<div data-stage=1 class="stage-flow-line"></div>
<div data-stage=2 class="stage-flow-item">2</div>
<div data-stage=2 class="stage-flow-line"></div>
<div data-stage=3 class="stage-flow-item">3</div>
</div>

Adding class dynamically based on which element is being scrolled past

I have a nav which contains letters and sections which are associated to letters in the nav.
When a user scrolls to a section, I want to addClass active to that letter. For example:
User scrolls to section with the id of a, the anchor with the data-letter with a will be active.
Currently, on scroll, all my letters in the nav become active and this is because it's always thinking it's on section A.
Demo:
$(function() {
$(window).scroll(function() {
// step 1: get id of section
var visible_section = $('section:visible'), id = visible_section.attr('id');
console.log(id);
// step 2: add class where id and data-letter match
$("nav a").removeClass("active");
$("nav a[data-letter='"+id+"']").addClass("active");
});
});
.nav {
background: grey;
padding: 30px 15px;
position: fixed;
top: 0;
width: 100%;
}
.nav a {
padding: 0 15px;
text-decoration: none;
}
.nav a:hover {
background: black;
color: white;
}
.nav a.active {
background: black;
color: white;
}
.sections {
margin-top: 100px;
}
section {
padding: 200px 0;
color: red;
display: flex;
align-items: center;
justify-content: center;
font-size: 50px;
}
section:nth-child(odd) {
background-color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<main>
<nav class="nav">
A
B
C
D
</nav>
<div class="sections">
<section id="a">A</section>
<section id="b">B</section>
<section id="c">C</section>
<section id="d">D</section>
</div>
</main>
FYI: https://stackoverflow.com/a/5354536/4571790
function isVisible(elm) {
var rect = elm.getBoundingClientRect();
var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
return !(rect.bottom < 0 || rect.top - viewHeight >= 0);
}
$(function() {
$(window).scroll(function() {
// step 1: get id of section
var visible_section = $('section:visible'), id="";
// this code will find which section is the first visible
$(".sections").find("section").each((i,a)=>id==""?(isVisible(a)?id=$(a).attr("id"):id):id);
$("#result").html(id +" is visible now");
//console.log(id);
// step 2: add class where id and data-letter match
$("nav a").removeClass("active");
$("nav a[data-letter='"+id+"']").addClass("active");
});
});
.nav {
background: grey;
padding: 30px 15px;
position: fixed;
top: 0;
width: 100%;
}
.nav a {
padding: 0 15px;
text-decoration: none;
}
.nav a:hover {
background: black;
color: white;
}
.nav a.active {
background: black;
color: white;
}
.sections {
margin-top: 100px;
}
section {
padding: 200px 0;
color: red;
display: flex;
align-items: center;
justify-content: center;
font-size: 50px;
}
section:nth-child(odd) {
background-color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js" integrity="sha512-894YE6QWD5I59HgZOGReFYm4dnWc1Qt5NtvYSaNcOP+u1T9qYdvdihz0PPSiiqn/+/3e7Jo4EaG7TubfWGUrMQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<main>
<nav class="nav">
A
B
C
D
<span id="result"></span>
</nav>
<div class="sections">
<section id="a">A</section>
<section id="b">B</section>
<section id="c">C</section>
<section id="d">D</section>
</div>
</main>
You should differentiate the nav tabs by setting their data-letter as you described to a, b, c and d:
<nav class="nav">
A
B
C
D
</nav>
All your nav become active not because it always thinks it's on a, but because all data-letter are set to a.
You can change ratio from 0.1 to 1:
$(function () {
let ratio = 0.6; // From 0.1 to 1
$(window).scroll(function () {
let sections = $('.sections section');
let scrollTop = $(window).scrollTop();
let screen_height = $(window).height();
$.each(sections, function () {
let top = $(this).offset().top;
let calc = (top - scrollTop) / screen_height;
if (calc >= (ratio * -1) && calc <= ratio) {
let id = $(this).attr('id');
$("nav a").removeClass("active");
$("nav a[data-letter='" + id + "']").addClass("active");
//console.log(`${id} is Active`);
}
});
});
});
.nav {
background: grey;
padding: 30px 15px;
position: fixed;
top: 0;
width: 100%;
}
.nav a {
padding: 0 15px;
text-decoration: none;
}
.nav a:hover {
background: black;
color: white;
}
.nav a.active {
background: black;
color: white;
}
.sections {
margin-top: 100px;
}
section {
padding: 200px 0;
color: red;
display: flex;
align-items: center;
justify-content: center;
font-size: 50px;
}
section:nth-child(odd) {
background-color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<main>
<nav class="nav">
A
B
C
D
</nav>
<div class="sections">
<section id="a">A</section>
<section id="b">B</section>
<section id="c">C</section>
<section id="d">D</section>
</div>
</main>
Your confusion seems to stem from the fact that you have an inaccurate understanding of what :visible selector in jQuery selects. From documentation: "Elements are considered visible if they consume space in the document. Visible elements have a width or height that is greater than zero." By this definition, all of the sections are "visible".
To achieve your use case you need to calculate coordinates of individual sections and compare them with scroll position in the document. The following appears to do what you are looking for (try it out here):
$(function () {
$(window).scroll(function () {
// determine element that is fully in the viewport
const fullyVisibleSection = Array.from(document.querySelectorAll('section')).find((section) => {
const topY = section.offsetTop;
const bottomY = topY + section.offsetHeight;
return (topY >= window.scrollY) && (bottomY <= (window.scrollY + window.innerHeight));
});
// only update classes if the section is fully visible
if (fullyVisibleSection) {
$("nav a").removeClass("active");
// add class where id and data-letter match
$("nav a[data-letter='" + fullyVisibleSection.id + "']").addClass("active");
}
});
});

Loop through elements by class name, click any element within the array, but only affect the element clicked?

I am working on a WordPress site and I have a snippet of html that iterates with repeating classes.
I am attempting to create a click function but only affect the element that is clicked. All in JavaScript.
As of right now my function is affecting all elements with the class name. Test code can be found at my CodePen or below.
I can accomplish this without nested loops as seen here. So my assumption is the problem lies within the second forEach loop. I would appreciate any light on the matter.
Thank you in advance.
/**
*Constructors
**/
const carousel = document.getElementsByClassName("carousel");
const btns = document.getElementsByClassName("btns");
/**
*Execute
**/
Array.from(btns).forEach((i) => {
i.addEventListener("click", (e) => {
Array.from(carousel).forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else if (i.classList.contains("slide-right")) {
n.scrollLeft += 20;
} else {
alert("ut oh");
}
});
});
});
/*
**Utilities
*/
/*containers*/
.feed-container {
position: absolute;
height: 200px;
width: 100%;
display: grid;
grid-template-columns: 1;
grid-template-rows: 1;
}
.carousel {
grid-row: 1;
grid-column: 1/5;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 1;
grid-gap: 15px;
align-self: center;
border: 1px solid #ccc;
overflow-x: scroll;
overflow-y: hidden;
}
/*div-buttons*/
div[class*="slide-"] {
/*opacity: 0;*/
position: sticky;
grid-row: 1;
z-index: 5;
place-self: center;
transition: 0.5s;
padding: 15px;
}
.slide-left {
grid-column: 1;
}
.slide-right {
grid-column: 4;
}
/*items*/
div[class*="item-"] {
grid-row: 1;
width: 400px;
height: 200px;
}
.item-1 {
background: blue;
}
.item-2 {
background: red;
}
.item-3 {
background: grey;
}
.item-4 {
background: yellow;
}
/*scrollbar*/
::-webkit-scrollbar {
display: none;
}
/*chevrons*/
[class*="chevron-"] {
box-sizing: border-box;
position: relative;
display: block;
transform: scale(var(--ggs, 1));
width: 22px;
height: 22px;
border: 2px solid transparent;
border-radius: 25px;
}
[class*="chevron-"]::after {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 40px;
height: 40px;
border-bottom: 8px solid;
border-left: 8px solid;
bottom: 0;
}
.chevron-left::after {
transform: rotate(45deg);
left: 15px;
}
.chevron-right::after {
transform: rotate(-135deg);
right: 15px;
}
/*
**Exceptions
*/
.btns:hover {
cursor: pointer;
}
.opaque {
opacity: 1 !important;
}
.show {
display: block;
}
<div id="wrapper" style="display:grid; grid-template-rows:repeat(2, auto); grid-gap: 100px;">
<div>
<h1>Header</h1>
<div class="feed-container">
<div class="carousel">
<div class="item-1"></div>
<div class="item-2"></div>
<div class="item-3"></div>
<div class="item-4"></div>
</div>
<div class="slide-left btns">
<div class="chevron-left"></div>
</div>
<div class="slide-right btns">
<div class="chevron-right"></div>
</div>
</div>
</div>
<br>
<div>
<h1>Header</h1>
<div class="feed-container">
<div class="carousel">
<div class="item-1"></div>
<div class="item-2"></div>
<div class="item-3"></div>
<div class="item-4"></div>
</div>
<div class="slide-left btns">
<div class="chevron-left"></div>
</div>
<div class="slide-right btns">
<div class="chevron-right"></div>
</div>
</div>
</div>
</div>
It's because you're getting all the elements with class name carousel and then looping through them with each click.
const carousel = document.getElementsByClassName("carousel");
Instead what you need to do is get the carousels only under the button's parent when you trigger the click event
eg something like this:
Array.from(btns).forEach((i) => {
i.addEventListener("click", (e) => {
const targetElement = e?.target || e?.srcElement;
const parent = targetElement.parentElement();
const carousel = Array.from(parent.getElementsByClassName("carousel"));
carousel.forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else if (i.classList.contains("slide-right")) {
n.scrollLeft += 20;
} else {
alert("ut oh");
}
});
});
});
I took a look at the following as recommend and it seems to do the trick.
I created a variable that calls the parentNode "forEach()" button clicked. Oppose to looping through each element.
Working example, codePen
const carousel = document.querySelectorAll(".carousel");
const btns = document.querySelectorAll(".btns");
btns.forEach((i) => {
i.addEventListener("click", () => {
var x = i.parentNode;
var y = Array.from(x.querySelectorAll(".carousel"));
y.forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else {
n.scrollLeft += 20;
}
});
});
});

Disabling inactive toggle in jQuery

I have the following functional code. However, I would like to know how I can disable toggle buttons. I always want to have one of my bottom navbar icons active and its respective content should be shown in the main section. If I click on the active navbar icon (the toggle) it wouldn't be deactivated.
Thanks in advance for your help!
$(document).ready(function() {
// only show menu-1
$('.menu-1').click(function() {
if ($('.menu-2, .menu-3').hasClass('active')) {
$('.menu-2, .menu-3').removeClass('active');
$('.content-2, .content-3').removeClass('active');
}
$('.menu-1').toggleClass('active');
$('.content-1').toggleClass('active');
});
// only show menu-2
$('.menu-2').click(function() {
if ($('.menu-1, .menu-3').hasClass('active')) {
$('.menu-1, .menu-3').removeClass('active');
$('.content-1, .content-3').removeClass('active');
}
$('.menu-2').toggleClass('active');
$('.content-2').toggleClass('active');
});
// only show menu-3
$('.menu-3').click(function() {
if ($('.menu-2, .menu-1').hasClass('active')) {
$('.menu-2, .menu-1').removeClass('active');
$('.content-2, .content-1').removeClass('active');
}
$('.menu-3').toggleClass('active');
$('.content-3').toggleClass('active');
});
});
.container {
margin: 0 auto;
background-color: #eee;
border: 1px solid lightgrey;
width: 20vw;
height: 90vh;
font-family: sans-serif;
position: relative;
}
header {
background-color: lightgreen;
padding: 5px;
text-align: center;
text-transform: uppercase;
}
.bottom-navbar {
position: absolute;
bottom: 0;
width: 100%;
padding: 6px 0;
overflow: hidden;
background-color: lightgreen;
border-top: 1px solid var(--color-grey-dark-3);
z-index: 50;
display: flex;
justify-content: space-between;
> a {
display: block;
color: green;
text-align: center;
text-decoration: none;
font-size: 20px;
padding: 0 10px;
&.active {
color: black;
}
}
}
.menu-1.active,
.menu-2.active,
.menu-3.active {
color: black;
}
.content-1,
.content-2,
.content-3 {
display: none;
}
.content-1.active,
.content-2.active,
.content-3.active {
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
<div class="container">
<header>My header</header>
<div class="main-content">
<div class="content-1">House content</div>
<div class="content-2">Map content</div>
<div class="content-3">Explore content</div>
<div class="bottom-navbar">
<i class="fa fa-home"></i>
<i class="fa fa-map"></i>
<i class="fa fa-search"></i>
</div>
</div>
If you find it easier, here's my CodePen: https://codepen.io/fergos2/pen/vYYaRzN
You can use this jQuery code. Anyone can enhance that.
$(document).ready(function() {
$('.bottom-navbar a').click(function(){
var cls = $(this).attr('class');
var lastchr = cls.substr(cls.length - 1);
$(this).siblings('a').removeClass('active');
$(this).addClass('active');
$("div[class^='content-'],div[class*=' content-']").removeClass('active');
$('.content-'+ lastchr).addClass('active');
})
});
Instead of toggleClass() you could use addClass():
https://codepen.io/vladanme/pen/LYYBrqJ
$(document).ready(function() {
// only show menu-1
$('.menu-1').click(function() {
if ($('.menu-2, .menu-3').hasClass('active')) {
$('.menu-2, .menu-3').removeClass('active');
$('.content-2, .content-3').removeClass('active');
}
$('.menu-1').addClass('active');
$('.content-1').addClass('active');
});
// only show menu-2
$('.menu-2').click(function() {
if ($('.menu-1, .menu-3').hasClass('active')) {
$('.menu-1, .menu-3').removeClass('active');
$('.content-1, .content-3').removeClass('active');
}
$('.menu-2').addClass('active');
$('.content-2').addClass('active');
});
// only show menu-3
$('.menu-3').click(function() {
if ($('.menu-2, .menu-1').hasClass('active')) {
$('.menu-2, .menu-1').removeClass('active');
$('.content-2, .content-1').removeClass('active');
}
$('.menu-3').addClass('active');
$('.content-3').addClass('active');
});
});
Use addClass() instead of toggleClass().
It looks like you have the code to clear the inactive buttons already. So you're only left with the button that you would like to maintain active.
[..]
$('.menu-1').addClass('active');
$('.content-1').addClass('active');
[..]
[..]
$('.menu-2').addClass('active');
$('.content-2').addClass('active');
[..]
[..]
$('.menu-3').addClass('active');
$('.content-3').addClass('active');
[..]

How to reorder divs using flex box?

I am trying to keep a seo friendly and semantic structure for my DOM, without repeating whole elements to display them in various positions.
My layout is based on display: flex items. I try to achieve the following:
Important things to know:
I do not want to show/hide divs based on the window width (to avoid unnecessary duplicates)
None of the divs has a known or fixed height
On desktops the divs should be vertical centered, while the right column builds a tag-team (behaves like one single div)
The layout needs to support at least IE11+
Is there a css only solution to achieve this?
If not, it would be easy to cut out the green div and paste its content into the pink one using javascript. But I do have concerns about the performance and "flickering" using this, although resizing the browser makes it more complicated. Do I make this needlessly complicated?
Here is fiddle showing a working solution but with javascript:
CODEPEN DEMO
In general, you can't do this with Flexbox alone, though there might be a compromise based on each given case.
With Flexbox alone, using fixed height, you can accomplish this
* {
box-sizing: border-box;
}
body, html {
margin: 0;
}
.flex {
width: 90%;
margin: 5vh auto;
height: 90vh;
background: rgba(0, 0, 0, 0.05);
display: flex;
flex-flow: column wrap;
}
.flex div {
flex: 1;
width: 50%;
}
.flex div:nth-child(2) {
order: -1;
}
.flex::before {
content: '';
height: 100%;
}
#media (max-width:768px) {
.flex div {
width: auto;
}
.flex::before {
display: none;
}
.flex div:nth-child(2) {
order: 0;
}
}
/* styling */
.flex-child {
color: white;
font-size: 2em;
font-weight: bold;
}
.flex-child:nth-child(1) {
background: #e6007e;
}
.flex-child:nth-child(2) {
background: #f4997c;
}
.flex-child:nth-child(3) {
background: #86c06b;
}
<div class="flex">
<div class="flex-child">
<div>Top/Right</div>
</div>
<div class="flex-child">
<div>Center/Left</div>
</div>
<div class="flex-child">
<div>Bottom/Right</div>
</div>
</div>
In this case, where no fixed height is allowed, you can combine Flexbox and float.
By set up it for mobile using Flexbox where you add the center item first in the markup and then, with order, move it between the top and bottom.
With a media query you then simply make the flex container a block element and use float to position the left to the left and the right to the right.
* {
box-sizing: border-box;
}
body, html {
margin: 0;
}
.flex {
max-width: 1024px;
width: 90%;
margin: 5vh auto;
height: 90vh;
background: rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
}
.flex-child {
color: white;
font-size: 2em;
font-weight: bold;
padding: 5%;
flex-basis: 33.333%;
display: flex;
align-items: center;
}
.flex-child:nth-child(1) {
background: #e6007e;
order: 1;
}
.flex-child:nth-child(2) {
background: #f4997c;
}
.flex-child:nth-child(3) {
background: #86c06b;
order: 2;
}
#media (min-width: 768px) {
.flex {
display: block;
}
.flex-child {
width: 50%;
}
.flex-child:nth-child(1) {
float: left;
height: 100%;
}
.flex-child:nth-child(2),
.flex-child:nth-child(3) {
float: right;
height: 50%;
}
}
<div class="flex">
<div class="flex-child">
<div>Center/Left</div>
</div>
<div class="flex-child">
<div>Top/Right</div>
</div>
<div class="flex-child">
<div>Bottom/Right</div>
</div>
</div>
Update
Here is another version combining Flexbox with position: absolute, which also vertically center the items in desktop mode
Updated, added a script to control so the absolute positioned element won't get bigger than the right items, and if so, adjust the flex containers height.
Note, the script is by no means optimized, it is only there to show how a fix in certain situations
(function() {
window.addEventListener("resize", resizeThrottler, false);
var fp = document.querySelector('.flex');
var fi = fp.querySelector('.flex-child:nth-child(1)');
var resizeTimeout;
function resizeThrottler() {
// ignore resize events as long as an actualResizeHandler execution is in the queue
if ( !resizeTimeout ) {
resizeTimeout = setTimeout(function() {
resizeTimeout = null;
actualResizeHandler();
// The actualResizeHandler will execute at a rate of 15fps
}, 66);
}
}
function actualResizeHandler() {
// handle the resize event
if (fp.offsetHeight <= fi.offsetHeight) {
fp.style.cssText = 'height: '+fi.offsetHeight+'px';
} else {
fp.style.cssText = 'height: auto';
}
}
window.addEventListener('load', function() {
actualResizeHandler();
})
}());
* {
box-sizing: border-box;
}
body, html {
margin: 0;
}
.flex {
position: relative;
max-width: 1024px;
width: 90%;
margin: 5vh auto;
height: 90vh;
background: rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
}
.flex-child {
color: white;
font-size: 2em;
font-weight: bold;
padding: 5%;
}
.flex-child:nth-child(1) {
order: 1;
}
.flex-child:nth-child(3) {
order: 2;
}
.flex-child:nth-child(1) div {
background: #e6007e;
}
.flex-child:nth-child(2) div {
background: #f4997c;
}
.flex-child:nth-child(3) div {
background: #86c06b;
}
#media (min-width: 768px) {
.flex {
justify-content: center;
}
.flex-child {
width: 50%;
}
.flex-child:nth-child(1) {
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.flex-child:nth-child(n+2) {
margin-left: 50%;
}
}
<div class="flex">
<div class="flex-child">
<div>Center/Left<br>with more<br>content<br>than any<br>of the<br>other items<br>other items<br>other items<br>other items<br>other items</div>
</div>
<div class="flex-child">
<div>Top/Right<br>with more<br>content</div>
</div>
<div class="flex-child">
<div>Bottom/Right<br>with more</div>
</div>
</div>
With script one can also reorder/move items between elements.
Stack snippet
You can also combine this with a media query, and use it to do the actual re-order of the elements
$( document ).ready(function() {
$(window).resize(function() {
if ($( window ).width() < 600 ) {
$(".one").insertBefore("#b");
} else {
$(".one").insertBefore(".two");
}
});
});
.outer, #flex, #flex2 {
display: flex;
flex-direction: column;
}
#a {
order: 4;
background: #ccc;
}
#b {
order: 1;
background: #aaa;
}
#c {
order: 3;
background: #d33;
}
.one {
order: 2;
background: #aaa;
}
.two {
order: 5;
background: #aaa;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="outer">
<div id="flex">
<div id="a">A</div>
<div id="b">B</div>
<div id="c">C</div>
</div>
<div id="flex2">
<div class="one">Show me 2nd</div>
<div class="two">Show me 5th</div>
</div>
</div>
Update 2 (answered at another question but later moved here)
If we talk about smaller items, like a header or smaller menus, one can do what many website platform providers like "squarespace", "weebly", "wordpress", etc does. Their templates holds different markup structures, where an item sometimes exist twice, one visible for desktop, another for mobile.
Also, being so small, there will be less to nothing when it comes to performance (and personally I don't see anymore issue with this than having duplicate CSS rules, one for each screen size, and happily do this instead of introducing script).
Fiddle demo
Stack snippet
.container {
display: flex;
}
.container > div {
width: 50%;
}
.container div:nth-child(-n+2) {
border: dashed;
padding: 10px;
}
.container > div:nth-child(1) {
display: none; /* hide outer "Flower" */
}
#media (max-width:768px) {
.container {
flex-direction: column;
}
.container div {
width: auto;
}
.container div:nth-child(1) {
display: block; /* show outer "Flower" */
}
.container div:nth-child(3) div:nth-child(1) {
display: none; /* hide inner "Flower" */
}
}
<div class="container">
<div>Flower</div>
<div>Tree</div>
<div>
<div>Flower</div>
<div>Bee</div>
</div>
</div>

Categories