I have a single HTML page with several sections to which the user can navigate by clicking the corresponding link at the top. That link will get highlighted when the user navigates via that link.
Now I would like that when the user scrolls up or down manually, and a different section comes into view, that the corresponding link in the top menu will get the highlight.
My attempt is in the scroll event handler, but I am facing an issue with finding the HTML section anchor id that corresponds to the current scroll position.
Note that I am required to produce the li dynamically -- so that cannot change: the ul must be empty as per requirements of my project so all list items and hyperlinks must be generated in JavaScript (as it is currently done).
Here is my page. My problem is in the scroll event handler:
/**
*
* Manipulating the DOM exercise.
* Exercise programmatically builds navigation,
* scrolls to anchors from navigation,
* and highlights section in viewport upon scrolling.
*
* Dependencies: None
*
* JS Version: ES2015/ES6
*
* JS Standard: ESlint
*
*/
/**
* Define Global Variables
*
*/
// All sections - Navigation Bar - Fragment & AllLinks
const navigationBar = document.getElementById('navbar__list');
var navigationBarContainer = document.getElementById("landing__container");
const fragment = document.createDocumentFragment();
const pagesections = document.querySelectorAll('section');
const navigationBarLi = document.querySelectorAll('nav .landing__container ul li');
// adding new classes to sections to match with anchor id for scroll purposes
//document.getElementById('li').className('section')
// for each single section
/*function retrieveElementsById(ids) {
var listIds = ids.split(" ");
var arrayresults = [], item;
for (var i = 0; i < listIds.length; i++){
item = document.getElementById(idList[i]);
if (item) {
results.push(item);
}
}
return(results);
}
allSectionsinSingFnct(querySelectorAll("section1 section2 section3 section4 section5 section6 section7"))
*/
//
function sectionsidattr() {
var sec1 = document.getElementById("section1");
var sec2 = document.getElementById("section2");
var sec3 = document.getElementById("section3");
var sec4 = document.getElementById("section4");
var sec5 = document.getElementById("section5");
var sec6 = document.getElementById("section6");
var sec7 = document.getElementById("section7");
}
const sectionsids = [
'section1', '#section1',
'section2', '#section2',
'section3', '#section3',
'section4', '#section4',
'section5', '#section5',
'section6', '#section6',
'section7', '#section7'
]
//const elements = document.querySelectorAll(ids.map(id => `#${id}`).join(', '));
//const sectionidelements = document.querySelectorAll(sectionids.map(id => `#${id}`).join(', '))
/**
* End Global Variables
* Start Helper Functions
*
*/
/**
* End Helper Functions
* Begin Main Functions
*
*/
/* Reference
https://stackoverflow.com/questions/65407419/how-to-add-active-class-to-the-list-href-that-equal-showed-in-the-viewport-sec
*/
// Building the Navigation Bar
for (let pagesection of pagesections) {
const pagelist = document.createElement('li');
const pagelinks = document.createElement('a');
const pagesectionId = pagesection.getAttribute('id');
const pagesectionTitle = pagesection.getAttribute('data-nav');
pagelinks.classList = 'menu__link';
pagelinks.setAttribute('href', `#${pagesectionId}`);
pagelinks.innerText = pagesectionTitle;
fragment.appendChild(pagelist);
pagelist.appendChild(pagelinks);
// Smooth Scroll
pagelinks.addEventListener('click', function (event) {
event.preventDefault();
window.scrollTo({
top: pagesection.offsetTop - 50,
behavior: 'smooth'
});
});
}
navigationBar.appendChild(fragment);
const allpageLinks = navigationBar.querySelectorAll('a');
//Scroll to using anchor ID
var scrollanchorid = document.querySelectorAll("section");
function scrollToAnchor() {
scrollToAnchor.scrollIntoView(true);
}
// Set the section as active when it is in the scope of the screen.
// vars at top pagesections & navigationBarLi
// when scrolling run the following function
window.addEventListener('scroll', () => {
let current = ''; // no current section at the beginning
//looping through all the sections
pagesections.forEach(section => {
const topSction = section.offsetTop;
//added for future reference that the logic loops through the values of the page
//console.log(topSction);
//retrieve sectionHeight
console.log(pageYOffset);
const sctionHeight = section.clientHeight;
if (pageYOffset >= (topSction - sctionHeight / 1)) {
current = section.getAttribute('id');
}
//Page Y Offset means how much we are scrolled here
})
//End of for each loop
// added for future refernce
//console.log(current);
navigationBarLi.forEach(li => {
li.classList.remove('active');
if (li.classList.contains('current')) {
li.classList.add('active')
}
})
// Add class 'active' to section when near top of viewport
// Another for each loop for active classes
const links = document.querySelectorAll('li');
for (var i = 0; i < links.length; i++) {
links[i].addEventListener("click", function () {
var currenstate = document.getElementsByClassName("active");
currenstate[0].className = currenstate[0].className.replace("active", "");
this.className += "active";
});
};
});
//Set the menu item as active when the corresponding section is active.
/*Reference
https://www.w3schools.com/howto/howto_js_active_element.asp
*/
// Return Top Button
function scrollTopfunction() {
if (document.body.scrollTop > 25 ||
document.documentElement.scrollTop > 25) {
TopButton.style.display = "block";
} else {
TopButton.style.display = "none";
}
}
// At user click return to top of page for chrome , safari , firefox & most modern browsers
function pushtopfunction() {
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
}
/* User time out function when idle at 500 seconds */
setTimeout(function () { alert("User Time Out Message : 5 minutes"); }, 500000);
/*
*
* CSS written based on SMACSS architecture.
* To learn more, visit: http://smacss.com/
*
* For simplicity, no reset or normalize is added.
* To learn more, visit: https://css-tricks.com/reboot-resets-reasoning/
*
*/
#import url('https://fonts.googleapis.com/css2?family=Roboto:wght#300&display=swap');
/* ---- Base Rules ---- */
*{
padding: 0;
margin: 0;
box-sizing: border-box;
}
html{
font-family: 'Roboto', sans-serif;
scroll-behavior: smooth;
}
body {
margin: 0;
background: linear-gradient(to bottom, #c31432, #240b36);
color: #fff;
}
ul {
margin: 0;
padding: 0;
overflow: hidden;
background: linear-gradient(to right top, #abbaab, #a34444);
}
section {
position: relative;
min-height: 100vh;
width: 100%;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
}
img {
max-width: 100%;
}
button {
cursor: pointer;
border-radius: 12.5px;
}
/* Typeography General*/
header {
position: absolute;
top: 5px;
bottom: 5px;
align-items: center;
text-align: center;
background: transparent;
text-decoration: blanchedalmond;
text-shadow: black;
}
h1 {
font-family: 'Fira Sans', sans-serif;
border-radius: 50%;
border-color: black;
text-align: center;
align-items: center;
color: goldenrod;
background: linear-gradient(to right top, #8e0e00, #1f1c18);
}
.main__hero {
font-family: 'Fira Sans', sans-serif;
font-size: large;
top: 85px;
float: middle;
align-items: center;
border-color: black;
text-align: center;
color: black;
position: sticky;
}
#media only screen and (min-width: 35em) {
h1 {
font-size: large;
text-align: center;
align-items: center;
color: goldenrod;
}
}
h2 {
border-bottom: 1px solid #cc1;
font-family: 'Oxygen', Sans-Serif;
font-size: 1.2em;
color: azure;
}
p {
line-height: 1.5em;
font-size: 0.9em;
color: #eee;
word-spacing: 0.1em;
}
/* ---- Layout Rules ---- */
main {
margin: 10vh 1em 10vh;
}
.main-hero {
min-height: 40vh;
padding-top: 3em;
}
/* Some sections features*/
section h2 {
display: sticky;
font-size: 7.7vh;
top: 10px;
bottom: 7px;
text-shadow: black;
color: cornsilk;
}
section h3 {
display: flex;
font-size: 2.0rem;
top: 10px;
bottom: 7px;
text-shadow: black;
color: burlywood;
}
/* ---- Module Rules ---- */
/* Return Top Button*/
.topbtnclass {
padding: 15px;
bottom: 20px;
right: 10px;
position: fixed;
text-align: center;
border-radius: 15px;
outline: none;
color: white;
z-index: 99;
font-size: 12.5px;
cursor: pointer;
align-items: center;
background: linear-gradient(to right bottom, #304352, #d7d2cc);
font-family: 'Oxygen', Sans-Serif;
}
/* Return Top Button Hover */
.topbtnclass:hover {
color: goldenrod;
display: flex;
background: linear-gradient(to right bottom, #870000, #190a05);
}
/* Adding an active class */
.active, .a:hover {
background: linear-gradient(to right bottom, #852121, #190a05);
color: goldenrod;
border-radius: 50%;
}
/* Navigation Styles*/
.navbar__menu {
position: fixed;
top: 0px;
float: middle;
background: linear-gradient(to right bottom, #304352, #d7d2cc);
width: 100%;
max-width: 2150px;
margin:0 auto;
text-align: center;
padding: 10px;
}
.navbar__menu ul {
position: relative;
padding-left: 1px;
padding-right: 1px;
text-align: center;
display: inline-block;
background: linear-gradient(to right bottom, #304352, #d7d2cc);
border-radius: 90%;
}
.navbar__menu li {
display: inline-block;
position: relative;
}
.navbar__menu li ul a {
display: inline-block;
padding: 7px 14px;
text-decoration: none;
}
.navbar__menu li ul.active{
background: linear-gradient(to right bottom, #c31432, #240b36);
color: goldenrod;
}
.navbar__menu .menu__link {
display: flex;
padding: 1em;
font-weight: bold;
text-decoration: none;
text-shadow: black;
color: goldenrod;
border-radius: 50%;
}
.navbar__menu .menu__link:hover {
color: wheat;
transition: ease 0.3s all;
}
/* Header Styles */
.page__header {
background: linear-gradient(to right bottom, #c31432, #240b36);
position: sticky;
font-size: 2vw;
width: 100%;
z-index: 50;
border-color: black;
}
/* Footer Styles */
.page__footer {
background: #000;
padding: 3em;
color: #fff;
}
.page__footer p {
color: #fff;
}
/* ---- Theme Rules ---- */
/* Landing Container Styles */
.landing__container {
padding: 1em;
text-align: center;
align-items: center;
}
#media only screen and (min-width: 35em) {
.landing__container {
max-width: 50em;
padding: 4em;
}
}
section:nth-of-type(even) .landing__container {
text-align: center;
align-items: center;
}
/* Background Circles */
/* Note that background circles are created with psuedo elements before and after */
/* Circles appear to be random do to use of :nth-of-type psuedo class */
section:nth-of-type(odd) .landing__container::before {
content: '';
background: rgba(255, 255, 255, 0.187);
position: fixed;
z-index: -5;
width: 50vh;
height: 50vh;
border-radius: 50%;
opacity: 0;
transition: ease 0.5s all;
}
section:nth-of-type(even) .landing__container::before {
content: '';
background: rgb(255, 255, 255);
background: linear-gradient(0deg, rgba(255, 255, 255, .1) 0%, rgba(255, 255, 255, .2) 100%);
position: fixed;
top: 3em;
right: 3em;
z-index: -5;
width: 40vh;
height: 40vh;
border-radius: 50%;
opacity: 0;
transition: ease 0.5s all;
}
section:nth-of-type(3n) .landing__container::after {
content: '';
background: rgb(255, 255, 255);
position: absolute;
right: 0;
bottom: 0;
z-index: -5;
width: 10vh;
height: 10vh;
border-radius: 50%;
opacity: 0;
transition: ease 0.5s all;
}
section:nth-of-type(3n + 1) .landing__container::after {
content: '';
background: rgb(255, 255, 255);
position: absolute;
right: 20vw;
bottom: -5em;
z-index: -5;
width: 15vh;
height: 15vh;
border-radius: 50%;
opacity: 0;
transition: ease 0.5s all;
}
/* ---- Theme State Rules ---- */
/* Section Active Styles */
/* Note: your-active-class class is applied through javascript. You should update the class here and in the index.html to what you set in your javascript file. */
section.active {
color: goldenrod;
background: linear-gradient(to right bottom, #870000, #190a05);
}
section.active .landing__container::before {
opacity: 1;
animation: rotate 4s linear 0s infinite forwards;
}
section.active .landing__container::after {
opacity: 1;
animation: rotate 5s linear 0s infinite forwards reverse;
}
/* Section Active Styles Keyframe Animations */
#keyframes rotate {
from {
transform: rotate(0deg) translate(-1em) rotate(0deg);
}
to {
transform: rotate(360deg) translate(-1em) rotate(-360deg);
}
}
/* Active Class */
li a.active {
color: goldenrod;
}
.active {
color: goldenrod;
background: linear-gradient(to right bottom, #870000, #190a05);
cursor: pointer;
position: relative;
}
.active:hover {
color: goldenrod;
background: linear-gradient(to right bottom, #870000, #190a05);
position: relative;
}
/*Page sections Gradients by id*/
#section1 {
/*border-radiuse at 25% to makesections more rounded*/
border-radius: 25%;
background-color: #fdb813;
background-image: linear-gradient(315deg, #fdb813 0%, #788cb6 74%);
}
#section2 {
border-radius: 25%;
background-color: #edd812;
background-image: linear-gradient(315deg, #edd812 0%, #766a65 74%);
}
#section3 {
border-radius: 25%;
background-color: #edd812;
background-image: linear-gradient(315deg, #edd812 0%, #766a65 74%);
}
#section4 {
border-radius: 25%;
background-color: #fdb813;
background-image: linear-gradient(315deg, #fdb813 0%, #788cb6 74%);
}
#section5 {
border-radius: 25%;
background-color: #edd812;
background-image: linear-gradient(315deg, #edd812 0%, #766a65 74%);
}
#section6 {
border-radius: 25%;
background-color: #b3cdd1;
background-image: linear-gradient(315deg, #b3cdd1 0%, #9fa4c4 74%);
}
#section7 {
border-radius: 25%;
background-color: #eaf818;
background-image: linear-gradient(315deg, #eaf818 0%, #f6fc9c 74%);
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Manipulating the DOM</title>
<!-- Load Google Fonts -->
<link href="https://fonts.googleapis.com/css?family=Fira+Sans:900|Merriweather&display=swap" rel="stylesheet">
<!-- Load Styles -->
<link href="css/styles.css" rel="stylesheet">
</head>
<!-- HTML Follows BEM naming conventions
IDs are only used for sections to connect menu achors to sections -->
<link href="https://fonts.googleapis.com/css?family=Fira+Sans:900|Merriweather&display=swap" rel="stylesheet">
<!-- HTML Follows BEM naming conventions
IDs are only used for sections to connect menu achors to sections -->
<header class="page__header">
<nav class="navbar__menu">
<!-- Navigation starts as empty UL that will be populated with JS -->
<div class="menucontainer">
<ul id="navbar__list"></ul>
</div>
</nav>
</header>
<main>
<header class="main__hero">
<h1>Landing Page </h1>
</header>
<!-- Each Section has an ID (used for the anchor) and
a data attribute that will populate the li node.
Adding more sections will automatically populate nav.
The first section is set to active class by default -->
<section id="section1" data-nav="Section 1">
<div class="landing__container">
<h2>Section 1</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.
Quisquam fugit nobis eaque sunt aperiam molestiae cum in sapiente placeat iusto debitis expedita alias non,
vitae velit cumque exercitationem, minima consectetur.
</p>
</div>
</section>
<section id="section2" data-nav="Section 2">
<div class="landing__container">
<h2>Section 2</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.
Quisquam fugit nobis eaque sunt aperiam molestiae cum in sapiente placeat iusto debitis expedita alias non,
vitae velit cumque exercitationem, minima consectetur.
</p>
</div>
</section>
<section id="section3" data-nav="Section 3">
<div class="landing__container">
<h2>Section 3</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.
Quisquam fugit nobis eaque sunt aperiam molestiae cum in sapiente placeat iusto debitis expedita alias non,
vitae velit cumque exercitationem, minima consectetur.
</p>
</div>
</section>
<section id="section4" data-nav="Section 4">
<div class="landing__container">
<h2>Section 4</h2>
<img src="https://cdn.pixabay.com/photo/2015/01/15/16/16/staircase-600468_1280.jpg" alt=:"staircase-600468_1280"
width="150" height="150">
<img src="https://cdn.pixabay.com/photo/2015/12/08/00/39/steps-1081909_1280.jpg" alt=:"steps-1081909_1280"
width="150" height="150">
<img src="https://cdn.pixabay.com/photo/2014/03/08/22/32/escalator-283448_1280.jpg"
alt=:"escalator-5899073_1280" width="150" height="150">
</div>
</section>
<section id="section5" data-nav="Section 5">
<div class="landing__container">
<h2>Section 5</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.
Quisquam fugit nobis eaque sunt aperiam molestiae cum in sapiente placeat iusto debitis expedita alias non,
vitae velit cumque exercitationem, minima consectetur.
</p>
</div>
</section>
<section id="section6" data-nav="Section 6">
<div class="landing__container">
<h2>Section 6</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit.
Quisquam fugit nobis eaque sunt aperiam molestiae cum in sapiente placeat iusto debitis expedita alias
non,
vitae velit cumque exercitationem, minima consectetur.
</p>
</div>
</section>
<section id="section7" data-nav="Section 7" class="active">
<div class="landing__container">
<h2>Section 7</h2>
<label for"email">Email:</label>
</br>
<input type="text" name="email">
</br>
<label for"Last_name.">Last Name:</label>
</br>
<input type="text" name="Last_name">
</br>
<label for"First_name.">First Name:</label>
</br>
<input type="text" name="First_name."></br>
</form>
</br>
</br>
<textarea class="text_area_class" rows="25" cols=50>
Enterinquiry here.
</textarea>
</br>
<input type="submit">
<br>
</div>
</section>
<button class="topbtnclass" onclick="pushtopfunction()">Top</button>
<script src="js/app.js">
</script>
</main>
<footer class="page__footer">
<p>© Udacity</p>
</footer>
There are some issues:
The expression pageYOffset >= (topSction - sctionHeight / 1) is not correct. Subtracting the height of the section from its top offset is without meaning. Instead, make your loop interruptable (use for instead of forEach) and check whether pageYOffset + top < topSction + sctionHeight. If so, break out of the loop as searching further is no longer needed: we found the section.
Take into account that the menu bar at the top makes part of the section invisible, so make sure that the logic of the point above takes this offset into account. Define top as menu.offsetTop + menu.clientHeight, where menu is the .navbar__menu element.
li.classList.contains('current') is a condition that is never true, as you don't have a current CSS class. You wanted to refer here to the variable current, but use a literal string instead. Moreover, the value of current (e.g. "section3") will not be found in the class list of the li element. You could look for it in the href attribute of the child a elements:
li.children[0].href.endsWith(current)
The navigationBarLi collection is empty because you initialise this variable at a moment that the menu is not built yet. Instead you could use navigationBar.children, which will dynamically find the li elements.
Taking this together, use this code in the first part of your scroll event handler:
let current = '';
const menu = document.querySelector('.navbar__menu');
const top = menu.offsetTop + menu.clientHeight; // Add this offset
for (let section of pagesections) { // make a for-loop, so you can break out
const topSction = section.offsetTop;
const sctionHeight = section.clientHeight;
if (pageYOffset + top < topSction + sctionHeight) { // compare with end of section
current = section.getAttribute('id');
break; // <-- add break
}
}
for (let li of navigationBar.children) { // the collection you had was empty
li.classList.remove('active');
if (li.children[0].href.endsWith(current)) { // This identifies the section's li
li.classList.add('active');
}
}
Why my damn footer won't stay at the bottom of the page?
import React from "react"
import Navbar from "./navbar"
import Footer from "./footer"
import Sidebar from "./sidebar"
import "./layoutplus.css"
const Layout = ({ children }) => {
return (
<>
<Navbar />
<div className="main-cont">
<main id="main-post" className="main-post">{children}</main>
<Sidebar id="sidebar" className="sidebar"/>
</div>
<Footer />
</>
)
}
export default Layout
import React from 'react'
import {Link} from 'gatsby'
import {FaFacebook, FaTwitter, FaInstagram} from 'react-icons/fa'
export default function Footer() {
return (
<div id="footer" className="footer">
<div className="footer-middle">
<ul>
<li><Link className="footer-link" to="/">Home</Link></li>
<li><Link className="footer-link" to="/guides">Guides</Link></li>
<li><Link className="footer-link" to="/about">About</Link></li>
</ul>
<div className="footer-bottom">
<ul className="footer-ul">
<li><Link to="//" ><FaFacebook className="footer-dot" /></Link></li>
<li><Link to="//"><FaTwitter className="footer-dot" /></Link></li>
<li><Link to="//"><FaInstagram className="footer-dot"/></Link></li>
</ul>
<p>Build with <Link style={{ textDecoration: "none", color: "#663399" }} to="https://www.gatsbyjs.com/">Gatsby</Link>.</p>
<p>©{new Date().getFullYear()} <Link className="footer-link" to="/about">blablabla</Link> - All Rights Reserved</p>
</div>
</div>
</div>
)
}
/*MAIN CONT*/
.main-cont {
max-width: 1344px;
margin: 0 auto;
display: grid;
height: 100vh;
grid-template-columns: 4.5fr 1.5fr;
grid-template-rows: 1fr 1fr;
gap: 1rem;
grid-template-areas:
"main-post test-ads"
"main-post test-hottopic";
padding-top: 40px;
}
#media only screen and (max-width: 800px) {
.main-cont {
grid-template-columns: 1fr;
grid-template-rows: 2fr 1fr 1fr;
grid-template-areas:
"main-post"
"test-ads"
"test-hottopic";
padding-right: 10px;
padding-left: 10px;
}
}
.main-post {
grid-area: main-post;
}
.test-ads {
grid-area: test-ads;
border: 1px solid greenyellow;
text-align: center;
}
.test-hottopic {
grid-area: test-hottopic;
border: 1px solid red;
text-align: center;
}
/*FOOTER*/
.footer {
position: fixed;
bottom: 0;
width: 100%;
text-align: center;
background: var(--bgGray);
margin: 0;
padding-bottom: 20px;
}
.footer-middle {
color: var(--mainWithe);
}
.footer-middle ul {
list-style: none;
color: var(--mainWhite);
padding: 0rem 2px;
}
.footer-middle li {
font-size: 1.2rem;
display: inline;
margin-right: 1.5rem;
}
.footer-bottom {
padding-top: 0.5rem;
padding-bottom: 2rem;
}
.footer-link {
color: var(--offWhite);
text-decoration: none;
}
.footer-link:hover {
color: var(--mainOrange);
}
.footer-ul {
list-style: none;
display: inline;
}
.footer-dot {
color: var(--offWhite);
height: 27px;
width: 27px;
}
.footer-dot:hover {
transition: var(--mainTransition);
color: var(--mainOrange);
}
The footer seems to stay at the bottom of the screen always, but never at the end of the page. I don't know why, this problem starts when I added the grid to the .main-cont container, because I need a grid nested into the body.
Anyone has a solution?
With Gatsby, you should struck the brain a little bit, since it's not that easy to access the wrappers of the content, but of course, it doable.
Taking your <Layout> as an example:
import React from "react"
import Navbar from "./navbar"
import Footer from "./footer"
import Sidebar from "./sidebar"
import "./layoutplus.css"
const Layout = ({ children }) => {
return (
<>
<Navbar />
<div className="main-cont">
<main id="main-post" className="main-post">{children}</main>
<Sidebar id="sidebar" className="sidebar"/>
</div>
<Footer />
</>
)
}
export default Layout
Using the previous structure, you can't access the wrapper of your components because Gatsby wraps it between an unreachable <div> with an empty class. However, what you can do is to wrap it again within a known class element, such as:
const Layout = ({ children }) => {
return (
<section className="site-wrapper">
<Navbar />
<div className="main-cont">
<main id="main-post" className="main-post">{children}</main>
<Sidebar id="sidebar" className="sidebar"/>
</div>
<Footer />
</section>
)
}
Given that site-wrapper class you can add the following CSS:
.site-wrapper {
display: flex;
min-height: 100vh;
flex-direction: column;
}
main {
flex-grow: 1;
}
Basically, you are telling the wrapper (site-wrapper) to expand until filling the viewport (100vh). Since your main tag (change it to the desired class if needed) can grow free (flex-grow: 1) so your footer will be always at the bottom of the page because it's pushed by the rest of the flexbox column.
Flexbox has better acceptance/fallback within old browsers than grid has, but you can achieve it either way, the idea is always to push (or let the main grow) the footer at the bottom without depending on the content above, using some minmax should do the trick using a grid-based approach.
I've to make a single section with a vertical scroll transition effect please have a look at the video here for reference: https://drive.google.com/file/d/1Fy4BDqc0-LDrPnEVYuQZdiJ0Pk9qDXA5/view?usp=sharing
How could I achieve this design using javascript or if possible which widget would help me to design this on a wordpress website using elementor?
You can do it easily using pure JS and the Intersection Observer API - when a specific element scrolls into view, animate horizontally using CSS transition transform and translateX the inner element of the right sticky frame
const expo = function(el, entries) {
entries.forEach(entry => {
if (entry.isIntersecting)
el.style.transform = `translateX(-${100 * entry.target.dataset.expo}%)`;
});
};
document.querySelectorAll(".expo").forEach(el => {
const els = el.querySelector(".expo-slides");
const Obs = new IntersectionObserver(expo.bind(null, els), {threshold: 0.5});
el.querySelectorAll(".expo-article").forEach(el => Obs.observe(el));
});
/*QuickReset*/*{margin:0;box-sizing: border-box;}
body {font: 14px/1.4 sans-serif;}
header, footer {background: #ddd;padding: 60px 0;}
/* EXPO */
.expo {
position: relative;
display: flex;
}
.expo-articles {
flex: 1;
}
.expo-article {
min-height: 100vh;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
box-shadow: inset 0 0 0 1px #000;
}
.expo-slidesWrapper {
flex: 1;
position: sticky;
top: 0px;
height: 100vh;
overflow: hidden;
}
.expo-slides {
position: relatie;
display: flex;
height: inherit;
flex-flow: row nowrap;
transition: 0.8s;
}
.expo-slide {
flex: 0 0 100%;
display: flex;
flex-flow: column;
justify-content: center;
align-items: center;
}
<header>
<h1>HEADER</h1>
</header>
<div class="expo">
<section class="expo-articles">
<article data-expo="0" class="expo-article">
<h1>Article 1</h1>
<p>Lorem ipsum article 1</p>
</article>
<article data-expo="1" class="expo-article">
<h1>Article 2</h1>
<p>Lorem ipsum article 2</p>
</article>
<article data-expo="2" class="expo-article">
<h1>Article 3</h1>
<p>Lorem ipsum article 3</p>
</article>
</section>
<div class="expo-slidesWrapper">
<div class="expo-slides">
<div class="expo-slide" style="background: #0bf;">1</div>
<div class="expo-slide" style="background: #f0b;">2</div>
<div class="expo-slide" style="background: #bf0;">3</div>
</div>
</div>
</div>
<footer>
<h2>FOOTER</h2>
</footer>
I have two lists where item i in list 1 is related to item i in list 2.
i want both lists to be scrollable vertically, so that items i always have the same scrolling position in both lists.
But list 2 has more content for a screen to display (like a timeline of sorts), so I want that list to be scrollable horizontally (Mind you not the item itself but the whole list), while list 1 stays fixed.
Is there any way to do this in CSS only?
This is what I have so far:
Codesandbox
Please note that this is just illustrating the point. The actual items are much bigger DOM structures and not just text.
I updated the codesandbox to illustrate the point.
I can't really seem to get the horizontal scrolling working. Any help would be much appreciated.
Set width to some appropriate large value (eg. 500px) and also set white-space: nowrap on the .rightPaneItem.
Keep in mind that value of the width property on the .rightPaneItem element will need to be adjusted according to the content of the rightPaneItem that has the longest content.
function App() {
return (
<div className="App">
<div className="leftPane">
LeftPane
{new Array(100).fill(0).map((_, i) => (
<div key={i} class="leftPaneItem">
item {i}
</div>
))}
</div>
<div className="rightPane">
RightPane
{new Array(100).fill(0).map((_, i) => (
<div key={i} class="rightPaneItem">
item {i} with a way longer text so you should be able to scroll
horizontally
</div>
))}
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
.App {
text-align: center;
max-height: 300px;
max-width: 300px;
display: flex;
border: 3px dashed green;
overflow: auto;
padding: 5px;
}
.leftPane {
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
border: 2px solid red;
padding: 5px;
}
.rightPane {
display: flex;
flex-direction: column;
height: 100%;
flex: 2;
border: 2px solid blue;
padding: 5px;
overflow-x: auto;
}
.leftPaneItem {
height: 20px;
}
.rightPaneItem {
height: 20px;
width: 500px;
overflow-x: hidden;
white-space: nowrap;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Edit:
Since you will have the content with variable length in the right column, you could use the following approach.
Instead of setting the width and overflow properties on .rightPaneItem, you could add overflow: auto on the outer container .App and .rightPane element.
function App() {
return (
<div className="App">
<div className="leftPane">
LeftPane
{new Array(100).fill(0).map((_, i) => (
<div key={i} class="leftPaneItem">
item {i}
</div>
))}
</div>
<div className="rightPane">
RightPane
{new Array(100).fill(0).map((_, i) => {
if (i % 2 == 0) {
return (
<div key={i} class="rightPaneItem">
item {i} with a way longer text so you should be able to scroll
horizontally
</div>);
} else {
return (
<div key={i} class="rightPaneItem">
item {i} with a way longer text so you should be able to scroll
horizontally. item {i} with a way longer text so you should be able to scroll
horizontally. item {i} with a way longer text so you should be able to scroll
horizontally
</div>);
}
})}
</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
.App {
font-family: sans-serif;
text-align: center;
max-height: 300px;
max-width: 300px;
display: flex;
border: 3px dashed green;
overflow: auto;
padding: 5px;
}
.leftPane {
display: flex;
flex-direction: column;
height: 100%;
flex: 1;
border: 2px solid red;
padding: 5px;
}
.rightPane {
display: flex;
flex-direction: column;
height: 100%;
flex: 2;
border: 2px solid blue;
padding: 5px;
overflow: auto;
}
.leftPaneItem {
height: 20px;
}
.rightPaneItem {
height: 20px;
position: relative;
white-space: nowrap;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I have also created a demo on codesandbox that is similar to the one posted in your question and uses the same CSS as the one used in second code snippet.
I am creating a tabbed navigation bar wherein when the tab is active it should change its color the color that i set it to change with. Navigating thru the pages with the tabs works fine however the color highlight on the active tab just don't seem to work.
here is my code so far:
HTML:
<section class="tab" id="active_Current_Tabs">
<header>Active/Current Tabs</header>
<nav class="navbar">
<ul>
<li class="btn currentTab" onclick="activeTab(event, 'lorem7')">TAB1</li>
<li class="btn currentTab" onclick="activeTab(event, 'lorem8')">TAB2</li>
<li class="btn currentTab" onclick="activeTab(event, 'lorem9')">TAB3</li>
</ul>
</nav>
<main class="main-doc">
<article class="selectedPage" id='lorem7'>
<h2>Lorem</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus, est?</p>
</article>
<article class="selectedPage" id="lorem8">
<h2>Lorem</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus, est?</p>
</article>
<article class="selectedPage" id="lorem9">
<h2>Lorem</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus, est?</p>
</article>
</main>
</section>
CSS:
article {
text-align: center;
width: 100%;
height: 300px;
max-height: 300px;
margin: 0;
}
/*navbar css*/
nav {
width: 100%;
height: 75px;
padding: 0;
margin: 0;
}
nav ul {
height: 100%;
padding: 0;
display: flex;
list-style: none;
justify-content: space-around;
}
.btn {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
height: 100%;
background-color: #8b9d98;
cursor: pointer;
font-weight: 500;
}
.btn:hover {
background-color: #d7e0e0;
font-weight: 700;
transition: .5s;
}
/*main css*/
main {
margin-top: 0;
}
/*Active/Current Tab */
#lorem7 {
display: flex;
flex-direction: column;
background-color: #49c2a4;
align-items: center;
justify-content: center;
}
#lorem8 {
display: none;
flex-direction: column;
background-color:#35386f;
align-items: center;
justify-content: center;
}
#lorem9 {
display: none;
flex-direction: column;
background-color:#e28968;
align-items: center;
justify-content: center;
}
Javascript:
// active/current tab function
function activeTab(evnt, currPage) {
var currenttab;
var pages = document.getElementsByClassName('selectedPage');
for (i = 0; i < pages.length; i++) {
pages[i].style.display = "none";
}
//for dehighlighting inactive tabs
currenttab = document.getElementsByClassName('currentTab');
for(j = 0; j < currenttab.length; j++) {
currenttab[j].className = currenttab[j].className.replace("green", " ");
}
document.getElementById(currPage).style.display = "flex";
evnt.currentTarget.className += "green"; //this appends the color to active tab
}
help please! T_T
I'm not sure what you were trying to do with the "green" class because there were no rules for it in your CSS. I answered the question assuming you wanted the active tab to be the same color as the active page. Sorry if that's not what you intended, but I think it makes sense.
To avoid problems with specific class names, you can use .classList methods like "add" and "remove". This way you don't have to worry about the order of class names in the markup. Examples:
tabs[i].classList.remove('active')
e.currentTarget.classList.add('active')
You can also attach your event listener (click handler) dynamically to keep your HTML uncluttered. Example:
for(j = 0; j < tabs.length; j++) {
// attach event listener to all tabs
tabs[j].addEventListener('click', clickTab)
}
You can also make your CSS less repetitive by assigning similar styles to a common class:
.page {display:none;}
.page.active {
display:flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
I modified your IDs in order to be able to reference tabs and pages independently without explicitly passing parameters to the click handler functions. Example:
<li id="t2" class="tab">TAB2</li>
...
<article class="page" id="p2">...</article>
Here is my JS Bin:
http://jsbin.com/defidih/edit?html,css,js,console,output
evnt.currentTarget.className += "green";
This line will add to the already existing className.
So your class class="btn currentTab"
becomes class="btn currentTabgreen"
instead of class="btn currentTab green" if you didn't add green to it before.
So it would better to use currenttab[j].className.replace("green", ""); to reset the previous green classes and evnt.currentTarget.className += " green"; to set the new green class.
Edit: this does imply that the classname will keep growing with one space every time. SO the optimal would be to use classList.add() and classList.remove() instead of manually editting the class string.
function activeTab(evnt, currPage) {
var currenttab;
var pages = document.getElementsByClassName('selectedPage');
for (i = 0; i < pages.length; i++) {
pages[i].style.display = "none";
}
//for dehighlighting inactive tabs
currenttab = document.getElementsByClassName('currentTab');
for(j = 0; j < currenttab.length; j++) {
currenttab[j].className = currenttab[j].className.replace("green", "");
}
document.getElementById(currPage).style.display = "flex";
evnt.currentTarget.className += " green"; //this appends the color to active tab
}
article {
text-align: center;
width: 100%;
height: 300px;
max-height: 300px;
margin: 0;
}
/*navbar css*/
nav {
width: 100%;
height: 75px;
padding: 0;
margin: 0;
}
nav ul {
height: 100%;
padding: 0;
display: flex;
list-style: none;
justify-content: space-around;
}
.btn {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
height: 100%;
background-color: #8b9d98;
cursor: pointer;
font-weight: 500;
}
.btn:hover {
background-color: #d7e0e0;
font-weight: 700;
transition: .5s;
}
/*main css*/
main {
margin-top: 0;
}
/*Active/Current Tab */
#lorem7 {
display: flex;
flex-direction: column;
background-color: #49c2a4;
align-items: center;
justify-content: center;
}
#lorem8 {
display: none;
flex-direction: column;
background-color:#35386f;
align-items: center;
justify-content: center;
}
#lorem9 {
display: none;
flex-direction: column;
background-color:#e28968;
align-items: center;
justify-content: center;
}
.green {
background-color: green;
}
<section class="tab" id="active_Current_Tabs">
<header>Active/Current Tabs</header>
<nav class="navbar">
<ul>
<li class="btn currentTab" onclick="activeTab(event, 'lorem7')">TAB1</li>
<li class="btn currentTab" onclick="activeTab(event, 'lorem8')">TAB2</li>
<li class="btn currentTab" onclick="activeTab(event, 'lorem9')">TAB3</li>
</ul>
</nav>
<main class="main-doc">
<article class="selectedPage" id='lorem7'>
<h2>Lorem</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus, est?</p>
</article>
<article class="selectedPage" id="lorem8">
<h2>Lorem</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus, est?</p>
</article>
<article class="selectedPage" id="lorem9">
<h2>Lorem</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus, est?</p>
</article>
</main>
</section>
To create a tabbed navigation bar wherein when the tab is active it should change its color to a custom color that you have set. You could use this few lines of vanilla JavaScript.
JS:
var activeTab;
var acctOptions = document
.querySelector(".account-options")
.querySelectorAll("li");
acctOptions.forEach(option => {
option.addEventListener("click", function() {
if (activeTab) activeTab.classList.remove("active");
activeTab = option;
activeTab.classList.add("active");
});
});
CSS:
.active {
background: blue;
}
HTML:
<ul class="account-options">
<li class='login'><a>Login</a></li>
<li class='register'>Register</li>
<li class='account' ><a>My Account</a></li>
<li class='reward-points'><a>Reward Points</a></li>
<li class='password-reset'><a>Reset Password</a></li>
<li class='logout'><a>Logout</a></li>
</ul>