Automatically scroll through navigation based on active element - javascript

I'm about to give up, so I'm rewriting this question one more time. I just can't make it work, I have too much left to learn for this to work out. I really tried my best but I need help.
As you can see on my Codepen I managed to make it work "somehow". But I know the calculation is wrong and can't even think about how it could work when scrolling back up.
https://codepen.io/Nimyr/pen/ZEaWpzb
All I want is a navigation like here:
https://www.lieferando.de/speisekarte/bella-italia-bunde
As you can see the navigation sticks to the top and moves the navigation-element thats fitting the currently active section automatically to the left as you scroll down.
I've set up a WordPress Site where I am trying to make the navigation work just like that. Any ideas? :( I'm really lost.
The usecase for this is pretty simple: I want to make a (especially mobile-)user-friendly page that shows a restaurant's menu.
Please don't hesitate to ask any questions if my explanation is weak, I don't know how to formulate it more precisely.
Thanks in advance!
I will also post the code from the Codepen:
HTML
<body>
<nav>
<div class="nav-test">
<ul class="nav-test2">
<li>About</li>
<li>Team</li>
<li>Gallery</li>
<li>Testimonials</li>
<li>Contact</li>
</ul>
</div>
</nav>
<section class="about">
<h1>About</h1>
</section>
<section class="team">
<h1>Team</h1>
</section>
<section class="gallery">
<h1>Gallery</h1>
</section>
<section class="testimonials">
<h1>Testimonials</h1>
</section>
<section class="contact">
<h1>Contact</h1>
</section>
</body>
CSS
* {
margin: 0;
padding: 0;
}
body {
font-family: Verdana, serif;
color: #fff;
}
nav {
position: fixed;
top: 0;
left: 0;
width: 100%;
text-align: center;
background: #515151;
}
.nav-test {
display: flex;
justify-content: center;
}
nav ul {
width: 500px;
overflow: scroll hidden;
display: flex;
flex-wrap: nowrap;
}
nav ul li {
list-style: none;
display: inline-block;
}
nav ul li a {
display: block;
padding: 20px 40px;
text-decoration: none;
color: #fff;
transition: background-color .4s ease;
}
nav ul li a.active {
background-color: #FF0000;
}
section {
padding: 30% 0;
text-align: center;
}
section.about {
background: #888;
}
section.team {
background: skyblue;
}
section.gallery {
background: #aaa;
}
section.testimonials {
background: orange;
}
section.contact {
background: #ccc;
}
JavaScript (please note that this project also contains highlighting the active navigation element through a change in background color. This is purely optional, I need the automatic scroll. But I started with a Codepen created by Kon Kim as a basic structure to figure the rest out. I failed. gg
(function(){
var navLinks = $('nav ul li a'),
navH = $('nav').height(),
section = $('section'),
documentEl = $(document);
documentEl.on('scroll', function() {
var currentScrollPos = documentEl.scrollTop();
section.each(function(){
var self = $(this);
if ( self.offset().top < (currentScrollPos + navH) && (currentScrollPos + navH) < (self.offset().top + self.outerHeight() ) ) {
var
targetClass = '.' + self.attr('class') + '-marker',
width = $(targetClass).outerWidth();
navLinks.removeClass('active'),
$(targetClass).addClass('active');
if ( $(targetClass).offset().left > $('.nav-test2').offset().left)
{
var
newWidth = $(targetClass).offset().left - $('.nav-test2').offset().left;
$('.nav-test2').animate(
{scrollLeft: newWidth},
{duration: 150}
);
return false;
}
}
});
});
})();

Related

HTML navigation of tabs broken

I have my portfolio website in English and translated it in to German with a combination of JSON and Javascript. I have a dropdown menu to pick a language, and once a language is picked a javascript script switches the content of every indicated id with the content of the other language.
I also have a navigation menu which gets underlined when you hover over it and when you click it, it takes you to its respective area on the website. However, the moment the user switches the language, both of these functions do not work anymore i.e the href="#header# as well as nav ul li a:hover::after{} break.
You can mimic this behaviour at alexverheecke.com. Before selecting a language, you can hover over "Home", "About" and it will become underlined and upon clicking, will take you to the section. Once you switch language, this breaks.
I'm assuming this will be a bit time-consuming for someone to look at but I would appreciate any ideas that could help in fixing this.
const jsonDE = {
"_Home": "Startseite",
// ...
}
document.querySelector('#language').addEventListener("change", function() {
if (this.value == "๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ ENG") {
for (let key in jsonEN) {
document.querySelector('#' + key).textContent = jsonEN[key]
}
else if (this.value == "๐Ÿ‡ฉ๐Ÿ‡ช DE") {
for (let key in jsonDE) {
document.querySelector('#' + key).textContent = jsonDE[key]
}
}
});
nav {
display: flex;
/* so image and links side-by-side */
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
}
nav ul li {
display: inline-block;
/* so horizontally aligned */
list-style: none;
margin: 10px 20px;
/* space between links */
}
nav ul li a {
color: white;
text-decoration: none;
font-size: 18px;
position: relative;
/* because abolute in :after */
}
nav ul li a::after {
content: '';
width: 0%;
height: 3px;
background: #3a65ed;
position: absolute;
left: 0;
bottom: -6px;
transition: 0.2s;
}
nav ul li a:hover::after {
width: 100%;
}
<nav>
<ul id="sidemenu">
<li id="_Home">Home </li>
<select id="language" class="language">
<option>๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ ENG</option>
<option>๐Ÿ‡ฉ๐Ÿ‡ช DE</option>
</select>
</ul>
</nav>
If you check your site with the DOM inspector you can see that after you change language the a elements have been removed from within the li of your navigation bar.
I would assume this is because your JSON content holds HTML, yet you're updating the textContent of the element. Change textContent to innerHTML and try again.
Also note that you can simplify the language switching logic by putting the language code as a property within a single object of the JSON. Then you only need one loop to work with every language. Note the use of a value attribute on the option elements to avoid the need to have to cater for the subscript language codes which have been added to the text within the UI of the option.
Below is a working example with both of the above issues corrected:
// mock JSON object...
const translations = {
"DE": {
"_Home": "Startseite"
},
"EN": {
"_Home": "Home"
},
"IT": {
"_Home": "Casa"
}
}
// content switching logic
document.querySelector('#language').addEventListener("change", function() {
for (let key in translations[this.value]) {
document.querySelector('#' + key).innerHTML = translations[this.value][key]
}
});
nav {
display: flex;
/* so image and links side-by-side */
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
}
nav ul li {
display: inline-block;
/* so horizontally aligned */
list-style: none;
margin: 10px 20px;
/* space between links */
}
nav ul li a {
/* color: white; removed so white text is visible */
text-decoration: none;
font-size: 18px;
position: relative;
/* because abolute in :after */
}
nav ul li a::after {
content: '';
width: 0%;
height: 3px;
background: #3a65ed;
position: absolute;
left: 0;
bottom: -6px;
transition: 0.2s;
}
nav ul li a:hover::after {
width: 100%;
}
<nav>
<ul id="sidemenu">
<li id="_Home">Home </li>
</ul>
</nav>
<select id="language" class="language">
<option value="EN">๐Ÿด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ ENG</option>
<option value="DE">๐Ÿ‡ฉ๐Ÿ‡ช DE</option>
<option value="IT">แดตแต€ IT</option>
</select>

Navbar change Color using Observer

Hey out there reading,
i'm making a webpage and im relativly new to JavaScript. I want the Navbar to change Color when its in section Two, while in Section One and Three the Navbar should have the same Color. I watched some tutorials and was able with that code to change the color of the navbar so that in section one and section two the navbar has the right color. When trying out the variables sectionTwo and sectionThree (in order to get the navbars Color to switch back to the color in sectionOne when entering sectionThree) on the other hand it didnt change the colors at the right position (like 100px befor the section). I dont know why this problem accures. If someone knows how to fix it, it would mean the world to me :).
const header = document.querySelector("header")
const sectionOne = document.querySelector(".one")
const sectionTwo = document.querySelector(".two")
const sectionThree = document.querySelector(".three")
const sectionOneOptions = {
}
const sectionOneObserver = new IntersectionObserver(function(entries, sectionOneObserver) {
entries.forEach(entry => {
if (entry.isIntersecting) {
header.classList.add("nav-scrolled")
} else {
header.classList.remove("nav-scrolled")
}
});
},
sectionOneOptions);
sectionOneObserver.observe(sectionTwo)
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Arial";
}
body {
width: 100vw;
height: 100vh;
margin: 0;
background: #000;
}
header {
--text: #fff;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 8rem;
z-index: 10000;
padding: 0 1rem 5rem;
display: flex;
justify-content: space-between;
align-items: center;
}
header nav {
margin: auto;
display: flex;
justify-content: center;
align-items: center;
}
header nav li {
flex: 0 0 auto;
list-style: none;
margin-left: 1rem;
}
header nav li a {
text-decoration: none;
padding: 6px, 15px;
color: var(--text);
border-radius: 20px;
}
.nav-scrolled {
--text: #000;
}
.one {
position: relative;
width: 100%;
height: 100vh;
padding: 200px 20vw;
display: flex;
}
.two {
height: 100vh;
position: relative;
padding: 100px 20vw;
background: #fff;
}
.three {
position: relative;
padding: 100px 20vw;
color: #fff;
height: 100vh;
}
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<header>
<nav>
<li>one</li>
<li>two</li>
<li>three</li>
</nav>
</header>
<section>
<div class="one" id="one"></div>
</section>
<section>
<div class="two" id="two"></div>
</section>
<section>
<div class="three" id="three"></div>
</section>
<script src="app.js"></script>
</body>
</html>
Let me know if you need some more Code and not only the snippet to fix this.
Hopefully some hero can help me :).
Greetings
Noel
I did research to try and answer this question, so please take the preface that I am not an expert in IntersectionObserver.
With that preface out of the way,
There were many things I changed from your example to create the desired effect, however the core feature that you would have needed to implement in your example is the option for intersection observers, rootMargin. By adding this option you can give the item on the screen a negative top and bottom margin. This is important, because you do not want the observer to fire until the header is about to cross into the observed section, and you dont want the header to change until just before it crosses back into the next section.
The way I chose to emulate this behavior, is by using the rootMargin option to give a negative margin. This makes the actual element observed begin however many pixels after we specify. For example, an element that is 100px in height, with "0px 0px -20px 0px" as the value for rootMargin in the options object, would not trigger the IntersectionObserver until 20px of the element was scrolled into the viewport height.
With this understanding, we can define our goal. We want to preform an action when the observed element is about to touch the header. We can describe this as the viewport height minus the header height in a negative margin to the bottom would adjust the element just enough to trigger the intersection observer as desired. Because the methods of obtaining margins are not exact, I subtract one from the numbers calculated to adjust for small error. If we set both margins to overlap eachother, we will never have the observer fire.
I also decided to use css variables and set the value of the variable depending on whether or not the element observed scrolled into or out of the viewport.
This code is not able to execute properly in a stack snippet because the viewport option that the intersection observer defaults to in the options is not set correctly for the snippet environment. I have not tested this code in environments that resize.
Hopefully that explanation helps you understand this code. Let me know if you have any questions.
relevant html:
<header>
<nav>
<li>one</li>
<li>two</li>
<li>three</li>
</nav>
</header>
<section id="one">
</section>
<section id="two">
</section>
<section id="three">
</section>
<script src="app.js"></script>
relevant css:
:root {
--header-text-color: white;
}
body {
margin: 0;
}
header {
position: fixed;
top: 0;
width: 100%;
}
header nav {
display: flex;
justify-content: center;
align-items: center;
}
header li {
list-style: none;
padding: 1rem;
font-size: 2rem;
}
header a {
color: var(--header-text-color);
text-decoration: none;
}
section {
height: 120vh;
background: black;
}
#two {
background: white;
}
relevant js:
const header = document.querySelector("header");
const sectionTwo = document.querySelector("#two");
const topMargin = header.offsetHeight - 1;
const bottomMargin = window.innerHeight - header.offsetHeight - 1;
const options = {
rootMargin: `-${topMargin}px 0px -${bottomMargin}px 0px`,
}
const observer = new IntersectionObserver(([entry]) => {
const color = entry.isIntersecting ? "black" : "white";
document.documentElement.style.setProperty('--header-text-color', color);
}, options);
observer.observe(sectionTwo);

