Active Navigation On Scroll anchor links - javascript

I've been trying to figure this out for hours. I currently have a fixed nav for my anchor website. I would like the menu link background color to change when scrolling through each section. Like this: https://codepen.io/dbilanoski/pen/LabpzG . When I scroll through each section I only see the hover background color not the active state color. If you have any tips or advice, please advise.
Thank you!
var navLink = $(".nav-link"),
topMenuHeight = navLink.outerHeight() + 15,
//All list items
menuItems = navLink.find("a"),
//Anchors corresponding to menu items
scrollItems = menuItems.map(function() {
var item = $($(this).attr("href"));
if (item.length) {
return item;
}
});
// Bind click handler to menu items
// so we can get a fancy scroll animation
menuItems.click(function(e) {
var href = $(this).attr("href")
offsetTop = href === "#" ? 0 : $(href).offset().top - topMenuHeight + 1;
$('html, body').stop().animate({
scrollTop: offsetTop
}, 300);
e.preventDefault();
});
// Bind to scroll
$(window).scroll(function() {
// Get container scroll position
var fromTop = $(this).scrollTop() + topMenuHeight;
// Get id of current scroll item
var cur = scrollItems.map(function() {
if ($(this).offset().top < fromTop)
return this;
});
// Get the id of the current element
cur = cur[cur.length - 1];
var id = cur && cur.length ? cur[0].id : "";
if (navLink !== id) {
navLink = id;
// Set/remove active class
menuItems
.parent().removeClass("active")
.end().filter("[href='#" + id + "']").parent().addClass("active");
}
});
a {
color: inherit;
text-decoration: none;
}
a:hover {
color: #fa448c;
text-decoration: underline;
}
a:active {
background-color: #fa448c;
color: #fff;
}
li {
list-style-type: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="menu">
<ul class="nav-list">
<li class="nav-item">
Home
</li>
<li class="nav-item">
Products
</li>
<li class="nav-item">
Featured
</li>
<li class="nav-item">
Best Sellers
</li>
<li class="nav-item">
Contact
</li>
</ul>
</div>

The trick of the codepen you've sent is that all sections have the same height. However, there's a more flexible way of doing that. Using this solution, you check for each section whether it's shown on the screen or not, and if it is, highlight its button in the navlist.

Related

Hide every menu opened just after click a link from it

I am making an Off Canvas sidebar dropdown menu in vanilla JS.
Almost at the bottom of my JS code I have made a function to hide the sidebar off canvas just after someone clicks an href link.
Also I have made a function to hide and show the hidden submenu level of the dropdown menu in order to click them.
But now I need a function to close every opened dropdown menu from the sidebar when a user clicks an href link, just before getting the user to anoher site page. Then, when the user opens again the menu all dropdowns will be closed.
NOTE: 'aTags' is the name of the variable that points to every clickeable href element
Any advice will be very helpfull!
const open = document.querySelector('.open');
let list_items = document.querySelectorAll('#subb');
let overlay = document.querySelector('.overlay');
let aTags = open.getElementsByClassName('ilink');
// Show and Hide sidebar when click icon
let toggles = document.querySelectorAll(".c-hamburger");
for (let i = toggles.length - 1; i >= 0; i--) {
let toggle = toggles[i];
toggleHandler(toggle);
};
function toggleHandler(toggle) {
toggle.addEventListener("click", function(e) {
e.preventDefault();
if (this.classList.contains("is-active") === true) {
this.classList.remove("is-active");
open.classList.remove('oppenned');
overlay.className = 'overlay';
} else {
this.classList.add("is-active");
open.classList.add('oppenned');
overlay.className += ' open';
}
});
window.addEventListener('click', function(event) {
if (event.target === overlay) {
toggle.classList.remove("is-active");
open.classList.remove('oppenned');
overlay.className = 'overlay';
}
});
// hide sidebar off canvas when click a menu link
for (let i = 0; i < aTags.length; i++) {
aTags[i].addEventListener("click", close);
}
function close(e) {
e.stopPropagation();
open.classList.remove('oppenned');
toggle.classList.remove("is-active");
overlay.className = 'overlay';
}
// Show and Hide the hidden Submenu ul
for (let i = 0; i < list_items.length; i++) {
list_items[i].addEventListener("click", show);
}
function show(e) {
e.stopPropagation();
this.classList.toggle("myClass");
}
}
.sub-menu ul ul {
max-height: 0;
position: relative;
top: 0;
left: 0;
opacity: 0;
visibility: hidden;
transition: all 0.6s ease-out;
background: white;
}
#subb.myClass>ul {
max-height: 32em;
visibility: visible;
opacity: 1;
}
<nav id="nav" class="sub-menu open">
<ul class="list-unstyled">
<li id="subb">
<a class="link">Soluciones</a>
<ul>
<li id="subb">
<a class="link">Correo y herramientas colaborativas</a>
<ul>
<li><a class="link ilink" href="https://www.google.com/">Google Workspace</a></li>
<li><a class="link ilink href=" https://www.google.com/ "">Google for Education</a></li>
<li><a class="link ilink" href="https://www.google.com/">Microsoft 365</a></li>
</ul>
</li>
</ul>

