Accounting for a fixed header with animate.scrolltop and (target).offset().top;
Asked Answered
F

5

14

This should be a pretty basic question, but i've thrown most of my morning at it, and at this point I'm close to throwing in the towel. I have not even a little bit of js foo -- but I found a nicely commented chunk of code that I'm hoping to use to animate anchor links it is:

$(document).ready(function() {
$('a[href*=#]').bind('click', function(e) {
e.preventDefault(); //prevent the "normal" behaviour which would be a "hard" jump

var target = $(this).attr("href"); //Get the target

var scrollToPosition = $(target).offset().top;

// perform animated scrolling by getting top-position of target-element and set it     as scroll target
$('html, body').stop().animate({ scrollTop: scrollToPosition}, 600, function() {
     location.hash = target;  //attach the hash (#jumptarget) to the pageurl
});

return false;

 });
});

I'm trying to get it to land 30px above the offset().top -- I tried

$('html, body').stop().animate({ scrollTop: scrollToPosition -30}, 600,

Which almost works -- it goes to the right place but then bounces back.

I've also tried

scrollTop: $(target).offset().top - 20 },

I've also tried

scrollTop: $(hash).offset().top + $('#access').outerHeight()

Which doesn't seem to change anything.

It seems like the answer might be here: JQuery page scroll issue with fixed header but I just can't quite seem to get it.

I know this is similar to other questions -- but I've gone through what I could find and I'm illiterate enough that I haven't been able to copy/paste in anything that fixes the issue.

I'd be incredibly grateful for a solution.

Many thanks,

Martin

PS

This other chunk of code I found does work but it's stripping the hashtag out, which makes it mostly useless.

$(function(){
$('a[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) {
            var targetOffset = $target.offset().top;
            $('html,body').animate({scrollTop: targetOffset - 30}, 1000);
            return false;
        }
    }
  });
});
Forge answered 30/1, 2012 at 18:11 Comment(0)
C
20

EDITED: You just need to detect the height of the fixed header and subtract that from the scrollToPosition which you were doing correctly. The issue is the window.location.hash = "" + target; jumps the page to top of the element with that id. So if you animate there like you were doing and then change to that hash it will "bounce back" like you described. Here is the first way we can combat this:

// Get the height of the header
var headerHeight = $("div#header").height();

// Attach the click event
$('a[href*=#]').bind("click", function(e) {
    e.preventDefault();

    var target = $(this).attr("href"); //Get the target
    var scrollToPosition = $(target).offset().top - headerHeight;

    $('html').animate({ 'scrollTop': scrollToPosition }, 600, function(){
        window.location.hash = "" + target;
        // This hash change will jump the page to the top of the div with the same id
        // so we need to force the page to back to the end of the animation
        $('html').animate({ 'scrollTop': scrollToPosition }, 0);
    });

    $('body').append("called");
});

Here's a new jsfiddle for this first method: http://jsfiddle.net/yjcRv/1/

FURTHER EDIT: An even better way to control hash change events is to use a plugin like jQuery Address. With this you can utilise your hashchange events much more. Here's an example usage:

// Get the height of the header
var headerHeight = $("div#header").height();

$.address.change(function(evt){
    var target = "#" + evt["pathNames"][0]; //Get the target from the event data

    // If there's been some content requested go to it…else go to the top
    if(evt["pathNames"][0]){
        var scrollToPosition = $(target).offset().top - headerHeight;
        $('html').animate({ 'scrollTop': scrollToPosition }, 600);
    }else{
        $('html').animate({ 'scrollTop': '0' }, 600);
    }

    return false;
});

// Attach the click event
$('a').bind("click", function(e) {
    // Change the location
    $.address.value($(this).attr("href"));

    return false;
});

Live example here: http://www.vdotgood.com/stack/user3444.html

NOTE: You don't need to add the hash to your links href attribute now. Here's a link that you could target with a jQuery selector:

<!-- This is correct -->
<a href="/target" class="myclass">Target</a>

<!-- These are incorrect -->
<a href="/#/target" class="myclass">Target</a>

<a href="#/target" class="myclass">Target</a>

To target this link you'd use a selector like:

$("a.myclass").click(function(){
    $.address.value($(this).attr("href"));
    return false;
});

jQuery Address does in fact look for links that have the following attribute:

<a href="/target" rel="address:/target">Target</a>

The rel attribute here contains address: followed by a relative url defined by you in this case /target. If you use this, jQuery Address will detect the link and fire the hash change event automatically.

