Sửa đổi location.hash mà không cần cuộn trang


147

Chúng tôi đã có một vài trang sử dụng ajax để tải nội dung và có một vài lần chúng tôi cần liên kết sâu vào một trang. Thay vì có một liên kết đến "Người dùng" và bảo mọi người nhấp vào "cài đặt", thật hữu ích khi có thể liên kết mọi người với user.aspx # cài đặt

Để cho phép mọi người cung cấp cho chúng tôi các liên kết chính xác đến các phần (để được hỗ trợ kỹ thuật, v.v.) Tôi đã thiết lập nó để tự động sửa đổi hàm băm trong URL mỗi khi nhấp vào nút. Vấn đề duy nhất tất nhiên là khi điều này xảy ra, nó cũng cuộn trang đến phần tử này.

Có cách nào để vô hiệu hóa điều này? Dưới đây là cách tôi đang làm điều này cho đến nay.

$(function(){
    //This emulates a click on the correct button on page load
    if(document.location.hash){
     $("#buttons li a").removeClass('selected');
     s=$(document.location.hash).addClass('selected').attr("href").replace("javascript:","");
     eval(s);
    }

    //Click a button to change the hash
    $("#buttons li a").click(function(){
            $("#buttons li a").removeClass('selected');
            $(this).addClass('selected');
            document.location.hash=$(this).attr("id")
            //return false;
    });
});

Tôi đã hy vọng điều return false;đó sẽ ngăn trang cuộn - nhưng nó chỉ khiến liên kết không hoạt động. Vì vậy, bây giờ chỉ cần nhận xét để tôi có thể điều hướng.

Có ý kiến ​​gì không?

Câu trả lời:


111

Bước 1: Bạn cần gỡ rối ID nút, cho đến khi hàm băm được thiết lập. Điều này được thực hiện bằng cách xóa ID khỏi nút trong khi hàm băm đang được đặt, sau đó thêm lại.

