123

I think this may not be possible, will try and explain as best as I can. I have a page containing tabs (jquery powered), controlled by the following:

I'm using this code, as provided by another user from a previous question.

<script type="text/javascript">
    $(function() {

     $('html, body').animate({scrollTop:0}); // this is my "fix"

        var tabContent = $(".tab_content");
        var tabs = $("#menu li");
        var hash = window.location.hash;
     tabContent.not(hash).hide();
        if(hash=="") {
      $('#tab1').fadeIn();
     }
        tabs.find('[href=' + hash + ']').parent().addClass('active');

        tabs.click(function() {
            $(this).addClass('active').siblings().removeClass('active');
            tabContent.hide();
            var activeTab = $(this).find("a").attr("href");

            $(activeTab).fadeIn();
           return false;
        });

    });
</script>

this code works great when I visit the "tabs" page directly.

however, I need to link to invidual tabs from other pages - so to do this, the code gets the window.location.hash then shows the appropiate tab.

the page doesn't "jump" to the anchor because of "return false".

this event is only triggered on a click event however. hence, if i visit my "tabs" from any other page, the "jump" effect is triggered. To combat this I automatically scroll to teh top of the page, but I would rather this didn't happen.

is there any way to simulate "return false" when the page loads, preventing the anchor "jump" from occuring.

hope this is clear enough.

thanks

Rubens Mariuzzo
  • 28,358
  • 27
  • 121
  • 148
Ross
  • 18,117
  • 7
  • 44
  • 64

16 Answers16

154

Does your fix not work? I'm not sure if I understand the question correctly - do you have a demo page? You could try:

if (location.hash) {
  setTimeout(function() {

    window.scrollTo(0, 0);
  }, 1);
}

