How to add smooth scrolling to Bootstrap's scroll spy function - javascript

I've been trying to add a smooth scrolling function to my site for a while now but can't seem to get it to work.
Here is my HTML code relating to my navigation:
<div id="nav-wrapper">
<div id="nav" class="navbar navbar-inverse affix-top" data-spy="affix" data-offset-top="675">
<div class="navbar-inner" data-spy="affix-top">
<div class="container">
<!-- .btn-navbar is used as the toggle for collapsed navbar content -->
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<!-- Everything you want hidden at 940px or less, place within here -->
<div class="nav-collapse collapse">
<ul class="nav">
<li>Home</li>
<li>Services</li>
<li>Contact</li>
</ul><!--/.nav-->
</div><!--/.nav-collapse collapse pull-right-->
</div><!--/.container-->
</div><!--/.navbar-inner-->
</div><!--/#nav /.navbar navbar-inverse-->
</div><!--/#nav-wrapper-->
Here is the JS code I've added:
<script src="js/jquery.scrollTo-1.4.3.1-min.js"></script>
<script>
$(document).ready(function(e) {
$('#nav').scrollSpy()
$('#nav ul li a').bind('click', function(e) {
e.preventDefault();
target = this.hash;
console.log(target);
$.scrollTo(target, 1000);
});
});
</script>
For what it's worth, here is where I received info on what I've done so far, and here is my site in it's current form. If you can help me I'll bake you a pie or cookies or something. Thanks!

Do you really need that plugin? You can just animate the scrollTop property:
$("#nav ul li a[href^='#']").on('click', function(e) {
// prevent default anchor click behavior
e.preventDefault();
// store hash
var hash = this.hash;
// animate
$('html, body').animate({
scrollTop: $(hash).offset().top
}, 300, function(){
// when done, add hash to url
// (default click behaviour)
window.location.hash = hash;
});
});
fiddle

If you have a fixed navbar, you'll need something like this.
Taking from the best of the above answers and comments...
$(".bs-js-navbar-scrollspy li a[href^='#']").on('click', function(event) {
var target = this.hash;
event.preventDefault();
var navOffset = $('#navbar').height();
return $('html, body').animate({
scrollTop: $(this.hash).offset().top - navOffset
}, 300, function() {
return window.history.pushState(null, null, target);
});
});
First, in order to prevent the "undefined" error, store the hash to a variable, target, before calling preventDefault(), and later reference that stored value instead, as mentioned by pupadupa.
Next. You cannot use window.location.hash = target because it sets the url and the location simultaneously rather than separately. You will end up having the location at the beginning of the element whose id matches the href... but covered by your fixed top navbar.
In order to get around this, you set your scrollTop value to the vertical scroll location value of the target minus the height of your fixed navbar. Directly targeting that value maintains smooth scrolling, instead of adding an adjustment afterwards, and getting unprofessional-looking jitters.
You will notice the url doesn't change. To set this, use return window.history.pushState(null, null, target); instead, to manually add the url to the history stack.
Done!
Other notes:
1) using the .on method is the latest (as of Jan 2015) jquery method that is better than .bind or .live, or even .click for reasons I'll leave to you to find out.
2) the navOffset value can be within the function or outside, but you will probably want it outside, as you may very well reference that vertical space for other functions / DOM manipulations. But I left it inside to make it neatly into one function.

$("#YOUR-BUTTON").on('click', function(e) {
e.preventDefault();
$('html, body').animate({
scrollTop: $("#YOUR-TARGET").offset().top
}, 300);
});

// styles.css
html {
scroll-behavior: smooth
}
Source: https://www.w3schools.com/howto/howto_css_smooth_scroll.asp#section2

If you download the jquery easing plugin (check it out),then you just have to add this to your main.js file:
$('a.smooth-scroll').on('click', function(event) {
var $anchor = $(this);
$('html, body').stop().animate({
scrollTop: $($anchor.attr('href')).offset().top + 20
}, 1500, 'easeInOutExpo');
event.preventDefault();
});
and also dont forget to add the smooth-scroll class to your a tags like this:
<li>About Us</li>

I combined it, and this is the results -
$(document).ready(function() {
$("#toTop").hide();
// fade in & out
$(window).scroll(function () {
if ($(this).scrollTop() > 400) {
$('#toTop').fadeIn();
} else {
$('#toTop').fadeOut();
}
});
$('a[href*=#]').each(function() {
if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'')
&& location.hostname == this.hostname
&& this.hash.replace(/#/,'') ) {
var $targetId = $(this.hash), $targetAnchor = $('[name=' + this.hash.slice(1) +']');
var $target = $targetId.length ? $targetId : $targetAnchor.length ? $targetAnchor : false;
if ($target) {
var targetOffset = $target.offset().top;
$(this).click(function() {
$('html, body').animate({scrollTop: targetOffset}, 400);
return false;
});
}
}
});
});
I tested it and it works fine. hope this will help someone :)