hash = hash.replace( /^#/, '' );
var node = $( '#' + hash );
if ( node.length ) {
  node.attr( 'id', '' );
}
document.location.hash = hash;
if ( node.length ) {
  node.attr( 'id', hash );
}

Bước 2: Một số trình duyệt sẽ kích hoạt cuộn dựa trên vị trí nút ID'd được nhìn thấy lần cuối, do đó bạn cần giúp đỡ họ một chút. Bạn cần thêm một phần bổ sung divvào đầu khung nhìn, đặt ID của nó thành hàm băm và sau đó cuộn mọi thứ lại:

hash = hash.replace( /^#/, '' );
var fx, node = $( '#' + hash );
if ( node.length ) {
  node.attr( 'id', '' );
  fx = $( '<div></div>' )
          .css({
              position:'absolute',
              visibility:'hidden',
              top: $(document).scrollTop() + 'px'
          })
          .attr( 'id', hash )
          .appendTo( document.body );
}
document.location.hash = hash;
if ( node.length ) {
  fx.remove();
  node.attr( 'id', hash );
}

Bước 3: Gói nó trong một plugin và sử dụng nó thay vì viết vào location.hash...


1
Không, điều đang xảy ra ở bước 2 là một div ẩn được tạo và đặt tại vị trí hiện tại của cuộn. Trực quan giống như vị trí: fixed / top: 0. Do đó, thanh cuộn được "di chuyển" đến chính xác vị trí hiện tại.
Borgar

3
Giải pháp này thực sự hoạt động tốt - tuy nhiên dòng: top: $ .scroll (). Top + 'px' Nên là: top: $ (window) .scrollTop () + 'px'
Mark Perkins

27
Sẽ rất hữu ích khi biết trình duyệt nào cần bước 2 ở đây.
djc

1
Cảm ơn bạn. Nó đánh đố tôi rằng bạn đang nối thêm div fx trước khi xóa id của nút đích. Điều đó có nghĩa là ngay lập tức có ID trùng lặp trong tài liệu. Có vẻ như là một vấn đề tiềm năng, hoặc ít nhất là cách cư xử tồi tệ;)
Ben

1
Cảm ơn bạn rất nhiều, điều này cũng đã giúp tôi. Nhưng tôi cũng nhận thấy một tác dụng phụ khi nó hoạt động: Tôi thường cuộn bằng cách khóa "nút chuột giữa" và chỉ cần di chuyển chuột theo hướng tôi muốn cuộn. Trong Google Chrome, điều này làm gián đoạn luồng cuộn. Có thể có một cách giải quyết ưa thích cho điều đó?
YMMD

99

Sử dụng history.replaceStatehoặc history.pushState* để thay đổi hàm băm. Điều này sẽ không kích hoạt bước nhảy đến phần tử liên quan.

Thí dụ

$(document).on('click', 'a[href^=#]', function(event) {
  event.preventDefault();
  history.pushState({}, '', this.href);
});

Bản trình diễn trên JSFiddle

* Nếu bạn muốn hỗ trợ lịch sử tiến và lùi

Hành vi lịch sử

Nếu bạn đang sử dụng history.pushStatevà bạn không muốn cuộn trang khi người dùng sử dụng các nút lịch sử của trình duyệt (tiến / lùi), hãy kiểm tra scrollRestorationcài đặt thử nghiệm (chỉ Chrome 46+) .

history.scrollRestoration = 'manual';

Hỗ trợ trình duyệt


5
replaceStatecó lẽ là cách tốt hơn để đi đến đây Sự khác biệt là pushStatethêm một mục vào lịch sử của bạn trong khi replaceStatekhông.
Aakil Fernandes

Cảm ơn @AakilFernandes bạn đã đúng. Tôi vừa cập nhật câu trả lời.
HaNdTriX

Nó không phải luôn luôn bodylà cuộn, đôi khi nó là documentElement. Xem ý chính này của Diego Perini
Matijs 10/03/2015

1
có ý tưởng nào về eg8 / 9 ở đây không?
dùng151496

1
@ user151496 kiểm tra: stackoverflow.com/revutions/ từ
HaNdTriX

90

Tôi nghĩ rằng tôi có thể đã tìm thấy một giải pháp khá đơn giản. Vấn đề là hàm băm trong URL cũng là một yếu tố trên trang mà bạn được cuộn đến. nếu tôi chỉ thêm một số văn bản vào hàm băm, thì bây giờ nó không còn tham chiếu đến một phần tử hiện có!

$(function(){
    //This emulates a click on the correct button on page load
    if(document.location.hash){
     $("#buttons li a").removeClass('selected');
     s=$(document.location.hash.replace("btn_","")).addClass('selected').attr("href").replace("javascript:","");
     eval(s);
    }

    //Click a button to change the hash
    $("#buttons li a").click(function(){
            $("#buttons li a").removeClass('selected');
            $(this).addClass('selected');
            document.location.hash="btn_"+$(this).attr("id")
            //return false;
    });
});

Bây giờ URL xuất hiện như page.aspx#btn_elementIDkhông phải là một ID thực trên trang. Tôi chỉ xóa "btn_" và nhận ID phần tử thực tế


5
Giải pháp tuyệt vời. Hầu hết không đau đớn của rất nhiều.
Swader

Lưu ý rằng nếu bạn đã cam kết với URL.aspx # ElementID vì một số lý do, bạn có thể đảo ngược kỹ thuật này và gửi lại "btn_" cho tất cả các ID của bạn
joshuahedlund

2
Không hoạt động nếu bạn đang sử dụng: bộ chọn giả giả đích trong CSS.
Jason T Featheringham

1
Yêu giải pháp này! Chúng tôi đang sử dụng hàm băm URL cho điều hướng dựa trên AJAX, chuẩn bị ID có /vẻ hợp lý.
Connell

3

Một đoạn mã gốc của bạn:

$("#buttons li a").click(function(){
    $("#buttons li a").removeClass('selected');
    $(this).addClass('selected');
    document.location.hash=$(this).attr("id")
});

Thay đổi điều này thành:

$("#buttons li a").click(function(e){
    // need to pass in "e", which is the actual click event
    e.preventDefault();
    // the preventDefault() function ... prevents the default action.
    $("#buttons li a").removeClass('selected');
    $(this).addClass('selected');
    document.location.hash=$(this).attr("id")
});

Điều này sẽ không hoạt động vì cài đặt thuộc hashtính sẽ khiến trang cuộn xuống.
jordanbtucker

3

Gần đây tôi đã xây dựng một băng chuyền dựa vào window.location.hashđể duy trì trạng thái và phát hiện ra rằng các trình duyệt Chrome và webkit sẽ buộc cuộn (thậm chí đến một mục tiêu không nhìn thấy được) với một cú giật khó xử khi window.onhashchangesự kiện được bắn.

Ngay cả khi cố gắng đăng ký một trình xử lý mà dừng propogation:

$(window).on("hashchange", function(e) { 
  e.stopPropogation(); 
  e.preventDefault(); 
});

Không có gì để ngăn chặn hành vi trình duyệt mặc định. Giải pháp tôi tìm thấy là sử dụng window.history.pushStateđể thay đổi hàm băm mà không kích hoạt các tác dụng phụ không mong muốn.

 $("#buttons li a").click(function(){
    var $self, id, oldUrl;

    $self = $(this);
    id = $self.attr('id');

    $self.siblings().removeClass('selected'); // Don't re-query the DOM!
    $self.addClass('selected');

    if (window.history.pushState) {
      oldUrl = window.location.toString(); 
      // Update the address bar 
      window.history.pushState({}, '', '#' + id);
      // Trigger a custom event which mimics hashchange
      $(window).trigger('my.hashchange', [window.location.toString(), oldUrl]);
    } else {
      // Fallback for the poors browsers which do not have pushState
      window.location.hash = id;
    }

    // prevents the default action of clicking on a link.
    return false;
});

Sau đó, bạn có thể nghe cả sự kiện trao đổi thông thường và my.hashchange:

$(window).on('hashchange my.hashchange', function(e, newUrl, oldUrl){
  // @todo - do something awesome!
});

tất nhiên my.hashchangechỉ là một tên sự kiện ví dụ
tối đa

2

Được rồi, đây là một chủ đề khá cũ nhưng tôi nghĩ rằng tôi sẽ đưa ra câu trả lời 'chính xác' không hoạt động tốt với CSS.

Giải pháp này về cơ bản ngăn sự kiện nhấp chuột di chuyển trang để chúng tôi có thể có được vị trí cuộn trước tiên. Sau đó, chúng tôi tự thêm băm và trình duyệt sẽ tự động kích hoạt sự kiện băm . Chúng tôi nắm bắt sự kiện hashchange và cuộn trở lại vị trí chính xác. Một cuộc gọi lại phân tách và ngăn chặn mã của bạn gây ra sự chậm trễ bằng cách giữ băm băm của bạn ở một nơi.

var hashThis = function( $elem, callback ){
    var scrollLocation;
    $( $elem ).on( "click", function( event ){
        event.preventDefault();
        scrollLocation = $( window ).scrollTop();
        window.location.hash = $( event.target ).attr('href').substr(1);
    });
    $( window ).on( "hashchange", function( event ){
        $( window ).scrollTop( scrollLocation );
        if( typeof callback === "function" ){
            callback();
        }
    });
}
hashThis( $( ".myAnchor" ), function(){
    // do something useful!
});

Bạn không cần trao đổi, chỉ cần cuộn lại ngay lập tức
daniel.gindi

2

Erm tôi có một phương pháp làm việc hơi thô nhưng chắc chắn.
Chỉ cần lưu trữ vị trí cuộn hiện tại trong một biến tạm thời và sau đó đặt lại sau khi thay đổi hàm băm. :)