Using CSS Transitions on Auto Height Dimensions

I'm making a collapsible panel and the issue is with the [data-panel] not keeping the css transition when it's set to height: 100%. it works fine for fixed height i.e. height: 150px , but it's important to keep height dynamic since I don't know the available space of the content inside. i'd prefer not modifying the html or js but i'm open to suggestions...
Codepen: issue on line 44 in css
https://codepen.io/oneezy/pen/bGBpXaW
function activeLinks() {
document.addEventListener('click', e => {
let linkEl = e.target.closest('[data-link]');
if (linkEl) {
let url = linkEl.dataset.link;
let linkEls = document.querySelectorAll('[data-link]');
let ActivelinkEls = document.querySelectorAll(`[data-link="${url}"]`);
// remove "active" class from all links
Array.from(linkEls).forEach( (el) => {
el.classList.remove('active');
});
// add "active" class to all matching links
Array.from(ActivelinkEls).forEach( (el) => {
let prevLink = el.parentElement.previousElementSibling;
let prevPrevLink = el.parentElement.parentElement.previousElementSibling;
el.classList.add('active');
if (prevLink && prevLink.dataset.link) {
prevLink.classList.add('active');
prevLink.parentElement.classList.add('active');
}
if (prevPrevLink && prevPrevLink.dataset.link) {
prevPrevLink.classList.add('active');
prevPrevLink.parentElement.classList.add('active');
}
});
}
});
}
activeLinks();
/* Reset
*********************************/
* { margin: 0; padding: 0; box-sizing: border-box; font-family: roboto; }
html, body { height: 100%; overflow-x: hidden; }
a { text-decoration: none; display: block; }
/* Layout
*********************************/
#page { display: grid; grid-template-columns: 160px 1fr; gap: 3rem; height: 100%; }
#nav { background: black; display: block; align-items: start; align-content: start; justify-content: stretch; padding: 2rem 1rem; }
#main { display: flex; justify-content: space-around; padding: 2rem 5rem 0 0; }
/* Navigation
*********************************/
/* Sections */
#nav .link-level__one { margin: 1rem 0; }
#nav .link-level__two { }
#nav .link-level__three { position: relative; }
#nav .link-level__three::after { content: ""; position: absolute; top: 0; left: 0; right: 0; height: 1px; width: 100%; background: gray; }
/* Links */
#nav .link-level__one a { padding: .25rem .5rem; color: gray; font-weight: 900; }
#nav .link-level__one a.active { color: white; }
#nav .link-level__two a { padding: .25rem .5rem; color: gray; font-weight: normal; }
#nav .link-level__two a.active { color: white; }
#nav .link-level__three a { padding: .25rem .5rem; color: gray; }
#nav .link-level__three a.active { color: white; font-weight: normal; }
/* Main
*********************************/
#main section { }
#main a { color: black; padding: 1rem .5rem; }
#main a.active { background: blue; color: white; }
/* Panel
*********************************/
[data-panel] { height: 0; overflow: hidden; transition: .22s .22s ease-in-out; }
.active ~ [data-panel] { height: 150px; } /* I NEED THIS TO BE DYNAMIC HEIGHT! */
<div id="page">
<nav id="nav">
<!-- LINK 1
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->
<!-- LEVEL 1 -->
<section class="navigation-links__wrapper link-level__one">
Link 1
<!-- LEVEL 2 -->
<section class="link-level__two" data-panel>
Link 1a
<!-- LEVEL 3 -->
<section class="link-level__three" data-panel>
Link 1a-1
Link 1a-2
Link 1a-3
</section>
</section>
</section>
<!-- LINK 2
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->
<!-- LEVEL 1 -->
<section class="navigation-links__wrapper link-level__one">
Link 2
<!-- LEVEL 2 -->
<section class="link-level__two" data-panel>
Link 2a
<!-- LEVEL 3 -->
<section class="link-level__three" data-panel>
Link 2a-1
Link 2a-2
Link 2a-3
</section>
</section>
</section>
<!-- LINK 3
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->
<!-- LEVEL 1 -->
<section class="navigation-links__wrapper link-level__one">
Link 3
<!-- LEVEL 2 -->
<section class="link-level__two" data-panel>
Link 3a
<!-- LEVEL 3 -->
<section class="link-level__three" data-panel>
Link 3a-1
Link 3a-2
Link 3a-3
</section>
</section>
</section>
</nav>
<main id="main">
<section>
<h2>Link Level 1</h2>
Link 1
Link 2
Link 3
</section>
<section>
<h2>Link Level 2</h2>
Link 1a
Link 2a
Link 3a
</section>
<section>
<h2>Link Level 3</h2>
Link 1a-1
Link 1a-2
Link 1a-3
Link 2a-1
Link 2a-2
Link 2a-3
Link 3a-1
Link 3a-2
Link 3a-3
</section>
</main>
</div>
It's a duplicate question that has been answered here:
How can I transition height: 0; to height: auto; using CSS?
You can use a max-height greater than it will ever be to accomplish this.
#menu #list {
max-height: 0;
transition: max-height 1s ease-out;
overflow: hidden;
background: #d5d5d5;
}
#menu:hover #list {
max-height: 1000px;
transition: max-height 1s ease-in;
}
I've somewhat came up with a solution I'm proud of; however, it's not perfect. The main trick in getting this to work was giving the [data-panel] a line-height: 0 and transitioning it when it becomes active (THE LINE-HEIGHT ONLY). Also you'll need to make sure the contents of the [data-panel] don't have any margin or padding (until the panel becomes active) or it will completely throw off the UI.
https://codepen.io/oneezy/pen/bGBBEmp
/* Panel
*********************************/
[data-panel] {
overflow: hidden;
line-height: 0;
opacity: 0;
pointer-events: none;
transition: line-height .22s ease-in-out;
}
.active + [data-panel] {
line-height: 1.4;
opacity: 1;
pointer-events: auto;
}
I'm answering my own question; however, I'm not accepting it as the chosen answer. I'd love to see more unique solutions added to this thread.
The short answer is that you can't. Height transitions will only work on elements that use a unit based value for their height property.
Here's an article detailing different techniques to achieve the same outcome: https://css-tricks.com/using-css-transitions-auto-dimensions/