Calebcaledonia answered 30/1, 2012 at 18:52 Comment(9)
Thank you so much Steve. I swear I'm going to learn enough to understand what I'm copyandpasting, hopefully this year, but this just saved me no end of headaches. Cheers -mForge
Quick question: The code works perfectly, but the hash added to the url is coming up as "#undefined" - any ideas?Forge
No worries mate. Just keep at it, get onto some good online tutorials on a regular basis and you'll soak up the knowledge you're after! Defo check out the tutsplus websites (Webdesigntuts, Nettuts) and Team Treehouse web design/developement learning from Carsonified, all great free resources.Calebcaledonia
Huh -- doesn't seem to be working. But then, I just tried it in chrome (as opposed to ff) and there's a whole lotta nothing happening -- so larger fish to fry, it seems.Forge
Hey Steve, Thanks and thanks! Going with the JQuery address ex , any idea why it would be adding a "/#" -- address ended up as /#/#myanchor -- i've been taking out lines one at a time...but that's the best I can do.Forge
It may because your links have a hash in the href. I've added some more info to the answer for you :)Calebcaledonia
To get it working in Chrome / Safari and other webkit browsers, you need to change both $('html').animate to $('html,body').animateConsol
perfect... it saved my time :)Driver
This example works great but in the console, I get an error: Uncaught TypeError: Cannot read properties of undefined (reading '0') Is it because we need to define on the var target line we need to check if it's defined or not?Intradermal
N
6

I know this is an old question (kind of) but I ran into a similar problem with a fixed dropdown navigation on a website. Note this is a smooth scroll code snippet, though you could easily make it automatic by changing the animation speed.

jQuery:

$('body').on('click','a[href^="#"]',function(event){
    event.preventDefault();
    var target_offset = $(this.hash).offset() ? $(this.hash).offset().top : 0;
    //change this number to create the additional off set        
    var customoffset = 75
    $('html, body').animate({scrollTop:target_offset - customoffset}, 500);
});

I have used this chunk of code for a long time without any issues. The only thing I dislike about it, is it will grab ANY # tag. So in a plugin like Flexslider plugin where the navigation uses #'s I manually strip them from the plugin.

Neibart answered 2/1, 2013 at 22:8 Comment(0)
T
3

I have tweaked the original script from http://www.paulund.co.uk/smooth-scroll-to-internal-links-with-jquery. It works marvels but you cannot set a delay as it is.

var headerHeight = $("header").height();


        $(document).ready(function(){
    $('a[href^="#"]').on('click',function (e) {
        e.preventDefault();

        var target = this.hash,
        $target = $(target);

        $('html, body').stop().animate({
            'scrollTop': $target.offset().top - headerHeight
        }, 1200, 'swing', function () {
            window.location.hash = target ;
        });
    });
});

Yes, I'm a bit late but this problem just occurred to me... Cheers!

Tereus answered 19/2, 2014 at 19:42 Comment(0)
D
2

I had a similar problem with one of our subnavs. I wrote this code and it seemed to do it for me.

$(document).ready(function() {
var hashId = window.location.hash;
var sectionid = $(hashId);
if(sectionid.length != 0 {
{
$('html,body').animate({scrollTop: $(hashId).offset().top - 180}, 500);
}
}});

You can change 180 to your desired offset, and 500 to change the time it takes to do the animation.

Diddle answered 7/3, 2022 at 10:56 Comment(0)
A
0

On the odd chance that anyone will see this, 8 years after the question was asked and 6 years since the last comment, I'll ask a follow-up question...

I'm using a Bootstrap template that uses this javascript for scrolling to anchors and then collapsing the (previously expanded) mobile menu:

    $pageScrollLink.on('click', function(e){
        var anchor = $(this),
            target = anchor.attr('href');
        pageScroll(target);
        e.preventDefault();
    });

    function pageScroll(target){
        var ww = Math.max($window.width(), window.innerWidth),
                offset = ww > 992 ? navHeightShrink : navHeight;

        $htmlBody.stop().animate({
            scrollTop: $(target).offset().top - (offset - 1)
        }, 1000, 'easeInOutExpo');

        // Automatically retract the navigation after clicking on one of the menu items.
        $navbarCollapse.collapse('hide');
    };

As long as 'href' is a plain old "#anchor" things work fine. But if I try to use an anchor link in a different file, say "otherfile.php#myanchor" or even a fully qualified URL, say "https://somesite.com/index.php#section3" it fails, throwing an error complaining about "top" being undefined or something.

Does anyone know why it only works if the href is #anchor and not another format?

Thanks.

Atbara answered 22/5, 2020 at 19:40 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.