What onetrickpony posted is okay, but if you want to have a more general solution, you can just use the code below.
Instead of selecting just the id of the anchor, you can make it bit more standard-like and just selecting the attribute name of the <a>-Tag. This will save you from writing an extra id tag. Just add the smoothscroll class to the navbar element.
What changed
1) $('#nav ul li a[href^="#"]') to $('#nav.smoothscroll ul li a[href^="#"]')
2) $(this.hash) to $('a[name="' + this.hash.replace('#', '') + '"]')
Final Code
/* Enable smooth scrolling on all links with anchors */
$('#nav.smoothscroll ul li a[href^="#"]').on('click', function(e) {
// prevent default anchor click behavior
e.preventDefault();
// store hash
var hash = this.hash;
// animate
$('html, body').animate({
scrollTop: $('a[name="' + this.hash.replace('#', '') + '"]').offset().top
}, 300, function(){
// when done, add hash to url
// (default click behaviour)
window.location.hash = hash;
});
});

with this code, the id will not appear on the link
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
anchor.addEventListener('click', function (e) {
e.preventDefault();
document.querySelector(this.getAttribute('href')).scrollIntoView({
behavior: 'smooth'
});
});
});

Related

Scroll top undefined in shopify site

I am working on an ecommerce store(shopify/liuquid).
I want to scroll smoothly to different hashs.
Now because this is a CMS, I have had to add some attributes via editor or manually with JS.
Here I give it the href
$(document).ready(function() {
$(".hero__cta").addClass("scroll");
$(".hero__cta").attr("href", "#section2")
});
If you go to the website, just under the main image, #the new entries# title, has this is the markup:
<a id="section2"> </a>
And here is the JS function:
$(document).ready(function(){
$("a").on('click', function(event) {
if (this.hash !== "") {
event.preventDefault();
var hash = this.hash;
$('html, body').animate({scrollTop: $(hash).offset().top}, 800,
function() {
window.location.hash = hash;
}
);
}
});
});
So if you go to the site and click on the CTA button, it should scroll smoothly to that anchor. Works on a Codepen etc, but not on the Shopify platform.
I get :
Uncaught TypeError: Cannot read property 'top' of undefined
at HTMLAnchorElement.
The site is live here:
https://www.toptrendshopping.com/
Oh and how and where do I add an IntersectionObserver polifill for this?
I have found a fix. As you can see, this works now on the live site.
$(document).ready(function(){
$(".hero__cta").attr("href", "#two");
$('a[href*="#"]')
// Remove links that don't actually link to anything
.not('[href="#"]')
.not('[href="#0"]')
.click(function(event) {
// On-page links
if (
location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '')
&&
location.hostname == this.hostname
) {
// Figure out element to scroll to
var target = $(this.hash);
target = target.length ? target : $('[name=' + this.hash.slice(1) + ']');
// Does a scroll target exist?
if (target.length) {
// Only prevent default if animation is actually gonna happen
event.preventDefault();
$('html, body').animate({
scrollTop: target.offset().top
}, 1000, function() {
// Callback after animation
// Must change focus!
var $target = $(target);
$target.focus();
if ($target.is(":focus")) { // Checking if the target was focused
return false;
} else {
$target.attr('tabindex','-1'); // Adding tabindex for elements not focusable
$target.focus(); // Set focus again
};
});
}
}
});
});
Found this on CSS tricks, still do not entirely why all this is required but work for now.
And a little extra, if you want to add a polyfill on a Shopify store, do it in the head section of the "theme.liquid" file.

Limiting area of page scroll on navigation click