Edit: tested and works in Firefox, IE & Chrome on Windows.
Edit 2: move setTimeout() inside if block, props @vsync.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
dave1010
  • 15,135
  • 7
  • 67
  • 64
  • 2
    hero! Yes, my "fix" did work, but the page would "scroll" (as my code told it to), and I would rather it didn't scroll. your code works perfectly. I hadn't looked at ScrollTo - is the function even necessary? it appears to work fine with just window.scrollTo(0, 0); – Ross Sep 07 '10 at 13:43
  • 3
    I tried it in Firefox on an empty page without the `setTimeout` and it didn't always work. You probably don't need it if it's in jQuery's `$(function() {` anyway. You don't really need the `location.hash`, but it's nice leaving it in so browsers may not have to do anything. Also reminds you what the scrollTo is for! – dave1010 Sep 07 '10 at 13:50
  • just tried it in FF/IE/Chrome and as you say, without the "function" it wasnt 100% consistent. Leaving it in and it works great in all 3. much obliged – Ross Sep 07 '10 at 13:51
  • You do need the 1 millisecond timeout even if it's in jQuery's `$(function` – chim May 28 '12 at 21:20
  • This trick doesn't seem to work with the IE9 engine. If you switch to IE7 or IE8 under debug it does work. Any ideas? – Lea Hayes Dec 16 '12 at 03:31
  • @Lea it's been a whike, but I think this worked fine with IE9. Does it work if you set the setTimeout to 1000ms? Have you ran your JS through http://jshint.com? – dave1010 Dec 17 '12 at 07:55
  • 2
    Today I had this same problem. I actually had to add the hash (identical to tab names/href values) to my nav links. I ended up adding a 1-char suffix to my tabs, and then substringing the values by 1 char (str.slice(0,-1) when comparing them to window.location.hash This way the hashes are different and no jumping occurs. – developer10 Apr 21 '14 at 20:44
  • 1
    The `if` should be outside, not inside. also, the minimum for interval is about 10, so 0 or 1 is the same..as 10. depending on the browser. – vsync Nov 06 '14 at 17:43
  • Thanks for this! The only issue I have is with IE which seems to want a longer delay on the setTimeout, 200ms seems to work ok. So I just add a ternary to use the 200ms delay for IE and 10ms on everything else. – Sujimichi Apr 08 '15 at 18:36
  • 2
    This does NOT prevent the browser to jump to the hashtag, you have to put an empty hashtag to prevent the 'jumpieness', see my answer here http://stackoverflow.com/a/29823913/826194 – Larzan May 31 '15 at 12:01
  • doesnt work for Chrome, browser stiil save scroll position and restore it – MeetJoeBlack Nov 23 '15 at 20:06
54

See my answer here: Stackoverflow Answer

The trick is to just remove the hashtag ASAP and store its value for your own use:

It is important that you do not put that part of the code in the $() or $(window).load() functions as it would be too late and the browser already has moved to the tag.

// store the hash (DON'T put this code inside the $() function, it has to be executed 
// right away before the browser can start scrolling!
var target = window.location.hash,
    target = target.replace('#', '');

// delete hash so the page won't scroll to it
window.location.hash = "";

// now whenever you are ready do whatever you want
// (in this case I use jQuery to scroll to the tag after the page has loaded)
$(window).on('load', function() {
    if (target) {
        $('html, body').animate({
            scrollTop: $("#" + target).offset().top
        }, 700, 'swing', function () {});
    }
});
DeveloperDan
  • 4,626
  • 9
  • 40
  • 65
Larzan
  • 9,389
  • 3
  • 42
  • 41
  • 1
    i had ran into a bug with FF where I was using max-height overflow-y on a div with a really long list, FFwould scroll down where the hidden node would be in the dom. This only used the top three lines of actual code (not the comments) and this corrected my issue with FireFox. – JQII Aug 24 '16 at 18:44
  • This is exactly what I was looking for. Modified it a little to fit my needs but a much better solution than scrolling back to the top. – Jeff Sterup Dec 09 '21 at 13:33
  • 1
    If you're having issues with the browser scrolling to the original loaded scroll pos after the page is loaded you can use history.scrollRestoration which is widely supported now. See this answer https://stackoverflow.com/a/45758297/2211053 – Gavin Aug 25 '22 at 13:01
12

I used dave1010's solution, but it was a bit jumpy when I put it inside the $().ready function. So I did this: (not inside the $().ready)

    if (location.hash) {               // do the test straight away
        window.scrollTo(0, 0);         // execute it straight away
        setTimeout(function() {
            window.scrollTo(0, 0);     // run it a bit later also for browser compatibility
        }, 1);
    }
lopsided
  • 2,370
  • 6
  • 28
  • 40
12
  1. Browser jumps to <element id="abc" /> if there are http://site.com/#abc hash in the address and the element with id="abc" is visible. So, you just should hide the element by default: <element id="anchor" style="display: none" />. If there is no visible element with id=hash at the page, the browser would not jump.

  2. Create a script that checks the hash in url, and then finds and shows the prrpopriate element (that is hidden by default).

  3. Disable default "hash-like link" behaviour by binding an onclick event to a link and specify return false; as a result of onclick event.

When I implemented tabs, I wrote something like that:

    <script>
    $(function () {         
        if (location.hash != "") {
            selectPersonalTab(location.hash);
        }
        else {
            selectPersonalTab("#cards");
        }
    });

    function selectPersonalTab(hash) {
        $("#personal li").removeClass("active");
        $("a[href$=" + hash + "]").closest("li").addClass("active");
        $("#personaldata > div").hide();
        location.hash = hash;
        $(hash).show();
    }

    $("#personal li a").click(function (e) {
        var t = e.target;
        if (t.href.indexOf("#") != -1) {
            var hash = t.href.substr(t.href.indexOf("#"));
            selectPersonalTab(hash);
            return false;
        }
    });
</script>
Sergei Smirnov
  • 151
  • 1
  • 3
12

There are other ways of tracking what tab you're on; perhaps setting a cookie, or a value in a hidden field, etc etc.

I would say that if you don't want the page jumping on load, you would be better off using one of these other options rather than the hash, because the main reason for using the hash in preference to them is to allow exactly what you're wanting to block.

Another point - the page won't jump if your hash links don't match the names of the tags in the document, so perhaps if you want to keep using the hash you could manipulate the content so that the tags are named differently. If you use a consistent prefix, you will still be able to use Javascript to jump between then.

Hope that helps.

Spudley
  • 166,037
  • 39
  • 233
  • 307
10

None of answers do not work good enough for me, I see page jumping to anchor and then to top for some solutions, some answers do not work at all, may be things changed for years. Hope my function will help to someone.

/**
 * Prevent automatic scrolling of page to anchor by browser after loading of page.
 * Do not call this function in $(...) or $(window).on('load', ...),
 * it should be called earlier, as soon as possible.
 */
function preventAnchorScroll() {
    var scrollToTop = function () {
        $(window).scrollTop(0);
    };
    if (window.location.hash) {
        // handler is executed at most once
        $(window).one('scroll', scrollToTop);
    }

    // make sure to release scroll 1 second after document readiness
    // to avoid negative UX
    $(function () {
        setTimeout(
            function () {
                $(window).off('scroll', scrollToTop);
            },
            1000
        );
    });
}
Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
kdmitry
  • 359
  • 3
  • 12
  • The best solution I found so far. And actually saw a lot. – MarkSkayff Apr 10 '18 at 21:02
  • Thank you for this! Also only solution that worked for me after hours of attempts, but the best part of this solution for me is that I don't have to remove the hash. – Chris Vecchio Mar 20 '20 at 16:24
  • This dont work if the scroller is already at the top of the page on page load then it will scroll down like usual and after page is done it jumps to top. Anyone know way this is the case ? – robgha01 Jun 04 '20 at 13:36
  • @robgha01 please make sure that function is executed as soon as possible and does not wait for any event, this is wrong: `$(document).ready(function () { preventAnchorScroll(); });`, just call the function at very beginning of your JavaScript. I tested it now, works for me in Chrome, Firefox and Opera. – kdmitry Jun 05 '20 at 16:51
6

dirty CSS only fix to stay scrolled up every time the anchor is used (not useful if you still want to use anchors for scroll-jumps, very useful for deeplinking):

.elementsWithAnchorIds::before {
   content: "";
   display: block;
   height: 9999px;
   margin-top: -9999px; //higher thin page height
}
anotheryou
  • 61
  • 1
  • 1
3

While the accepted answer does work well, I did find that sometimes, especially on pages containing large images that the scroll bar will jump about wildly using

 window.scrollTo(0, 0);

Which can be quite distracting for the user.

The solution I settled on in the end is actually pretty simple, and that's to use a different ID on the target to the one used in location.hash

Eg:

Here is the link on some other page

<a href="/page/with/tabs#tab2">Some info found in tab2 on tabs page</a>

So of course if there is an element with an ID of tab2 on the tabs page the window will jump to it on load.

So you can append something to the ID to prevent it:

<div id="tab2-noScroll">tab2 content</div>

And then you can append "-noScroll" to location.hash in the javascript:

<script type="text/javascript">
    $(function() {

        var tabContent = $(".tab_content");
        var tabs = $("#menu li");
        var hash = window.location.hash;


     tabContent.not(hash + '-noScroll').hide();                           
        if(hash=="") {       //^ here
      $('#tab1-noScroll').fadeIn();
     }
        tabs.find('[href=' + hash + ']').parent().addClass('active');

        tabs.click(function() {
            $(this).addClass('active').siblings().removeClass('active');
            tabContent.hide();
            var activeTab = $(this).find("a").attr("href") + '-noScroll'; 
                                                               //^ and here

            $(activeTab).fadeIn();
           return false;
        });

    });
</script>
andrew
  • 9,313
  • 7
  • 30
  • 61
2

In my case because of using anchor link for CSS popup I've used this way:

Just save the pageYOffset first thing on click and then set the ScrollTop to that:

$(document).on('click','yourtarget',function(){
        var st=window.pageYOffset;
        $('html, body').animate({
                'scrollTop' : st
            });
});
  • After trying the other solutions this was the one which worked for me, in my case the website I was working with used the # in the url to load a specific section of a checkout. Rather than modify the checkout functionality for the hash in the url I just used this. Thanks – chris c May 24 '19 at 05:46
1

I think I've found a way for UI Tabs without redoing the functionality. So far I haven't found any problems.

    $( ".tabs" ).tabs();
    $( ".tabs" ).not(".noHash").bind("tabsshow", function(event, ui) { 
        window.location.hash = "tab_"+ui.tab.hash.replace(/#/,"");
        return false;
    });

    var selectedTabHash = window.location.hash.replace(/tab_/,"");
    var index = $( ".tabs li a" ).index($(".tabs li a[href='"+selectedTabHash+"']"));
    $( ".tabs" ).not(".noHash").tabs('select', index);

All within a ready event. It prepends "tab_" for the hash but work both when clicked and page loaded with hash.

JRomero
  • 4,878
  • 1
  • 27
  • 49
1

This will work with bootstrap v3

$(document).ready(function() {
  if ($('.nav-tabs').length) {
    var hash = window.location.hash;
    var hashEl = $('ul.nav a[href="' + hash + '"]');
    hash && hashEl.tab('show');

    $('.nav-tabs a').click(function(e) {
      e.preventDefault();
      $(this).tab('show');
      window.location.hash = this.hash;
    });

    // Change tab on hashchange
    window.addEventListener('hashchange', function() {
      var changedHash = window.location.hash;
      changedHash && $('ul.nav a[href="' + changedHash + '"]').tab('show');
    }, false);
  }
});
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/css/bootstrap.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.0/js/bootstrap.min.js"></script>
<div class="container">
<ul class="nav nav-tabs">
  <li class="active"><a data-toggle="tab" href="#home">Home</a></li>
  <li><a data-toggle="tab" href="#menu1">Menu 1</a></li>
</ul>

<div class="tab-content">
  <div id="home" class="tab-pane fade in active">
    <h3>HOME</h3>
    <p>Some content.</p>
  </div>
  <div id="menu1" class="tab-pane fade">
    <h3>Menu 1</h3>
    <p>Some content in menu 1.</p>
  </div>
</div>
</div>
Lasithds
  • 2,161
  • 25
  • 39
0

Another approach

Try checking if the page has been scrolled and only then reset position:

var scrolled = false;

$(window).scroll(function(){
  scrolled = true;
});

if ( window.location.hash && scrolled ) {
  $(window).scrollTop( 0 );
}

Heres a demo

hitautodestruct
  • 20,081
  • 13
  • 69
  • 93
0

I did not have much success with the above setTimeout methods in Firefox.

Instead of a setTimeout, I've used an onload function:

window.onload = function () {
    if (location.hash) {
        window.scrollTo(0, 0);
    }
};

It's still very glitchy, unfortunately.

bozdoz
  • 12,550
  • 7
  • 67
  • 96
0

I have not had any consistent success with these solutions.

Apparently due to the fact that the jump happens before Javascript => reference(http://forum.jquery.com/topic/preventing-anchor-jump)

My solution is to position all anchors at the top by default with CSS.

.scroll-target{position:fixed;top:0;left:0;}

Then use jquery to scroll to the parent of the target anchor, or a sibling(maybe an empty em tag)

<a class="scroll-target" name="section3"></a><em>&nbsp</em>

jquery example for scrolling for when URL is entered with hash
(page must not be already loaded in window/tab, this covers links from other/outside sources)

setTimeout(function() {
    if (window.location.hash) {               
        var hash = window.location.hash.substr(1);   
        var scrollPos = $('.scroll-target[name="'+hash+'"]').siblings('em').offset().top; 
        $("html, body").animate({ scrollTop: scrollPos }, 1000);    
    }
}, 1);

Also you'll want to prevent default for anchor clicks while on the page and then scroll to their targets

<a class="scroll-to" href="#section3">section three</a>

and jquery

$('a.scroll-to').click(function(){
    var target = $(this).attr('href').substr(1);
    var scrollPos = $('.scroll-target[name="'+target+'"]').siblings('em').offset().top; 
    $("html, body").animate({ scrollTop: scrollPos }, 1000);
    return false;
});

The good thing about this method is the anchor tag targets, remain structurally beside the relevant content, although their CSS position is at the top.

This should mean that search engine crawlers won't have a problem.

Cheers, I hope this helps
Gray

0

Try this to prevent client from any kind of vertical scrolling on hashchanged (inside your event handler):

var sct = document.body.scrollTop;
document.location.hash = '#next'; // or other manipulation
document.body.scrollTop = sct;

(browser redraw)

andy
  • 1
0

Solved my promlem by doing this:

// navbar height 
var navHeigth = $('nav.navbar').height();    

// Scroll to anchor function
var scrollToAnchor = function(hash) {
  // If got a hash
  if (hash) {
    // Scroll to the top (prevention for Chrome)
    window.scrollTo(0, 0);
    // Anchor element
    var term = $(hash);

    // If element with hash id is defined
    if (term) {

      // Get top offset, including header height
      var scrollto = term.offset().top - navHeigth;

      // Capture id value
      var id = term.attr('id');
      // Capture name value
      var name = term.attr('name');

      // Remove attributes for FF scroll prevention
      term.removeAttr('id').removeAttr('name');
      // Scroll to element
      $('html, body').animate({scrollTop:scrollto}, 0);

      // Returning id and name after .5sec for the next scroll
      setTimeout(function() {
        term.attr('id', id).attr('name', name);
      }, 500);
    }
  }
};

// if we are opening the page by url
if (location.hash) {
  scrollToAnchor(location.hash);
}

// preventing default click on links with an anchor
$('a[href*="#"]').click(function(e) {
  e.preventDefault();
  // hash value
  var hash = this.href.substr(this.href.indexOf("#"));
  scrollToAnchor(hash);
});`
xottabych007
  • 142
  • 2
  • 10