Search code examples
javascriptjquerysmooth-scrolling

jQuery code disabling links to other web pages


I have some jQuery code that allows for smoothing scrolling on my web pages.

$(document).ready(function(){
    // browser window scroll position (in pixels) where the button will appear
    var offset = 200,
        
    // duration of the animation (in ms)
    scroll_top_duration = 700,
    
    // bind with the button
    $back_to_top = $('.back-to-top');

    // display and hide the button
    $(window).scroll(function(){
        ( $(this).scrollTop() > offset ) ? $back_to_top.addClass('make-visible-btt') : $back_to_top.removeClass('make-visible-btt');
    });

    //smooth scroll to top
    $back_to_top.on('click', function(event){
        event.preventDefault();
        $('body,html').animate({
            scrollTop: 0 ,
            }, scroll_top_duration
        );
    });
});

$(document).ready(function() {
  // browser window scroll position (in pixels) where the button will appear
  var offset = 200,

    // duration of the animation (in ms)
    scroll_top_duration = 700,

    // bind with the button
    $back_to_top = $('.back-to-top');

  // display and hide the button
  $(window).scroll(function() {
    ($(this).scrollTop() > offset) ? $back_to_top.addClass('make-visible-btt'): $back_to_top.removeClass('make-visible-btt');
  });

  //smooth scroll to top
  $back_to_top.on('click', function(event) {
    event.preventDefault();
    $('body,html').animate({
      scrollTop: 0,
    }, scroll_top_duration);
  });
});
.back-to-top {
  position: fixed;
  bottom: 20px;
  right: 20px;
  display: inline-block;
  height: 40px;
  width: 40px;
  background: url(../images/back-to-top.png) no-repeat;
  background-size: contain;
  overflow: hidden;
  text-indent: 100%;
  white-space: nowrap;
  border: 1px solid #aaa;
  visibility: hidden;
  opacity: 0;
  transition: opacity .3s 0s, visibility 0s .3s;
}

.make-visible-btt {
  visibility: visible;
  opacity: 1;
  transition: opacity 1s 0s, visibility 0s 0s;
}