Dropdown Menu Closes On Hover (Error)

Whhenever I hover over the menu it works fine. But, when I try to get to the submenu links and children, the menu closes
/*----------------------------------------------------
/* Dropdown menu
/* ------------------------------------------------- */
jQuery(document).ready(function($) {
function mtsDropdownMenu() {
var wWidth = $(window).width();
if (wWidth > 865) {
$('#navigation ul.sub-menu, #navigation ul.children').hide();
var timer;
var delay = 100;
$('#navigation li').hover(
function() {
var $this = $(this);
timer = setTimeout(function() {
$this.children('ul.sub-menu, ul.children').slideDown('fast');
}, delay);
},
function() {
$(this).children('ul.sub-menu, ul.children').hide();
clearTimeout(timer);
}
);
} else {
$('#navigation li').unbind('hover');
$('#navigation li.active > ul.sub-menu, #navigation li.active > ul.children').show();
}
}
mtsDropdownMenu();
$(window).resize(function() {
mtsDropdownMenu();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<li id="menu-item-513" class="menu-item "><i class="fa fa-calculator"></i> OFFERTE AANVRAGEN
<ul class="sub-menu">
<li id="menu-item-1146" class="menu-item">Zonnepanelen installatie (Belgiƫ)
</li>
<li id="menu-item-1144" class="menu-item">Zonnepanelen reinigen (Belgiƫ)
</li>
<li id="menu-item-1145" class="menu-item">Zonnepanelen installatie (Nederland)
</li>
</ul>
</li>
The code you posted works just fine; here's a plnkr to prove it: https://plnkr.co/edit/IFaueUhKE3J1K9vY1NkQ?p=preview
(simply wrapped a <div id='navigation'><ul> round the top li).
If you still loose the hover over the child-elements, it's caused by something you're not showing in the original question. E.g. adding this css:
li.menu-item {
position: relative;
top: 50px;
left: 300px;
}
would make it difficult to reach the child items because you, briefly, lose the parent-hover while moving to the child.

JQuery contextmenu Not Working On Appended Elements

JSFiddle Demo
On my mail side nav I have a custom right-click hijack of which I have just made it so you can add a new sub-folder as partly seen below;
if ($(this).hasClass('NewSubFolder')) {
if($('ul.inbox-nav li.Clicked').find('ul').length) {
$('ul.inbox-nav li.Clicked > ul').prepend("<li class='NewSubFolder'><input type='text'></li>");
} else {
$('ul.inbox-nav li.Clicked').append('<ul><li class="NewSubFolder"><input type="text"></li></ul>');
}
$("ul.inbox-nav li.Clicked").removeClass('Clicked');
}
This will add another tier where there is not one to prepend where there is, an input field. Currently you have to hit the enter key after typing something for the new folder name and then it will have worked its magic...
...However this newly appended list item does not work when you right-click it.
Hopefully this gets what you need done.
Let me know if the comments are not clear enough.
EDIT
Made an edit to combine the two on(contextmenu) calls into one function. No need for redundancy.
$(document).ready(function() {
// Trigger action when the contexmenu is about to be shown
$('#inbox-nav').on("contextmenu", 'a', function(event) {
event.preventDefault();
$('.clicked').removeClass('clicked'); //Gets rid of all other clicked elements
$(this).closest('li').addClass('clicked');
//Clicks the closest li element
var menu = ($(this).is('#inbox-nav>li>a')) ? 'MailMenuFirstTier' : 'MailMenuSecondTier';
/*This is an inline if statement, read in words it goes like this:
if this element is a direct level link, then we're going to need to use the first menu tier.
else we're going to need use the second menu tier.
*/
$("#" + menu).finish().show(100)
//dynamically calls the menu we're using.
.css({
left: event.pageX,
top: event.pageY
}); //Moves the first mail menu to the event position
});
/*
check the element to see which menut to show instead of using two different things.
*/
$(document).on('mousedown', function(e) {
//Mouse down events!
if ($('.custom-menu').is(':visible') && !$(e.target).parent().hasClass('custom-menu')) {
/*
In English:
if a custom menu is visible, AND the target of the click DOES NOT have the custom-menu class, hide the custom menu.
*/
$('.custom-menu').finish().hide();
}
if ($(e.target).parent().hasClass('custom-menu')) {
//Figure out what to do since your element is a child of the custom menu
$('.custom-menu').finish().hide();
var action = $(e.target).data('action');
//Gets our action element
var clicked = $('.clicked');
//gets the clicked element we will be working on.
switch (action) {
case 'new-folder':
//If the clicked element does not have a child ul element, add one.
$('input.rename').focusout();
//Any current input.renames will have their focus out method called
if (clicked.children('ul').length == 0) {
clicked.append($('<ul></ul>'))
}
var ul = clicked.children('ul');
//Either this child element existed before or we just made it the step before.
var input = $('<input />', {
type: 'text',
value: 'New Sub Folder',
class: 'rename',
'data-start': 'New Sub Folder',
focusout: function() {
var value = ($(this).val() == '') ? $(this).data('start') : $(this).val();
$(this).siblings('a').html(value).show();
$(this).remove();
},
autofocus: true
});
//Creates an input tag of type text, with class rename, a placeholder value, and a focusout function.
var anchor = $('<a>', {
href: '#',
css: {
display: 'none'
}
});
//Creates an anchor tag that is originally hidden
ul.append($('<li>').append([input, anchor]));
ul.find('input').click();
//Adds the (should be selected) element and the anchor
//The input element takes care of things from there
break; // end new-folder case
case 'rename-folder':
$('input.rename').focusout();
//any current input.rename items will have their focusout method called
var anchor = clicked.find('a');
//get our closest anchor of our clicked element
anchor.before($('<input />', {
type: 'text',
value: anchor.html(),
class: 'rename',
'data-start': anchor.html(),
focusout: function() {
var value = ($(this).val() == '') ? $(this).data('start') : $(this).val();
$(this).siblings('a').html(value).show();
$(this).remove();
},
autofocus: true
})).hide();
//Creates an input element, adds it before the anchor element,
//hides anchor element. the newly created input element takes care of things from there
break;
/*
ADD NEW ACTIONS HERE
*/
default:
return;
break;
}
}
}).on('keyup', 'input.rename', function(e) {
//Used for laziness. If a user hits enter in the input.rename tag, we fire the focusout target
e.preventDefault();
if (e.keyCode == 13) {
$(e.target).focusout();
}
});
});
.custom-menu {
display: none;
z-index: 1000;
position: absolute;
margin: 0;
padding: 0;
list-style: none;
overflow: hidden;
border: 1px solid #CCC;
white-space: nowrap;
font-family: sans-serif;
background: #FFF;
color: #333;
border-radius: 5px;
font-size: 12px;
}
.custom-menu li {
padding: 8px 12px;
cursor: pointer;
}
.custom-menu li:hover {
background-color: #DEF;
}
menu {
position: absolute;
}
.custom-menu .divider {
content: " ";
height: 1px;
margin: 4px 10px;
background: #929292;
}
#MailBodyList.custom-menu li.Title {
color: #929292;
}
#MailBodyList.custom-menu li.Title:hover {
background: #FFF;
cursor: default;
}
#MailBodyList.custom-menu li.ForThisSenderMore {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="inbox-nav" id="inbox-nav">
<li class="active">
<a href="javascript:;" data-type="inbox" data-title="Inbox">
<div class="Arrow"></div>Inbox
</a>
<ul>
<li>Sub-Folder 1
</li>
<li>Sub-Folder 2
</li>
<li>
Sub-Folder 3
<ul>
<li>Sub-Folder 1
</li>
<li>Sub-Folder 2
</li>
</ul>
</li>
<li>Sub-Folder 4
</li>
<li>Sub-Folder 5
</li>
</ul>
</li>
<li>
Important
</li>
<li>
Sent
</li>
<li>
<a href="javascript:;" data-type="draft" data-title="Draft"> Draft
<span class="badge badge-danger">8</span>
</a>
</li>
<li>
<a href="javascript:;" class="sbold uppercase" data-title="Trash"> Trash
<span class="badge badge-info">23</span>
</a>
</li>
<li>
<a href="javascript:;" data-type="inbox" data-title="Promotions"> Promotions
<span class="badge badge-warning">2</span>
</a>
</li>
<li>
News
</li>
</ul>
<ul id="MailMenuFirstTier" class="custom-menu">
<li>Mark All As Read</li>
<li>Empty Folder</li>
</ul>
<ul class="custom-menu" id="MailMenuSecondTier">
<li class="NewSubFolder" data-action="new-folder">New Sub-Folder</li>
<li class="Rename" data-action="rename-folder">Rename</li>
<li class="Delete" data-action="delete-folder">Delete</li>
<li>Mark All As Read</li>
<li>Empty Folder</li>
</ul>
You can use .contextmenu() to overwrite right-clic behavior.
$('.NewSubFolder').contextmenu(function() {
console.log("Right clic detected!");
});
Documentation here: https://api.jquery.com/contextmenu/
I hope it helps! :)

JS Dropdown Menu Behavior

I have a dropdown menu working but I can't figure out how to close the previous menu onclick. All the menues stay open so I need them to close when a different menu is open.
Please see my jsfiddle
https://jsfiddle.net/yvhnphp4/
$(document).ready(function(){
// Dropdown menu
var findDropdowns = document.querySelectorAll(".has-dropdown");
for(var i = 0; i < findDropdowns.length; i++) {
if(i == 0) {
var dropdownId = "has-dropdown-1";
findDropdowns[i].setAttribute("id", dropdownId);
}else {
var addOneToIndex = i + 1;
dropdownId = "has-dropdown-" + addOneToIndex;
findDropdowns[i].setAttribute("id", dropdownId);
}
var targetDropdown = document.getElementById(dropdownId);
targetDropdown.onclick = dropdownTrigger;
}
function dropdownTrigger(e) {
e.preventDefault();
var showHideDropdown = e.target.nextElementSibling;
showHideDropdown.setAttribute("class", "show");
}
});
<ul class="nav">
<li><a class="has-dropdown" href="">Link</a>
<ul>
<li>Sub-Link</li>
<li>Sub-Link</li>
<li>Sub-Link</li>
</ul>
</li>
<li><a class="has-dropdown" href="">Link</a>
<ul>
<li>Sub-Link</li>
<li>Sub-Link</li>
<li>Sub-Link</li>
</ul>
</li>
</ul>
.nav ul {display:none;}
.nav ul.show{display:block;}
You can simply remove the .show class from all ul tags in .nav that still have it, before opening a new dropdown:
function dropdownTrigger(e) {
var opened = document.querySelectorAll(".nav ul.show");
for(var i = 0; i < opened.length; i++) {
opened[i].removeAttribute("class");
}
...
}
Note that since you're using jQuery anyway ($(document).ready) there is probably a much better way to do this.
Also, use href="#" instead of href="".

CSS Flyout Menu in conjunction with scrolling menu

I am new to javascript and am having trouble with menus. I can get a flyout menu to work, and I can get a scrolling menu to work (demo of scrolling menu http://css-tricks.com/examples/LongDropdowns/).
However, I cannot figure out how to merge the effects. Can someone please help me with the flyout code for below?
//HTML
<ul class="dropdown">
<li>CITIES BY STATE
<ul>
for (var p = 0; p < state.length; p++) {
<li> + states[p][0] + "");
<ul id="city\" class="city">
<li>CITY 1</li>
<li>CITY 2</li>
<li>CITY 3</li>
</ul>");
</li>");
}
</ul>
</ul>
</li>
//SCRIPT TO ALLOW SCROLL THROUGH LIST
<script>
var maxHeight = 600;
$(function(){
$(".dropdown > li").hover(function() {
var $container = $(this),
$list = $container.find("ul"),
$anchor = $container.find("a"),
height = $list.height() * 1.1, // make sure there is enough room at the bottom
multiplier = height / maxHeight; // needs to move faster if list is taller
// need to save height here so it can revert on mouseout
$container.data("origHeight", $container.height());
// so it can retain it's rollover color all the while the dropdown is open
$anchor.addClass("hover");
// don't do any animation if list shorter than max
if (multiplier > 1) {
$container
.css({
height: maxHeight,
overflow: "hidden"
})
.mousemove(function(e) {
var offset = $container.offset();
var relativeY = ((e.pageY - offset.top) * multiplier) - ($container.data("origHeight") * multiplier);
if (relativeY > $container.data("origHeight")) {
$list.css("top", -relativeY + $container.data("origHeight"));
};
});
}
}, function() {
var $el = $(this);
// put things back to normal
$el
.height($(this).data("origHeight"))
.find("ul")
.css({ top: 0 })
.show()
.find("a")
.removeClass("hover");
});
});
</script>
This is what I have tried, but I am off. I want a list of cities to display to the right of the state list when I hover on a state.
/*code to show/hide city menu*/
#city li:hover ul ul, #city li:hover ul ul ul, #city li:hover ul ul ul ul{
display:none;
}
#city li:hover ul, #city li li:hover ul, #city li li li:hover ul, #city li li li li:hover ul{
display:block;
}
Chrisoph is right. This is just jibberish:
<ul class="dropdown">
<li>CITIES BY STATE
<ul>
for (var p = 0; p < state.length; p++) {
<li> + states[p][0] + "");
<ul id="city\" class="city">
<li>CITY 1</li>
<li>CITY 2</li>
<li>CITY 3</li>
</ul>");
</li>");
}
</ul>
</ul>
</li>
This is syntactially correct HTML/Javascript. Screw it... I decided to just code it for you because I'm bored, but you really need to go back to basics in HTML/JavaScript.
<html>
<head>
<title>test</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function()
{
var map = [
{state: "NSW", cities: ["Sydney", "Newcastle", "Orange"]},
{state: "QLD", cities: ["Brisbane", "Cairns", "Townsville"]},
{state: "VIC", cities: ["Melbourne", "Geelong", "Ballarat"]},
{state: "SA", cities: ["Radelaide", "Mount Gambier"]},
{state: "TAS", cities: ["Hobart", "Devonport", "Launceston"]}
];
for (var i = 0; i < map.length; i++)
{
var a = $('<a href="#">').html(map[i].state);
var li = $('<li>').append(a);
var cities = $('<ul>').attr('id', map[i].state).addClass('cityList');
for(var j = 0; j < map[i].cities.length; j++)
{
cities.append($('<li>').html(map[i].cities[j]));
}
li.append(cities);
$('#citiesByState').append(li);
}
});
</script>
</head>
<body>
<ul class="dropdown">
<li>
CITIES BY STATE
<ul id="citiesByState"></ul>
</li>
</ul>
</body>
</html>
Now go back and fix your question and test it then we'll look at any new problems.

Categories