Vì vậy, cho ví dụ ban đầu:

$("#buttons li a").click(function(){
        $("#buttons li a").removeClass('selected');
        $(this).addClass('selected');

        var scrollPos = $(document).scrollTop();
        document.location.hash=$(this).attr("id")
        $(document).scrollTop(scrollPos);
});

Vấn đề với phương pháp này là với Trình duyệt di động, bạn có thể nhận thấy rằng các di chuyển được sửa đổi
Tofandel

1

Tôi không nghĩ rằng điều này là có thể. Theo như tôi biết, lần duy nhất trình duyệt không di chuyển đến thay đổi document.location.hashlà nếu hàm băm không tồn tại trong trang.

Bài viết này không liên quan trực tiếp đến câu hỏi của bạn, nhưng nó thảo luận về hành vi thay đổi điển hình của trình duyệtdocument.location.hash


1

nếu bạn sử dụng sự kiện hashchange với trình phân tích cú pháp băm, bạn có thể ngăn hành động mặc định trên các liên kết và thay đổi location.hash thêm một ký tự để có sự khác biệt với thuộc tính id của một phần tử

$('a[href^=#]').on('click', function(e){
    e.preventDefault();
    location.hash = $(this).attr('href')+'/';
});

$(window).on('hashchange', function(){
    var a = /^#?chapter(\d+)-section(\d+)\/?$/i.exec(location.hash);
});