I am using the following jquery code to scroll to particular sections when a menu in the navigation tab is clicked. You must have well guessed by now that its a one page website. So coming further, the problem is that when the menu is clicked it scrolls to that particular DIV section but the header hides behind the menu's div. I mean it scrolls way too much up. I want to limit the level of scrolling. Say the it should stop 200px before than what it actually reaches a stop point now. Is it possible?
Here is my code
$(document).ready(function() {
$('body').find('a').click(function(){
var $href = $(this).attr('href');
var $anchor = $($href).offset();
var $li = $(this).parent('li');
$li.addClass('active');
$li.siblings().removeClass('active');
$('body,html').animate({ scrollTop: $anchor.top }, 1000);
return false;
});
});
Instead of hard coding the header value, a better approach would be dynamically getting the height of header, so it won't create issues in mobile and other devices.
$(document).ready(function() {
$('body').find('a').click(function(){
var $heightEx = $('.navbar').height(); // use your respective selector
var $href = $(this).attr('href');
var $anchor = $($href).offset();
var $li = $(this).parent('li');
$li.addClass('active');
$li.siblings().removeClass('active');
$('body,html').animate({ scrollTop: ($anchor.top - $heightEx) }, 1000);
return false;
});
});
EDIT
This is the code I personally use
$("a").on('click', function(event) {
$heightEx = $('header').height();
if (this.hash !== "") {
event.preventDefault();
var hash = this.hash;
$('html, body').animate({
scrollTop: ($(hash).offset().top - $heightEx)
}, 800);
}
});
Maybe, you need to change 'animate' scrollTop parameter:
$('body,html').animate({ scrollTop: $anchor.top - 200px }, 1000);

Cannot read property 'top' of undefined Jquery/javascript