.section {
  border: 1px solid black;
  background-color: #ededed;
  height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<a href="#last">jump to last section</a>
<div class="section"></div>
<div class="section"></div>
<div class="section"></div>
<div class="section"></div>
<div class="section" id="last"></div>
<a href="/my-web-page/" class="back-to-top">Back to Top</a>

The code works fine. When users click the "back to top" button on the page, they're automatically smooth scrolled to the top. All good.

enter image description here

The code above, however, doesn't work for in-page links.

<a href="/my-web-page/#section-3">text text text</a>

So if a user clicks on a link like the one above, the pages instantly jumps to that section (which is the default behavior).

I added this code to fix that problem:

$(document).ready(function(){
  $('a[href*="\\#"]').on('click', function(event){
    var href = $(event.target).closest('a').attr('href'), 
        skip = false;
    if (!skip) {
      event.preventDefault();
      $('html,body').animate({scrollTop:$(this.hash).offset().top}, 500);
    }
  });
});

$(document).ready(function() {
  // browser window scroll position (in pixels) where the button will appear
  var offset = 200,

    // duration of the animation (in ms)
    scroll_top_duration = 700,

    // bind with the button
    $back_to_top = $('.back-to-top');

  // display and hide the button
  $(window).scroll(function() {
    ($(this).scrollTop() > offset) ? $back_to_top.addClass('make-visible-btt'): $back_to_top.removeClass('make-visible-btt');
  });

  //smooth scroll to top
  $back_to_top.on('click', function(event) {
    event.preventDefault();
    $('body,html').animate({
      scrollTop: 0,
    }, scroll_top_duration);
  });
});
$(document).ready(function(){
  $('a[href*="\\#"]').on('click', function(event){
    var href = $(event.target).closest('a').attr('href'), 
        skip = false;
    if (!skip) {
      event.preventDefault();
      $('html,body').animate({scrollTop:$(this.hash).offset().top}, 500);
    }
  });
});
.back-to-top {
  position: fixed;
  bottom: 20px;
  right: 20px;
  display: inline-block;
  height: 40px;
  width: 40px;
  background: url(../images/back-to-top.png) no-repeat;
  background-size: contain;
  overflow: hidden;
  text-indent: 100%;
  white-space: nowrap;
  border: 1px solid #aaa;
  visibility: hidden;
  opacity: 0;
  transition: opacity .3s 0s, visibility 0s .3s;
}

.make-visible-btt {
  visibility: visible;
  opacity: 1;
  transition: opacity 1s 0s, visibility 0s 0s;
}

.section {
  border: 1px solid black;
  background-color: #ededed;
  height: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<a href="#last">jump to last section</a>
<div class="section"></div>
<div class="section"></div>
<div class="section"></div>
<div class="section"></div>
<div class="section" id="last"></div>
<a href="/my-web-page/" class="back-to-top">Back to Top</a>

So now that's fixed.

But that second code block has created two new problems:

  1. Links with a fragment identifier (e.g., #section-of-page) no longer update in the browser address bar. For example, within a page, on click, the page does scroll smoothly to the target section (so it works), but the web address stays fixed at www.website.com/whatever, when it should update to www.website.com/whatever#section-of-page.

  2. Links with a fragment identifier don't work across pages. In other words, /this-web-page#section-of-page works fine. But /another-web-page#section-of-page and www.another-website.com/whatever#section-of-page both fail (click does nothing).

These problems didn't exist before adding that second code block.

Looking for some guidance on how to fix these problems.

Also if you can suggest a way to integrate all functions into one block of code, that would be great.

Lastly, I know about the CSS scroll-behavior property, but it's still very rudimentary (can't adjust any settings), so I'd rather stick with JS for now.

Thanks.


Solution

  • You can check if the href points to an internal location by creating a URL object from it and checking its host against window.location.host. Then call event.preventDefault and perform smooth scrolling only in that case.

    The callback function (third argument) to $.animate can be used to set the hash properly after the scrolling effect.

    if (new URL(href, window.location).host === window.location.host) {
        event.preventDefault();
        $('html,body').animate({
            scrollTop: $(this.hash).offset().top
        }, 500, function() {
            window.location.hash = new URL(href, window.location).hash;
        });
    }
    

    $(document).ready(function() {
      // browser window scroll position (in pixels) where the button will appear
      var offset = 200,
    
        // duration of the animation (in ms)
        scroll_top_duration = 700,
    
        // bind with the button
        $back_to_top = $('.back-to-top');
    
      // display and hide the button
      $(window).scroll(function() {
        ($(this).scrollTop() > offset) ? $back_to_top.addClass('make-visible-btt'): $back_to_top.removeClass('make-visible-btt');
      });
    
      //smooth scroll to top
      $back_to_top.on('click', function(event) {
        event.preventDefault();
        $('body,html').animate({
          scrollTop: 0,
        }, scroll_top_duration);
      });
    });
    $(document).ready(function(){
      $('a[href*="\\#"]').on('click', function(event){
        var href = $(event.target).closest('a').attr('href');
        if (new URL(href, window.location).host === window.location.host) {
            event.preventDefault();
            $('html,body').animate({
                scrollTop: $(this.hash).offset().top
            }, 500, function() {
                window.location.hash = new URL(href, window.location).hash;
            });
        }
      });
    });
    .back-to-top {
      position: fixed;
      bottom: 20px;
      right: 20px;
      display: inline-block;
      height: 40px;
      width: 40px;
      background: url(../images/back-to-top.png) no-repeat;
      background-size: contain;
      overflow: hidden;
      text-indent: 100%;
      white-space: nowrap;
      border: 1px solid #aaa;
      visibility: hidden;
      opacity: 0;
      transition: opacity .3s 0s, visibility 0s .3s;
    }
    
    .make-visible-btt {
      visibility: visible;
      opacity: 1;
      transition: opacity 1s 0s, visibility 0s 0s;
    }
    
    .section {
      border: 1px solid black;
      background-color: #ededed;
      height: 200px;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <a href="#last">jump to last section</a>
    <a href="https://example.com">External link</a>
    <div class="section"></div>
    <div class="section"></div>
    <div class="section"></div>
    <div class="section"></div>
    <div class="section" id="last"></div>
    <a href="/my-web-page/" class="back-to-top">Back to Top</a>