Here is the link to codepen: https://codepen.io/rishabhp/pen/aNXVbQ?limit=all&page=7&q=navigation+bar
I am using Brackets and created a html,css,js file. Everything seems to be working except for the js. I've linked the css and js files on the head section. Do I somehow have to link something else or download an extra file?
Head tag looks like this:
<link type="text/css" rel="stylesheet" href="style.css" />
<script src="theFile.js"></script>
HTML code:
<nav>
<ul>
<li>First</li>
<li>Second</li>
<li>Third</li>
<li>Fourth</li>
<li>Fifth</li>
</ul>
</nav>
<div class="sections">
<section id="1"><h1>First</h1></section>
<section id="2"><h1>Second</h1></section>
<section id="3"><h1>Third</h1></section>
<section id="4"><h1>Fourth</h1></section>
<section id="5"><h1>Fifth</h1></section>
</div>
<footer></footer>
<script class="cssdeck" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
CSS code:
#import url(https://fonts.googleapis.com/css?family=Open+Sans);
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Open Sans', sans-serif;
}
/* Navigation */
nav {
width: 100%;
height: 60px;
position: fixed;
top: 0;
background: #1ABC9C;
}
nav ul {
padding: 20px;
margin: 0 auto;
list-style: none;
text-align: center;
}
nav ul li {
display: inline-block;
margin: 0 10px;
}
nav ul li a {
padding: 10px 0;
color: #fff;
font-size: 1rem;
text-decoration: none;
font-weight: bold;
transition: all 0.2s ease;
}
nav ul li a:hover {
color: #34495E;
}
a.active {
border-bottom: 2px solid #ecf0f1;
}
/* Headings */
h1 {
font-size: 5rem;
color: #34495E;
}
/* Sections */
section {
width: 100%;
padding: 50px;
background: #fff;
border-bottom: 1px solid #ccc;
height: 500px;
text-align: center;
}
section:nth-child(even) {
background: #ecf0f1;
}
section:nth-child(odd) {
background: #bdc3c7;
}
.sections section:first-child {
margin-top: 60px;
}
section.active {}
footer {
height: 500px;
background: #34495e;
}
JS code:
var sections = $('section')
, nav = $('nav')
, nav_height = nav.outerHeight();
$(window).on('scroll', function () {
var cur_pos = $(this).scrollTop();
sections.each(function() {
var top = $(this).offset().top - nav_height,
bottom = top + $(this).outerHeight();
if (cur_pos >= top && cur_pos <= bottom) {
nav.find('a').removeClass('active');
sections.removeClass('active');
$(this).addClass('active');
nav.find('a[href="#'+$(this).attr('id')+'"]').addClass('active');
}
});
});
nav.find('a').on('click', function () {
var $el = $(this)
, id = $el.attr('href');
$('html, body').animate({
scrollTop: $(id).offset().top - nav_height
}, 500);
return false;
});
You have to include your JavaScript files after including the jquery.min.js. Otherwise your code gets executed before jQuery is initialized.
Remove <script src="theFile.js"></script> in the head tag and add it at the very bottom of your HTML.
<nav>
<ul>
<li>First</li>
<li>Second</li>
<li>Third</li>
<li>Fourth</li>
<li>Fifth</li>
</ul>
</nav>
<div class="sections">
<section id="1"><h1>First</h1></section>
<section id="2"><h1>Second</h1></section>
<section id="3"><h1>Third</h1></section>
<section id="4"><h1>Fourth</h1></section>
<section id="5"><h1>Fifth</h1></section>
</div>
<footer></footer>
<script class="cssdeck" src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.8.0/jquery.min.js"></script>
<script src="theFile.js"></script>
You can check your console (shortcut: F12), there should be a error like
$ is not defined
in the console.
Edit:
You can ensure jQuery is initialized when your code is executed by waiting to load the page completely. You just have to wrap your code with $(document).ready() function.
$(document).ready(function(){
var sections = $('section')
, nav = $('nav')
, nav_height = nav.outerHeight();
$(window).on('scroll', function () {
var cur_pos = $(this).scrollTop();
sections.each(function() {
var top = $(this).offset().top - nav_height,
bottom = top + $(this).outerHeight();
if (cur_pos >= top && cur_pos <= bottom) {
nav.find('a').removeClass('active');
sections.removeClass('active');
$(this).addClass('active');
nav.find('a[href="#'+$(this).attr('id')+'"]').addClass('active');
}
});
});
nav.find('a').on('click', function () {
var $el = $(this)
, id = $el.attr('href');
$('html, body').animate({
scrollTop: $(id).offset().top - nav_height
}, 500);
return false;
});
});
Related
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");
}
});
});
I have a sticky sidebar like this:
<ul class = "cars">
<li class=""> BMW </li>
......
<li class=""> Mersedes </li>
</ul>
And table like this:
<div class="element-title" id="car-category-1">BMW</div>
.....
<div class="element-title" id="car-category-2">Mersedes</div>
Now what I am trying to do:
Scrolling through the <div id="car-category-1> should change the class of <li> of BMW to .active
Same for Mersedes, if scroll through <div id="car-category-2> then change <li> with Mersedes to active.
this is jquery for click scroll
$(document).on('click', '.model', function () {
var this_id = $(this).data('id');
var gotom = setInterval(function () {
cars_go_to_navtab(this_id);
clearInterval(gotom);
}, 400);
$('.cars li').removeClass('active');
$(this).parent().addClass('active');
});
function cars_go_to_navtab(id) {
var scrolling_div = $('#car-category-' + id);
$('html, body').animate({
scrollTop: scrolling_div.offset().top - 70
}, 500);
}
There is a great article of CSS-Tricks for a pure CSS solution (not sure that it is suitable to your use-case) which also has a link to another great article that uses Intersection Observer. In short, place something like this in your code:
const observer = new IntersectionObserver(entries =>
{
for (const entry of entries) {
if (entry.isIntersecting) {
// add css style here
}
else {
// remove css style here
}
});
observer.observe(document.querySelector('#your-element-selector'));
Also, please mind support table over different browsers (canIuse to the rescue)
A simple implementation using Jquery. Also, explore the possibilities of Intersection Observer API
$(document).on("scroll", function() {
var scrollPos = $(document).scrollTop();
$('#menu a').each(function() {
var currLink = $(this);
var refElement = $(currLink.attr("href"));
if (refElement.position().top <= scrollPos && refElement.position().top + refElement.height() > scrollPos) {
$('#menu ul li a').removeClass("active");
currLink.addClass("active");
} else {
currLink.removeClass("active");
}
});
});
* {
margin: 0;
padding: 0;
}
.container {
display: flex;
font-family: helvetica, sans-serif;
}
.content {
flex: 1;
padding-left: 200px;
}
.section {
background-color: grey;
height: 100vh;
width: 100%;
overflow: hidden;
padding: 20px;
box-sizing: border-box;
}
#menu {
position: fixed;
top: 0;
background: #444;
width: 200px;
}
#menu a {
padding: 10px;
display: flex;
color: #FFF;
text-decoration: none;
}
h1 {
font-size: 30px;
color: #FFF;
}
.active {
background: #4CAF50;
color: #FFF;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div class="container">
<div id="menu">
<ul>
<li><a class="active" href="#bmw">BMW</a>
</li>
<li>Mercedes</li>
</ul>
</div>
<div class="content">
<div class="section" id="bmw">
<h1>
BMW
</h1>
</div>
<div class="section" id="mercedes">
<h1>
Mercedes
</h1>
</div>
</div>
</div>
Got a JS/JQuery issue I'm struggling to solve.
I'm building a simple one page site with a fixed header and anchor based scrolling navigation. When a user clicks a link the the scrollTop of the body is animated to the top of the relevant section. Included in this function is a calculation that subtracts the height of the header from the scroll distance to ensure the fixed header doesn't obscure the top of the section
The issue I'm having is because of a separate function that applies a class to the header if a user scrolls past a certain distance. This class causes a few properties to change and therefore the height of the header element changes.
This means that the value of $('header').outerHeight(); at the end of the scrolling animation is smaller than what it was when the animation began. This change means that the distance scrolled is wrong and the window ends up slightly too short of the desired location.
How do I allow for this change in value during the scrolling animation? Ideally I want everything to stay dynamic rather than specifying fixed heights/sizes. TIA.
$('a[href^="#"]').on("click", function() {
let headerHeight = $('header').outerHeight();
$('html, body').animate({
scrollTop: $($.attr(this, 'href')).offset().top - headerHeight
}, 500);
});
$(window).on('load resize scroll', function() {
if ($(window).scrollTop() >= 100 ) { // this refers to window
$('header').addClass('scroll');
} else {
$('header').removeClass('scroll');
}
});
body,html {
margin: 0;
padding: 0;
}
header {
padding: 30px 30px;
position:fixed;
left:0;
right: 0;
top: 0;
z-index:99;
transition: all .3s ease;
}
header h1 {
display:inline-block;
width:10%;
color: white;
}
header.scroll {
background: black;
padding: 15px 30px;
}
header nav {
display:inline-block;
width: 89%;
}
header nav ul {
margin: 0;
list-style: none;
text-align:right;
}
header nav ul li {
display:inline-block;
}
header nav ul li a {
color: white;
padding: 0 10px;
}
.hero {
height: 600px;
background: blue;
}
section h2 {
position: relative;
top: 50%;
transform: translateY(-50%);
margin: 0;
text-align: center;
color: white;
}
section {
height: 600px;
}
#one {
background: red;
}
#two {
background: green;
}
#three {
background: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
<h1>Title</h1>
<nav>
<ul>
<li>Section 1 </li>
<li>Section 2 </li>
<li>Section 3 </li>
</ul>
</nav>
</header>
<section class="hero"><h2>Hero</h2></section>
<section id="one"><h2>Section 1</h2></section>
<section id="two"><h2>Section 2</h2></section>
<section id="three"><h2>Section 3</h2></section>
If you run the above snippet and click on the link for "Section 1" at the top of the page. You can see the distance scrolled is slightly too short and the bottom of the .hero element is still visible due to the change in height.
Try this, I'm subtracting 30 from header's height because in start padding was "30px 30px" and then it's going to be "15px 30px" means header height should be:
height = actualHeight - - ,
and this required when your header is expanded(means header does not have class scroll) so I added condition.
$('a[href^="#"]').on("click", function() {
let headerHeight = $('header').outerHeight();
if(!$('header').hasClass('scroll')) {
headerHeight -= 30; //The padding removed
}
$('html, body').animate({
scrollTop: $($.attr(this, 'href')).offset().top - headerHeight
}, 500);
});
$(window).on('load resize scroll', function() {
if ($(window).scrollTop() >= 100 ) { // this refers to window
$('header').addClass('scroll');
} else {
$('header').removeClass('scroll');
}
});
body,html {
margin: 0;
padding: 0;
}
header {
padding: 30px 30px;
position:fixed;
left:0;
right: 0;
top: 0;
z-index:99;
transition: all .3s ease;
}
header h1 {
display:inline-block;
width:10%;
color: white;
}
header.scroll {
background: black;
padding: 15px 30px;
}
header nav {
display:inline-block;
width: 89%;
}
header nav ul {
margin: 0;
list-style: none;
text-align:right;
}
header nav ul li {
display:inline-block;
}
header nav ul li a {
color: white;
padding: 0 10px;
}
.hero {
height: 600px;
background: blue;
}
section h2 {
position: relative;
top: 50%;
transform: translateY(-50%);
margin: 0;
text-align: center;
color: white;
}
section {
height: 600px;
}
#one {
background: red;
}
#two {
background: green;
}
#three {
background: orange;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
<h1>Title</h1>
<nav>
<ul>
<li>Section 1 </li>
<li>Section 2 </li>
<li>Section 3 </li>
</ul>
</nav>
</header>
<section class="hero"><h2>Hero</h2></section>
<section id="one"><h2>Section 1</h2></section>
<section id="two"><h2>Section 2</h2></section>
<section id="three"><h2>Section 3</h2></section>
Ended up solving this by adding a callback to my animation function. The callback checks if the height of the header has changed since the function was first called and if it has, then the scrollTop is increased by this amount.
$('a[href^="#"]').on("click", function() {
let headerHeightOne = $('header').outerHeight();
$('html, body').animate({
scrollTop: $($.attr(this, 'href')).offset().top - headerHeightOne
}, 500, function() {
let headerHeightTwo = $('header').outerHeight();
let difference = headerHeightOne - headerHeightTwo;
if (difference > 0 ) {
$('html,body').animate({
scrollTop: $(window).scrollTop() + difference
}, 100);
}
});
});
I'm having a flickering issue with my bottom-border (nav link) when I scroll within a certain section. I suppose it's not really a flicker, but a transition that's being resetted every time I scroll within that section. For example (refer to source below), if my window is currently in a section and I scroll little by little, the border-bottom of my active nav link will flicker. In addition, if I hold onto the scroll bar and scroll down, the border-bottom disappears.
I'd like for it to:
1) Not flicker when scrolling.
2) When scrolling on the page with the scroll bar, keep the border-bottom present.
http://jsfiddle.net/binhxn89/zzmbex55/16/
HTML:
<body>
<div class="navbar">
<div class="container cf">
<ul>
<li>Home</li>
<li>Link1</li>
<li>link2</li>
<li>link3</li>
<li>link4</li>
</ul>
</div>
</div>
<section id="welcome" class="welcome"></section>
<section id="link1" class="link1"></section>
<section id="link2" class="link2"></section>
<section id="link3" class="link3"></section>
<section id="link4" class="link4"></section>
</body>
CSS:
body, html {
height: 100%;
color: #f3f3f3;
font-family: Arial, sans-serif;
}
body {
margin: 0;
overflow-x: hidden;
}
.container {
width: 90%;
margin: 0 auto;
}
.navbar {
background: rgba(0,0,0, 0.9);
width: 100%;
position: fixed;
z-index: 20;
}
.navbar ul li {
list-style: none;
float: left;
}
.navbar ul li a {
color: #fff;
display: block;
font-size: 14px;
margin: 0 10px;
padding: 20px 5px;
text-decoration: none;
-webkit-transition: all 0.2s ease-in-out;
-moz-transition: all 0.2s ease-in-out;
transition: all 0.2s ease-in-out;
}
.navbar ul li a:hover,
.navbar ul li a.active {
margin-top: -3px;
font-weight: bold;
padding-bottom: 8px;
border-bottom: 2px solid #fff;
}
section {
height:100%;
z-index: 10;
position: relative;
}
.welcome {
background: #ebebeb;
}
.link1 {
background: #aaa;
}
.link2 {
background: #bbb;
}
.link3 {
background: #ccc;
}
.link4 {
background: #ddd;
}
JS:
$(document).ready(function() {
$(window).scroll(function () {
var y = $(this).scrollTop();
$('.link').each(function (event) {
if (y >= $($(this).attr('href')).offset().top - 10) {
$('.link').not(this).removeClass('active');
$(this).addClass('active');
}
});
});
$('a[href*=#]:not([href=#])').click(function() {
if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {
var target = $(this.hash);
target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
if (target.length) {
$('html,body').animate({
scrollTop: target.offset().top
}, 600);
return false;
}
}
});
})
If anyone could help or refer me to a particular source regarding this issue, that would be great. Thanks!
Please take a look here or take a look at the code below. First, I calculate the heigh of one element and get his position. After that I compare the part to the current position. So far, no flickering on Safari or Chrome, I hope this will fix it. It seams that your solution raised multiple events, if you look at the console.log(y); you will notice a lot of double values for part 2, 3, 4 and 5.
$(window).scroll(function() {
var y = $(this).scrollTop();
var sectionHeigth = $('section').height();
var part = Math.round((y / sectionHeigth));
$('.link').each(function(event) {
var position = $($(this).attr('href')).offset().top;
if ((part * sectionHeigth) != position) {
$(this).removeClass('active');
} else {
$(this).addClass('active');
}
});
});
Here is the update to your comment. I'll check if the current position is between the start and start + height position. That's all.
$(window).scroll(function() {
var y = $(this).scrollTop();
$('.link').each(function(event) {
var position = $($(this).attr('href')).offset().top;
var a = $($(this).attr('href')).height();
if (y >= position && y <= position + a) {
$(this).addClass('active');
} else {
$(this).removeClass('active');
}
});
});
I have a navigation of lets say 12 items, and when resolution gets smaller items drop in a new line. I need to make that when an item doesn't fit on a navigation anymore it should put a "MORE" dropdown button on the right side of nav. and put that item that doesn't fit in a dropdown.
If you don't understand me there is image below.
But the problem is that navigation items aren't always the same width because navigation items are generated from REST api.
I tryed to make jQuery script for calculating items width and adding them to navigation.
Here is the script I created, I made it in a hurry so it's really bad.
I need to help on how to properly calculate items witdh and navigation width and calculating when to add items to navigation or remove items from navigation.
Here is image if you don't get it: http://img.hr/aagV
/*
* Here we check how many items can we put on the navigation bar
* If item doesn't fit we clone it on the more dropdown button
*/
function removeMany() {
var i = $items.length - 1;
if (itemsWidth > navWidth) {
while (itemsWidth > navWidth) {
$($items[i]).removeClass('first-level-item').addClass('second-level-item');
dropdownItems.push($items[i]);
$($items[i]).removeClass('showed');
$items.pop();
i--;
getItemsWidth();
}
$nav.append($navMore);
dropdownItems.reverse().forEach(function (element, index, array) {
$('ul.second-level').append(element);
});
getItems();
}
}
//If window is resized to bigger resolution we need to put back items on the navbar
function addMany() {
var i = dropdownItems.length - 1;
if (dropdownItems.length != 0) {
do {
$('ul.first-level').append(dropdownItems.reverse()[i]);
$items.push(dropdownItems[i]);
dropdownItems.pop();
i--;
getItemsWidth();
} while (itemsWidth < navWidth);
$navMore.remove();
$items.each(function (i) {
$(this).addClass('first-level-item showed').removeClass('second-level-item');
});
if (!(dropdownItems != 0)) {
return;
} else {
$nav.append($navMore);
}
}
}
body {
margin: 0;
padding: 0;
border: 0; }
ul, li {
margin: 0;
padding: 0;
list-style: none; }
ul.second-level li {
display: block !important; }
ul.second-level li > a {
color: black; }
a {
color: #fff;
text-decoration: none;
text-transform: uppercase; }
.second-level-item a {
color: #333 !important; }
.navigation {
width: 960px;
max-width: 100%;
background: #211;
color: #aaa;
margin: 0 auto; }
.first-level .first-level-item {
display: inline-block;
padding: 10px; }
.first-level .item-more {
display: inline-block; }
.first-level .item-more .second-level-item {
display: inline-block; }
.second-level {
position: absolute;
top: 100%;
right: 0;
width: 200px;
background: #fff;
padding: 10px;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4); }
.has-second-level {
position: relative; }
.has-second-level .second-level {
display: none; }
.has-second-level:hover {
background: #fff;
color: #000; }
.has-second-level:hover .second-level {
display: block; }
/*# sourceMappingURL=style.css.map */
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>DropDown</title>
<link rel="stylesheet" href="css/reset.css"/>
<link rel="stylesheet" href="css/style.css"/>
</head>
<body>
<nav class="navigation">
<ul class="first-level">
<li class="first-level-item showed">Introduction to Irish Culture</li>
<li class="first-level-item showed">Cellular and Molecular Neurobiology</li>
<li class="first-level-item showed">Guitar foundations</li>
<li class="first-level-item showed">Startup Inovation</li>
<li class="first-level-item showed">Astrophysics</li>
<li class="first-level-item item-more has-second-level">
<span> More </span>
<ul class="second-level">
</ul>
</li>
</ul>
</nav>
<script src="https://code.jquery.com/jquery-2.1.1.js"></script>
</body>
</html>
If you have fixed-width list-items, then it is simple to collect extra list-items and push them into a separate list. Here is a simple example. Explanation is in the code comments.
View the snippet in full-screen and try changing the window width.
Also a Fiddle: http://jsfiddle.net/abhitalks/860LzgLL/
Full Screen: http://jsfiddle.net/abhitalks/860LzgLL/embedded/result/
Snippet:
var elemWidth, fitCount, fixedWidth = 120,
$menu = $("ul#menu"), $collectedSet;
// Assuming that the list-items are of fixed-width.
collect();
$(window).resize(collect);
function collect() {
// Get the container width
elemWidth = $menu.width();
// Calculate how many list-items can be accomodated in that width
fitCount = Math.floor(elemWidth / fixedWidth) - 1;
// Create a new set of list-items more than the fit count
$collectedSet = $menu.children(":gt(" + fitCount + ")");
// Empty the collection submenu and add the cloned collection set
$("#submenu").empty().append($collectedSet.clone());
}
* { box-sizing: border-box; margin: 0; padding: 0; }
div { position: relative; background-color: #ccc; height: 32px; overflow: visible; }
ul#menu, ol { height: 32px; max-width: 80%; overflow: hidden; }
ul#menu > li, ol > li { display: block; float: left; height: 32px; width: 120px; padding: 4px 8px; }
ol { position: absolute; right: 0; top: 0; overflow: visible; }
ol > li { min-width: 120px; }
ol ul { position: absolute; top: 120%; right: 10%; }
ol li ul > li { list-style: none; background-color: #eee; border: 1px solid gray; padding: 4px 8px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<ul id="menu">
<li>Option One</li><li>Option Two</li><li>Option Three</li>
<li>Option Four</li><li>Option Five</li><li>Option Six</li>
</ul>
<ol><li>Collected<ul id="submenu"></ul></li></ol>
</div>
Update:
This is regarding your query on differing / variable widths of list-items. There would be a minor change.
Also a Fiddle: http://jsfiddle.net/abhitalks/tkbmcupt/1/
Full Screen: http://jsfiddle.net/abhitalks/tkbmcupt/1/embedded/result/
Snippet:
var elemWidth, fitCount, varWidth = 0, ctr, $menu = $("ul#menu"), $collectedSet;
// Get static values here first
ctr = $menu.children().length; // number of children will not change
$menu.children().each(function() {
varWidth += $(this).outerWidth(); // widths will not change, so just a total
});
collect(); // fire first collection on page load
$(window).resize(collect); // fire collection on window resize
function collect() {
elemWidth = $menu.width(); // width of menu
// Calculate fitCount on the total width this time
fitCount = Math.floor((elemWidth / varWidth) * ctr) - 1;
// Reset display and width on all list-items
$menu.children().css({"display": "block", "width": "auto"});
// Make a set of collected list-items based on fitCount
$collectedSet = $menu.children(":gt(" + fitCount + ")");
// Empty the more menu and add the collected items
$("#submenu").empty().append($collectedSet.clone());
// Set display to none and width to 0 on collection,
// because they are not visible anyway.
$collectedSet.css({"display": "none", "width": "0"});
}
* { box-sizing: border-box; margin: 0; padding: 0; }
div { position: relative; background-color: #ccc; height: 32px; overflow: visible; }
ul#menu, ol { height: 32px; max-width: 80%; overflow: hidden; }
ul#menu > li, ol > li { display: block; float: left; height: 32px; white-space: nowrap; padding: 4px 8px; }
ol { position: absolute; right: 0; top: 0; overflow: visible; }
ol > li { min-width: 120px; }
ol ul { position: absolute; top: 120%; right: 10%; }
ol li ul > li { list-style: none; background-color: #eee; border: 1px solid gray; padding: 4px 8px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
<ul id="menu">
<li>Option One</li><li>Option Two</li><li>Option Three</li>
<li>Option Four</li><li>Option Five</li><li>Option Six</li>
</ul>
<ol><li>Collected<ul id="submenu"></ul></li></ol>
</div>
Can and SHOULD be optimised (as it is quite inefficient from what i've tested), but that's up to you.
$(document).ready(function(){
var moreW = $(".more").outerWidth(), //width of your "more" element
totalW = -moreW, //cumulated width of list elements
totalN = $('.nav li').length - 1, //number of elements minus the "more" element
dw = document.documentElement.clientWidth;
$('.nav li').each(function(){
totalW += $(this).outerWidth();
});
function moveToDropdown(){
dw = document.documentElement.clientWidth;
//moves elements into the list
while(totalW > (dw - moreW)){
var temp = $(".nav li:nth-last-child(2)"); //element to be moved
totalW = totalW - temp.outerWidth();
$(".dropdown").append(temp.clone());
temp.remove();
}
//moves elements out of the list
var newList = $('.dropdown li').length; //check if we have elements
if(newList > 0){
var element = $('.dropdown li:last-child'), //element to be moved
elementW = $('.dropdown li:last-child').outerWidth(); //width of element to be moved
if(totalW + elementW < dw - moreW){
while(totalW + elementW < dw - moreW ){
var element = $('.dropdown li:last-child'),
elementW = $('.dropdown li:last-child').outerWidth();
totalW = totalW + elementW;
$(".nav > li:last-child").before(element.clone());
element.remove();
}
}
}
}
moveToDropdown();
$(window).resize(moveToDropdown)
});
.clearfix:after{
display:block;
content:'';
clear:both;
}
body,html{
width:100%;
height:100%;
margin:0;
padding:0;
}
ul{
list-style:none;
width:100%;
padding:0;
margin:0;
}
ul li{
float:left;
padding:5px;
}
.nav > li {
position:relative;
}
.nav ul{
position:absolute;
top:25px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul class="nav clearfix">
<li>Item</li>
<li>Item</li>
<li>Item</li>
<li>Item</li>
<li>Item</li>
<li>Item</li>
<li>Item</li>
<li class="more">
more
<ul class="dropdown">
<!-- we'll add elements here -->
</ul>
</li>
</ul>
This question is too old, but i want to post my answer too. Maybe this is more cleaner and easier way. I have created a pen: https://codepen.io/sergi95/pen/bmNoML
<div id="mainMenu" class="main-menu">
<ul id="autoNav" class="main-nav">
<li>
home
</li>
<li>
about us
</li>
<li>
portfolio
</li>
<li>
team
</li>
<li>
blog
</li>
<li>
contact
</li>
<li id="autoNavMore" class="auto-nav-more">
more
<ul id="autoNavMoreList" class="auto-nav-more-list">
<li>
policy
</li>
</ul>
</li>
</ul>
const $mainMenu = $("#mainMenu");
const $autoNav = $("#autoNav");
const $autoNavMore = $("#autoNavMore");
const $autoNavMoreList = $("#autoNavMoreList");
autoNavMore = () => {
let childNumber = 2;
if($(window).width() >= 320) {
// GET MENU AND NAV WIDTH
const $menuWidth = $mainMenu.width();
const $autoNavWidth = $autoNav.width();
if($autoNavWidth > $menuWidth) {
// CODE FIRES WHEN WINDOW SIZE GOES DOWN
$autoNav.children(`li:nth-last-child(${childNumber})`).prependTo($autoNavMoreList);
autoNavMore();
} else {
// CODE FIRES WHEN WINDOW SIZE GOES UP
const $autoNavMoreFirst = $autoNavMoreList.children('li:first-child').width();
// CHECK IF ITEM HAS ENOUGH SPACE TO PLACE IN MENU
if(($autoNavWidth + $autoNavMoreFirst) < $menuWidth) {
$autoNavMoreList.children('li:first-child').insertBefore($autoNavMore);
}
}
if($autoNavMoreList.children().length > 0) {
$autoNavMore.show();
childNumber = 2;
} else {
$autoNavMore.hide();
childNumber = 1;
}
}
}
// INIT
autoNavMore();
$(window).resize(autoNavMore);
.main-menu {
max-width: 800px;
}
.main-nav {
display: inline-flex;
padding: 0;
list-style: none;
}
.main-nav li a {
padding: 10px;
text-transform: capitalize;
white-space: nowrap;
font-size: 30px;
font-family: sans-serif;
text-decoration: none;
}
.more-btn {
color: red;
}
.auto-nav-more {
position: relative;
}
.auto-nav-more-list {
position: absolute;
right: 0;
opacity: 0;
visibility: hidden;
transition: 0.2s;
text-align: right;
padding: 0;
list-style: none;
background: grey;
border-radius: 4px;
}
.auto-nav-more:hover .auto-nav-more-list {
opacity: 1;
visibility: visible;
}
The script that Abhitalks made did not work properly for different element sizes. I modified the code a little bit do that it does:
$(function() {
function makeMenuFit() {
//Get data
var menuSize = menu.width();
//Determine how many items that fit
var menuTotalWidth = 0;
var itemThatFit = 0;
for(var i = 0; i < menuItems.length; i++) {
menuTotalWidth += menuItems[i];
if(menuTotalWidth <= menuSize) {
itemThatFit++;
continue;
}
break;
}
menu.children().css({"display": "block", "width": "auto"});
var collectedSet = menu.children(":gt(" + (itemThatFit - 1) + ")");
$("#submenu").empty().append(collectedSet.clone());
collectedSet.css({"display": "none", "width": "0"});
}
var menu = $(".tabletNavigation > ul");
var menuItems = [];
menu.children().each(function() {
menuItems.push($(this).outerWidth());
});
$(window).resize(makeMenuFit);
makeMenuFit();
});