Hoàn hảo! Sau năm giờ cố gắng khắc phục sự cố với tải lại băm ...: / Đây là điều duy nhất hoạt động !! Cảm ơn!!!
Igor Trindade

1

Thêm điều này vào đây bởi vì tất cả các câu hỏi phù hợp hơn đều đã được đánh dấu là trùng lặp chỉ vào đây

Tình hình của tôi đơn giản hơn:

  • người dùng nhấp vào liên kết ( a[href='#something'])
  • bấm xử lý nào: e.preventDefault()
  • chức năng smoothscroll: $("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
  • sau đó window.location = link;

Bằng cách này, cuộn xảy ra và không có bước nhảy khi vị trí được cập nhật.


0

Cách khác để làm điều này là thêm một div ẩn ở đầu khung nhìn. Div này sau đó được gán id của hàm băm trước khi băm được thêm vào url .... vì vậy sau đó bạn không nhận được cuộn.


0

Đây là giải pháp của tôi cho các tab kích hoạt lịch sử:

    var tabContainer = $(".tabs"),
        tabsContent = tabContainer.find(".tabsection").hide(),
        tabNav = $(".tab-nav"), tabs = tabNav.find("a").on("click", function (e) {
                e.preventDefault();
                var href = this.href.split("#")[1]; //mydiv
                var target = "#" + href; //#myDiv
                tabs.each(function() {
                    $(this)[0].className = ""; //reset class names
                });
                tabsContent.hide();
                $(this).addClass("active");
                var $target = $(target).show();
                if ($target.length === 0) {
                    console.log("Could not find associated tab content for " + target);
                } 
                $target.removeAttr("id");
                // TODO: You could add smooth scroll to element
                document.location.hash = target;
                $target.attr("id", href);
                return false;
            });

Và để hiển thị tab được chọn cuối cùng:

var currentHashURL = document.location.hash;
        if (currentHashURL != "") { //a tab was set in hash earlier
            // show selected
            $(currentHashURL).show();
        }
        else { //default to show first tab
            tabsContent.first().show();
        }
        // Now set the tab to active
        tabs.filter("[href*='" + currentHashURL + "']").addClass("active");

Lưu ý *=về filtercuộc gọi. Đây là một thứ dành riêng cho jQuery và nếu không có nó, các tab kích hoạt lịch sử của bạn sẽ thất bại.


0

Giải pháp này tạo ra một div tại scrollTop thực tế và xóa nó sau khi thay đổi hàm băm:

$('#menu a').on('click',function(){
    //your anchor event here
    var href = $(this).attr('href');
    window.location.hash = href;
    if(window.location.hash == href)return false;           
    var $jumpTo = $('body').find(href);
    $('body').append(
        $('<div>')
            .attr('id',$jumpTo.attr('id'))
            .addClass('fakeDivForHash')
            .data('realElementForHash',$jumpTo.removeAttr('id'))
            .css({'position':'absolute','top':$(window).scrollTop()})
    );
    window.location.hash = href;    
});
$(window).on('hashchange', function(){
    var $fakeDiv = $('.fakeDivForHash');
    if(!$fakeDiv.length)return true;
    $fakeDiv.data('realElementForHash').attr('id',$fakeDiv.attr('id'));
    $fakeDiv.remove();
});

tùy chọn, kích hoạt sự kiện neo khi tải trang:

$('#menu a[href='+window.location.hash+']').click();

0

Tôi có một phương pháp đơn giản hơn phù hợp với tôi. Về cơ bản, hãy nhớ những gì băm thực sự là trong HTML. Đó là một liên kết neo đến thẻ Tên. Đó là lý do tại sao nó cuộn ... trình duyệt đang cố cuộn đến một liên kết neo. Vì vậy, cho nó một!

  1. Ngay dưới thẻ BODY, hãy đặt phiên bản này của bạn:
 <a name="home"> </a> <a name="firstsection"> </a> <a name="secondsection"> </a> <a name="thirdsection"> </a>
  1. Đặt tên cho phần div của bạn với các lớp thay vì ID.

  2. Trong mã xử lý của bạn, loại bỏ dấu băm và thay thế bằng dấu chấm:

    var trimPanel = loadhash.sub chuỗi (1); // mất hàm băm

    var dotSelect = '.' + trimPanel; // thay thế hàm băm bằng dấu chấm

    $ (dotSelect) .addClass ("activepanel"). show (); // hiển thị div liên kết với hàm băm.

Cuối cùng, xóa phần tử.preventDefault hoặc return: false và cho phép điều hướng xảy ra. Cửa sổ sẽ ở trên cùng, hàm băm sẽ được thêm vào url của thanh địa chỉ và bảng điều khiển chính xác sẽ mở ra.


0

Tôi nghĩ bạn cần thiết lập lại cuộn đến vị trí của nó trước khi trao đổi.

$(function(){
    //This emulates a click on the correct button on page load
    if(document.location.hash) {
        $("#buttons li a").removeClass('selected');
        s=$(document.location.hash).addClass('selected').attr("href").replace("javascript:","");
        eval(s);
    }

    //Click a button to change the hash
    $("#buttons li a").click(function() {
            var scrollLocation = $(window).scrollTop();
            $("#buttons li a").removeClass('selected');
            $(this).addClass('selected');
            document.location.hash = $(this).attr("id");
            $(window).scrollTop( scrollLocation );
    });
});

0

Nếu trên trang của bạn, bạn sử dụng id dưới dạng một điểm neo và bạn có các tình huống mà bạn muốn người dùng nối thêm #s Something vào cuối url và có cuộn trang đến phần #s Something đó bằng cách sử dụng hoạt hình được xác định của riêng bạn chức năng javascript, trình lắng nghe sự kiện hashchange sẽ không thể làm điều đó.

Nếu bạn chỉ đơn giản là đặt trình gỡ lỗi ngay lập tức sau sự kiện hashchange, ví dụ, một cái gì đó như thế này (tốt, tôi sử dụng jquery, nhưng bạn nhận được điểm):

$(window).on('hashchange', function(){debugger});

Bạn sẽ nhận thấy rằng ngay khi bạn thay đổi url và nhấn nút enter, trang sẽ dừng ngay tại phần tương ứng, chỉ sau đó, chức năng cuộn được xác định của bạn sẽ được kích hoạt và nó sẽ cuộn đến phần đó, trông giống như vậy rất tệ.

Đề nghị của tôi là:

  1. không sử dụng id làm điểm neo của bạn đến phần bạn muốn cuộn đến.

  2. Nếu bạn phải sử dụng ID, như tôi làm. Thay vào đó, hãy sử dụng trình nghe sự kiện 'popstate', nó sẽ không tự động cuộn đến chính phần bạn thêm vào url, thay vào đó, bạn có thể gọi hàm được xác định của riêng mình bên trong sự kiện popstate.

    $(window).on('popstate', function(){myscrollfunction()});

Cuối cùng, bạn cần thực hiện một mẹo nhỏ trong chức năng cuộn được xác định của riêng bạn:

    let hash = window.location.hash.replace(/^#/, '');
    let node = $('#' + hash);
    if (node.length) {
        node.attr('id', '');
    }
    if (node.length) {
        node.attr('id', hash);
    }

xóa id trên thẻ của bạn và đặt lại nó.

Cái này cần phải dùng mẹo.


-5

Chỉ thêm mã này vào jQuery trên tài liệu đã sẵn sàng

Tham chiếu: http://css-tricks.com/snippets/jquery/smooth-scrolling/

$(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
        }, 1000);
        return false;
      }
    }
  });
});
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.