Probably this question has been answered many times before, but i can't find anything that related to my code.
Everything works properly, when the menu nav is open, etc. the smooth scrolling works as well, except when i click the arrow-down to go the the next section, smooth scrolling doesnt work.
I have been looking at it and trying to figure it out for a while but i am unable to do so.
I am still learning jquery and javascript.
A full DEMO of this code in use can be found HERE.
Open dev tools and you will see the errors in the console.
EDIT
added..
.arrow-down-wrapper a[href^="#"]
to
$('nav.mobile-nav a[href^="#"], .arrow-down-wrapper a[href^="#"]').on('click', function (e) {
...
}
Smooth scrolling not works for the 'arrow-down', but i am still getting
'Uncaught TypeError: Cannot read property 'top' of undefined'
console.log(target); outputs the correct targets. #Home, #about, etc..
This is the code i have:
//smooth transition to sctions when links in header are clicked
function onScroll(event){
var scrollPosition = $(document).scrollTop();
$('nav.mobile-nav a').each(function () {
var currentLink = $(this);
var refElement = $(currentLink.attr("href"));
if (refElement.position().top <= scrollPosition && refElement.position().top + refElement.height() > scrollPosition) {
$('nav.mobile-nav a').removeClass("current");
currentLink.addClass("current");
}
else{
currentLink.removeClass("current");
}
});
}
$(document).ready(function () {
$(document).on("scroll", onScroll);
$('nav.mobile-nav a[href^="#"]').on('click', function (e) {
e.preventDefault();
$(document).off("scroll");
$('nav.mobile-nav a').each(function () {
$(this).removeClass('current');
});
$(this).addClass('current');
var target = this.hash;
$target = $(target);
$('html, body').stop().animate({
'scrollTop': $target.offset().top
}, 1000, 'swing', function () {
window.location.hash = target;
$(document).on("scroll", onScroll);
});
});
});
The issue was that the code wasn't specific enough. The loop was iterating through all the items in the list, that is all the links that where #tags, and links to other pages. That is the reason i was getting the error of top not defined, that item it was looking for didn't exist. a[href^="#"' after adding that, loop only iterated the items with # ID tags.
Commented the changes i made
//smooth transition to sctions when links in header are clicked
function onScroll(event){
var scrollPosition = $(document).scrollTop();
$('nav.mobile-nav a[href^="#"').each(function () { //added a[href^="#"] so that the loop only iterates over the elements with the ID tag
var currentLink = $(this);
var refElement = $(currentLink.attr("href"));
console.log(currentLink.attr("href")); //log
if (refElement.position().top <= scrollPosition && refElement.position().top + refElement.height() > scrollPosition) {
$('nav.mobile-nav a').removeClass("current");
currentLink.addClass("current");
}
else{
currentLink.removeClass("current");
}
});
}
$(document).ready(function () {
$(document).on("scroll", onScroll);
$('nav.mobile-nav a[href^="#"], .arrow-down-wrapper a[href^="#"]').on('click', function (e) {
e.preventDefault();
$(document).off("scroll");
$('nav.mobile-nav a').each(function () {
$(this).removeClass('current');
});
$(this).addClass('current');
var target = this.hash;
$target = $(target);
console.log(target);
$('html, body').stop().animate({
'scrollTop': $target.offset().top - 100
}, 1000, 'swing', function () {
window.location.hash = target;
$(document).on("scroll", onScroll);
});
});
});
That is a style so the value, in javascript, would need to be .style.top not just .top. That is why you are getting 'undefined'. There is no top property assigned to the object you are using.
I don't use jQuery but, in javascript, to retrieve the top property, you could do something like:
var a = document.getElementsByTagName('div')[0];
var b = a.style.top;
console.log(parseInt(b,10)); //cause b will be equal to 'nnpx' and parseInt removes the 'px'
However, this doesn't read any top value set in CSS. You need to set the value with javascript in order to read that. There is a way to read the value set in a CSS stylesheet but I don't recall how.
I don't actually get the problem.
Opening the dev tools i have some errors but the smooth scroll on your website works quite fine for me.
Have a look at this jsfiddle i made:
https://jsfiddle.net/p4x3d2dn/1/
HTML
<ul class="nav">
<li><a class="scroll" href="#section_home">Home</a></li>
<li><a class="scroll" href="#section_about">About</a></li>
<li><a class="scroll" href="#section_team">Team</a></li>
<li><a class="scroll" href="#section_gallery">Gallery</a></li>
<li><a class="scroll" href="#section_contact">Contact</a></li>
</ul>
<section id="section_home">
<h1>Home</h1>
</section>
<section id="section_about">
<h1>About</h1>
</section>
<section id="section_team">
<h1>Team</h1>
</section>
<section id="section_gallery">
<h1>Gallery</h1>
</section>
<section id="section_contact">
<h1>Contact</h1>
</section>
Adding other links (such as a scroll down arrow) is just a matter of adding the correct href attribute to the .scroll tag:
<a class="scroll" href="#section_whatever_you_want">
<i class="fa fa-chevron-down"></i>
</a>
A slightly different approach must be taken if you have custom generated sections and you are not in control of the DOM
This is all the javascript you need:
$(document).ready(function() {
$(".scroll").on("click", function() {
//event.preventDefault();
var el = $(this).attr("href");
$('html, body').animate({
scrollTop: $(el).offset().top
}, 2000);
});
});

Remove anchor link after scrolling - works also on links from another page

I've set up a single page website with smooth scrolling that strips the anchor link from the URL after smooth scrolling. Here's the jQuery I'm using :
$(function() {
$('a.page-scroll').on('click', function(event) {
var $anchor = $(this);
$('html, body').stop().animate({
scrollTop: $($anchor.attr('href')).offset().top - 60
}, 1000, 'easeInOutExpo');
event.preventDefault();
});
});
Everything works great until I added other pages. I can't get the anchor link to strip out after a link like <a class="page-scroll" href="../#contact">Contact</a> on another external page.
I've searched high and low on SO but can't find a solution that works.
I don't totally care about the smooth scrolling if the link is from an external page. What I need most is to navigate / scroll to the id section of the main page (with offset to accommodate fixed navigation) and remove the anchor link from the browser URL window when the link is from an external page (from other pages on my website, or from other websites).
I've tried this also, but it likewise only works on internal links on to an id on the same page :
<script>
$(function() {
$('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 - 60
}, 1000);
return false;
}
}
});
});
</script>
I've also tried this with no luck :
<script type="text/javascript">
(function($) {
$(document).ready(function() {
var url=document.URL.split("#");
var ancher=url[1];
$('html, body').animate({
'scrollTop': $('#'+ancher).offset().top - 60
}, 5000);
});
})(jQuery);
</script>
Any New Year's Eve help would be most appreciated so I can get this project wrapped up!
It's possible I don't understand the extent of the question, but I believe you are trying to make it so the href doesn't fire on pages that are wanting to scroll but does on pages that are linking to other pages and not sections within the page itself. Perhaps something like this would work for you:
$(function() {
$('a.page-scroll').bind('click', function(event) {
var $anchor = $(this);
if ($anchor[0].href[0] === '#') {
$('html, body').stop().animate({
scrollTop: $($anchor.attr('href')).offset().top - 60
}, 1000, 'easeInOutExpo');
event.preventDefault();
return false;
} else {
return true;
}
});
});
What this does is look to see that the leading character in the href is a # implying that its a link to a section within itself.
Let me know if that helps and/or if I'm on the right track.
ps: I left the .bind in there because I don't know what version of jQuery you are on, but the newer syntax is to use .on
Happy New Year
Just to append slightly in regards to making it so that deep links to the main page go to the appropriate section but don't have the hash tag:
You can remove that 'hash' variable from window.location, but if you attempt to remove the hashtag entirely, it will cause the browser to refresh. This will also cause the viewer to lose the spot (thus removing the deep link's purpose).
To change the hash tag value (keeps the #):
window.location.hash = ''; // clears the hash tag
To remove the hash tag and its value (clears the # and everything past it):
window.location.href = window.location.href.substr(0, window.location.href.indexOf('#')); // this causes a browser refresh
And if it's not wholly apparent, you would run it on page load
$(document).ready(function() {
if (typeof(window.location.hash) !== 'undefined' && window.location.hash.length > 0) {
window.location.hash = ''; // will clear the hash anytime someone arrives with a hash tag
}
});
For a page with smooth scrolling try to use replaceState().
It will remove the hashtag at anchor link from the browser URL window (without page reloading).
// smooth scrolling
function scrollTo(selectors)
{
if(!$(selectors).length) return;
var selector_top = $(selectors).offset().top - 0;
$('html,body').animate({ scrollTop: selector_top }, 'slow');
}
// do scroll and clear the hash tag
$(window).on('load', function(){
if( typeof(location.hash) !== 'undefined' && location.hash.length ) {
scrollTo(location.hash);
history.replaceState(null, null, location.pathname);
}
});

Bootstrap scrolling navbar issues

The website I'm working on: zarwanhashem.com
You can find my previous question (which includes my code) here: Bootstrap one page website themev formatting problems
The selected answer solved my issues but I have another problem because of the jQuery adjustment with the -50. Now the navbar incorrectly indicates the page I am on. i.e. The navbar is supposed to darken the section that you are currently in. So if you click "about" it will take you to the about page and darken the about link in the navbar. But the link BEFORE the page you are on is highlighted because the -50 makes the navbar think that it is on the previous section. You can easily try this to see what I mean.
How can I fix this? Thanks. The reason I didn't add this onto my old question is because the person stopped looking at it.
Also please keep your explanations simple/dumb them down a little for me. I know very basic HTML and CSS, and I don't know any Javascript.
scrolling js:
//jQuery to collapse the navbar on scroll
$(window).scroll(function() {
if ($(".navbar").offset().top > 50) {
$(".navbar-fixed-top").addClass("top-nav-collapse");
} else {
$(".navbar-fixed-top").removeClass("top-nav-collapse");
}
});
//jQuery for page scrolling feature - requires jQuery Easing plugin
$(function() {
$('a.page-scroll').bind('click', function(event) {
var $anchor = $(this);
$('html, body').stop().animate({
scrollTop: $($anchor.attr('href')).offset().top -50
}, 1500, 'easeInOutExpo');
event.preventDefault();
});
});
js added at end of document as suggested by poster in previous question:
$(window).ready(function(){
$('div[class^="content-section"]').css('min-height', $(window).height());
})
You are putting the .active class on the wrong element somehow. You need to put the .active class on the clicked element. You should handle the active state with js. This is my solution based on your HTML structure but I'm sure there are different solutions as well.
$(document).on('click', '.page-scroll', function(event) {
var clicked = event.target; //get the clicked element
if($(clicked).closest('ul').hasClass('dropdown-menu')){ //check if clicked element is inside dropdown
$(clicked).closest('ul').parent().siblings().removeClass('active'); //remove active class from all
$(clicked).closest('ul').parent().addClass('active'); add active class on clicked element parent - in your case <li> tag.
}else{
$(clicked).parent().siblings().removeClass('active');
$(clicked).parent().addClass('active');
}
}
Let me know if this works for you.
EDIT after you posted your code
Try replacing your function with this:
$(function() {
$('a.page-scroll').bind('click', function(event) {
var $anchor = $(this);
$('html, body').stop().animate({
scrollTop: $($anchor.attr('href')).offset().top -50
}, 1500, 'easeInOutExpo');
if($($anchor).closest('ul').hasClass('dropdown-menu')){
$($anchor).closest('ul').parent().siblings().removeClass('active');
$($anchor).closest('ul').parent().addClass('active');
}else{
$($anchor).parent().siblings().removeClass('active');
$($anchor).parent().addClass('active');
}
event.preventDefault();
});
});
here is a work around this problem.
just change the contents of your scrolling-nav.js to the following:
//jQuery to collapse the navbar on scroll
$(window).scroll(function() {
if ($(".navbar").offset().top > 50) {
$(".navbar-fixed-top").addClass("top-nav-collapse");
} else {
$(".navbar-fixed-top").removeClass("top-nav-collapse");
}
});
//jQuery for page scrolling feature - requires jQuery Easing plugin
$(function() {
$('a.page-scroll').bind('click', function(event) {
var $anchor = $(this);
$('html, body').stop().animate({
scrollTop: $($anchor.attr('href')).offset().top -50
}, 1500, 'easeInOutExpo', function(){
$('ul.navbar-nav li, ul.dropdown-menu li').removeClass('active');
$($anchor).parent('li').addClass('active');
});
event.preventDefault();
});
});

Categories

Resources