How do you make an updatable NavBar?

I have made a Navbar for my website, but I would like to be able to update all of them across my website by editing some code in JS. Please note that I am extremely bad at JS, and I admit that it may not be possible with JavaScript. Here is the code for the HTML.
nav {
list-style-type: none;
background-color: lightblue;
border: 1px solid black;
width: fit-content;
height: min-content;
position: fixed;
}
nav li {
float: left;
}
nav img {
height: 47px;
display: block;
margin-left: auto;
margin-right: auto;
}
nav li a {
display: block;
color: black;
text-align: center;
padding: 14px 16px;
text-decoration: none;
font-family: -apple-system;
background-color: lightblue;
}
nav li a:hover {
background-color: blue;
color: white;
}
nav .nohover:hover {
color: white;
}
<nav class="container">
<li><img src="image" class="navimg"></li>
<li>website1</li>
<li>website2</li>
<li>website3</li>
</nav>
The information will be very helpful. If you know how to do it, please tell me.
To me it is not very clear what you want, but if you want to change the link you can use this. I also gave the a tags a class so I can call them with the JQuery code!
EDIT:
We are a community who helps you solve your coding problems, not write the entire code for you. So please keep that in mind for your next question on StackOverflow.
EDIT 2: I kept the css code out because it is irrelevant.
$(document).ready(function() {
$('.link1').parent().html('<a class="link1" href="changed1">changed1</a>');
$('.link2').parent().html('<a class="link2" href="changed2">changed2</a>');
$('.link3').parent().html('<a class="link3" href="changed3">changed3</a>');
// if you want to add a new item to the end
$('li:last').append('<li><a class="link4" href="changed4">changed4</a></li>')
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<nav class="container">
<li><img src="image" class="navimg"></li>
<li><a class="link1" href="website1">website1</a></li>
<li><a class="link2" href="webite2">website2</a></li>
<li><a class="link3" href="website3">website3</a></li>
</nav>
Remove the HTML part and add the following code in your js file . All you have to do is to edit the menuArray. Just populate as much as you want.
var menuArray = ['website1', 'website2', 'website3']
var navdiv = document.createElement("div");
navdiv.setAttribute('class', 'container')
var ul = document.createElement("ul");
menuArray.forEach((ele) => {
var li = document.createElement("li");
li.innerHTML = ele
ul.appendChild(li)
})
navdiv.appendChild(ul)
document.body.prepend(navdiv);

Hiding navbar when scrolling (Semplice wordpress)

I am using the semplice theme on wordpress to build my portfolio website. In the theme I am able to add CSS and JS coding. I would like to make my navbar dissapear when I scroll down and reappear when I scroll up.
I have found coding that should do this, however, it does not work and it does not look like coding which were suggested by semplice for other features. Unfortunately, semplice wont help in providing custom coding anymore.
I have been searching the whole afternoon on an answer. Does anyone have any experience with this? All suggestions are very welcome!!
Thanks in advance!
Tomas
You can set a variable as the window's current scrollY and when the user is scrolling, check is the scrollY is increasing or decreasing (scrolling down or scrolling up) to hide or show the navbar.
<!DOCTYPE html>
<html>
<head>
<style>
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
width: 100%;
position: fixed;
top: 0;
left: 0;
right: 0;
}
li {
float: left;
}
li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
li a:hover {
background-color: #111;
}
body, html{
height: 1000px;
}
</style>
</head>
<body>
<ul id="header">
<li><a class="active" href="#home">Home</a></li>
<li>News</li>
<li>Contact</li>
<li>About</li>
</ul>
<script>
var position = window.scrollY;
window.onscroll = function(){
var scroll = this.scrollY;
if(scroll>position){
//scrolling down
document.getElementById('header').style.display = "none";
} else {
//scrolling up
document.getElementById('header').style.display = "";
}
position = scroll;
}
</script>
</body>
</html>

Categories

Resources