การแก้ไข location.hash โดยไม่ต้องเลื่อนหน้า


148

เรามีหน้าเว็บสองสามหน้าที่ใช้ ajax เพื่อโหลดเนื้อหาและมีบางโอกาสที่เราจะต้องลิงก์ลึกเข้าไปในหน้าเว็บ แทนที่จะมีลิงก์ไปยัง "ผู้ใช้" และบอกให้ผู้คนคลิก "การตั้งค่า" มันมีประโยชน์มากที่จะสามารถเชื่อมโยงผู้คนกับuser.aspx # settings

เพื่อให้ผู้คนสามารถให้ลิงก์ที่ถูกต้องกับส่วนต่างๆ (สำหรับฝ่ายสนับสนุนด้านเทคนิคและอื่น ๆ ) เราได้ตั้งค่าให้แก้ไขแฮชใน URL โดยอัตโนมัติทุกครั้งที่มีการคลิกปุ่ม ปัญหาเดียวของหลักสูตรคือเมื่อเกิดเหตุการณ์นี้มันยังเลื่อนหน้าไปยังองค์ประกอบนี้

มีวิธีปิดใช้งานสิ่งนี้หรือไม่ ด้านล่างเป็นวิธีที่ฉันทำเช่นนี้จนถึง

$(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;
    });
});

ฉันหวังว่าreturn false;จะหยุดหน้าไม่ให้เลื่อน - แต่มันทำให้ลิงค์ไม่ทำงานเลย นั่นเป็นเพียงความเห็นตอนนี้เพื่อให้ฉันสามารถนำทาง

ความคิดใด ๆ

คำตอบ:


111

ขั้นตอนที่ 1: คุณต้องคลี่คลาย ID โหนดจนกว่าจะมีการตั้งค่าแฮช สิ่งนี้ทำได้โดยการลบ ID ออกจากโหนดในขณะที่ตั้งค่าแฮชแล้วจึงเพิ่มกลับเข้าไปใหม่

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

ขั้นตอนที่ 2: เบราว์เซอร์บางตัวจะทริกเกอร์การเลื่อนตามตำแหน่งที่มีการดูโหนด ID ครั้งสุดท้ายดังนั้นคุณต้องช่วยพวกเขาเล็กน้อย คุณต้องเพิ่มพิเศษdivไปที่ด้านบนสุดของวิวพอร์ตตั้งค่า ID เป็นแฮชแล้วหมุนกลับทุกอย่าง:

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 );
}

ขั้นตอนที่ 3: ล้อมไว้ในปลั๊กอินและใช้สิ่งนั้นแทนที่จะเขียนลงในlocation.hash...


1
ไม่สิ่งที่เกิดขึ้นในขั้นตอนที่ 2 คือการสร้าง div ที่ซ่อนอยู่และวางไว้ที่ตำแหน่งปัจจุบันของการเลื่อน มันเหมือนกันกับตำแหน่ง: fixed / top: 0 ดังนั้นแถบเลื่อนจะถูก "ย้าย" ไปยังตำแหน่งเดียวกันกับที่เปิดอยู่
Borgar

3
วิธีการแก้ปัญหานี้ได้ผลดี - อย่างไรก็ตามบรรทัด: top: $ .scroll (). top + 'px' ควรเป็น: top: $ (หน้าต่าง) .scrollTop () + 'px'
Mark Perkins

27
มันจะมีประโยชน์ที่จะทราบว่าเบราว์เซอร์ใดที่ต้องการขั้นตอนที่ 2 ที่นี่
djc

1
ขอบคุณ Borgar มันทำให้ฉันสับสนแม้ว่าคุณจะผนวก fx div ก่อนที่จะลบ id ของโหนดเป้าหมาย ซึ่งหมายความว่ามีการโต้ตอบแบบทันทีซึ่งมี ID ซ้ำกันในเอกสาร ดูเหมือนว่าอาจมีปัญหาหรืออย่างน้อยมารยาทที่ไม่ดี;)
เบ็น

1
ขอบคุณมากนี่ช่วยฉันด้วย แต่ฉันยังสังเกตเห็นผลข้างเคียงเมื่อมีการใช้งาน: ฉันมักจะเลื่อนโดยล็อค "ปุ่มกลางของเมาส์" และเพียงแค่เลื่อนเมาส์ไปในทิศทางที่ฉันต้องการเลื่อน ใน Google Chrome สิ่งนี้ขัดจังหวะการเลื่อน อาจจะมีวิธีแก้ปัญหาแฟนซีสำหรับที่?
YMMD

99

ใช้history.replaceStateหรือhistory.pushState * เพื่อเปลี่ยนแฮช สิ่งนี้จะไม่ทำให้เกิดการกระโดดไปยังองค์ประกอบที่เกี่ยวข้อง

ตัวอย่าง

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

การสาธิตเกี่ยวกับ JSFiddle

* หากคุณต้องการให้ประวัติย้อนหลังและการสนับสนุนย้อนหลัง

ประวัติพฤติกรรม

หากคุณกำลังใช้history.pushStateและคุณไม่ต้องการหน้าเลื่อนเมื่อผู้ใช้ใช้ปุ่มประวัติศาสตร์ของเบราว์เซอร์ (เดินหน้า / ถอยหลัง) ตรวจสอบการทดลองscrollRestorationการตั้งค่า(Chrome 46+ เท่านั้น)

history.scrollRestoration = 'manual';

รองรับเบราว์เซอร์


5
replaceStateน่าจะเป็นวิธีที่ดีกว่าที่จะไปที่นี่ ความแตกต่างที่pushStateเพิ่มรายการในประวัติของคุณในขณะที่replaceStateไม่ได้
Aakil Fernandes

ขอบคุณ @AakilFernandes คุณพูดถูก ฉันเพิ่งปรับปรุงคำตอบ
HaNdTriX

มันไม่ได้เป็นตัวbodyเลื่อนเสมอdocumentElementไป ดูส่วนสำคัญนี้โดย Diego Perini
Matijs

1
ความคิดใด ๆ เกี่ยวกับ ie8 / 9 ที่นี่?
user151496

1
@ user151496 ตรวจสอบ: stackoverflow.com/revisions/…
HaNdTriX

90

ฉันคิดว่าฉันอาจพบวิธีแก้ปัญหาที่ค่อนข้างง่าย ปัญหาคือแฮชใน URL เป็นองค์ประกอบในหน้าเว็บที่คุณเลื่อนไป ถ้าฉันแค่เติมข้อความบางอย่างลงในแฮชตอนนี้ก็ไม่ได้อ้างอิงองค์ประกอบที่มีอยู่อีกต่อไป!

$(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;
    });
});

ตอนนี้ URL จะปรากฏขึ้นpage.aspx#btn_elementIDซึ่งไม่ใช่รหัสจริงบนหน้าเว็บ ฉันเพิ่งลบ "btn_" และรับรหัสองค์ประกอบจริง


5
ทางออกที่ดี เจ็บปวดมากที่สุด
Swader

โปรดทราบว่าหากคุณมุ่งมั่นที่จะ page.aspx # elementID URL ด้วยเหตุผลบางประการคุณสามารถย้อนกลับเทคนิคนี้และเติม "btn_" ให้กับ ID ทั้งหมดของคุณได้
joshuahedlund

2
ไม่ทำงานหากคุณใช้: เป้าหมายตัวเลือกหลอกใน CSS
Jason T Featheringham

1
รักวิธีนี้! เรากำลังใช้แฮช URL สำหรับการนำทางที่ใช้ AJAX ซึ่งเป็นการเตรียม ID ด้วย/ตรรกะที่ดูเหมือนว่า
Connell

3

ตัวอย่างรหัสต้นฉบับของคุณ:

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

เปลี่ยนเป็น:

$("#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")
});

สิ่งนี้จะไม่ทำงานเนื่องจากการตั้งค่าhashคุณสมบัติจะทำให้หน้าเลื่อนอยู่ดี
jordanbtucker

3

เมื่อเร็ว ๆ นี้ฉันกำลังสร้างม้าหมุนซึ่งอาศัยwindow.location.hashการรักษาสถานะและทำให้ค้นพบว่า Chrome และเบราว์เซอร์ webkit จะบังคับให้เลื่อน (แม้จะเป็นเป้าหมายที่มองไม่เห็น) ด้วยการกระตุกที่น่าอึดอัดใจเมื่อwindow.onhashchangeเหตุการณ์ถูกยิง

แม้แต่ความพยายามที่จะลงทะเบียนตัวจัดการที่หยุดการสนับสนุน:

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

ไม่ทำอะไรเลยเพื่อหยุดพฤติกรรมเบราว์เซอร์เริ่มต้น วิธีแก้ปัญหาที่ฉันพบใช้window.history.pushStateเพื่อเปลี่ยนแฮชโดยไม่ทำให้เกิดผลข้างเคียงที่ไม่พึงประสงค์

 $("#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;
});

จากนั้นคุณสามารถฟังได้ทั้งเหตุการณ์แฮชเชนปกติและmy.hashchange:

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

แน่นอนmy.hashchangeเป็นเพียงชื่อเหตุการณ์ตัวอย่าง
สูงสุด

2

ตกลงนี่เป็นหัวข้อที่ค่อนข้างเก่า แต่ฉันคิดว่าฉันจะเขียนเพราะคำตอบที่ถูกต้องใช้งานไม่ได้กับ CSS

โซลูชันนี้จะป้องกันไม่ให้เหตุการณ์การคลิกย้ายหน้าเว็บเพื่อให้เราสามารถรับตำแหน่งการเลื่อนได้ก่อน จากนั้นเราเพิ่มแฮชด้วยตนเองและเบราว์เซอร์จะทริกเกอร์เหตุการณ์แฮชเชนส์โดยอัตโนมัติ เราจับภาพเหตุการณ์แฮชชิ่งและเลื่อนกลับไปที่ตำแหน่งที่ถูกต้อง การติดต่อกลับจะแยกและป้องกันรหัสของคุณทำให้เกิดความล่าช้าโดยทำให้แฮ็คแฮชของคุณอยู่ในที่เดียว

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!
});

คุณไม่ต้องการแฮชเชนจ์เพียงเลื่อนกลับทันที
daniel.gindi

2

เอิ่มฉันมีวิธีที่ค่อนข้างหยาบ แต่ทำงานได้อย่างแน่นอน
เพียงเก็บตำแหน่งการเลื่อนปัจจุบันในตัวแปรชั่วคราวแล้วรีเซ็ตหลังจากเปลี่ยนแฮช :)

ดังนั้นสำหรับตัวอย่างดั้งเดิม:

$("#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);
});

ปัญหาเกี่ยวกับวิธีนี้คือใช้เบราว์เซอร์มือถือคุณสามารถสังเกตเห็นว่าการเลื่อนถูกแก้ไข
Tofandel

1

ฉันไม่คิดว่ามันเป็นไปได้ เท่าที่ฉันรู้เพียงครั้งเดียวที่เบราว์เซอร์ไม่เลื่อนไปที่การเปลี่ยนแปลงdocument.location.hashคือถ้าแฮไม่มีอยู่ในหน้า

บทความนี้ไม่เกี่ยวข้องกับคำถามของคุณโดยตรง แต่มันอธิบายพฤติกรรมของเบราว์เซอร์ทั่วไปของการเปลี่ยนแปลงdocument.location.hash


1

หากคุณใช้เหตุการณ์แฮชเชนจ์กับตัวแยกวิเคราะห์แฮชคุณสามารถป้องกันการกระทำเริ่มต้นบนลิงก์และเปลี่ยนตำแหน่งได้การเพิ่มอักขระหนึ่งตัวเพื่อให้มีความแตกต่างกับคุณสมบัติ id ขององค์ประกอบ

$('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);
});

ที่สมบูรณ์แบบ! หลังจากห้าชั่วโมงพยายามแก้ไขปัญหาด้วยการโหลดแฮช ... : / นี่เป็นสิ่งเดียวที่ทำงานได้ !! ขอบคุณ !!!
Igor Trindade

1

การเพิ่มที่นี่เนื่องจากคำถามที่เกี่ยวข้องมากกว่าถูกทำเครื่องหมายว่าซ้ำกันที่นี่ ...

สถานการณ์ของฉันง่ายขึ้น:

  • ผู้ใช้คลิกที่ลิงค์ ( a[href='#something'])
  • ตัวจัดการคลิก: e.preventDefault()
  • ฟังก์ชั่น smoothscroll: $("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
  • แล้วก็ window.location = link;

ด้วยวิธีนี้การเลื่อนจะเกิดขึ้นและไม่มีการข้ามเมื่อมีการอัปเดตตำแหน่ง


0

วิธีอื่นในการทำเช่นนี้คือการเพิ่ม div ที่ซ่อนอยู่ที่ด้านบนของวิวพอร์ต div นี้ถูกกำหนด id ของแฮก่อนที่จะเพิ่ม hash ใน url .... ดังนั้นคุณจะไม่ได้รับการเลื่อน


0

นี่คือโซลูชันของฉันสำหรับแท็บที่เปิดใช้งานประวัติ:

    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;
            });

และเพื่อแสดงแท็บที่เลือกล่าสุด:

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");

สังเกต*=การfilterโทร นี่เป็นสิ่งเฉพาะ jQuery และหากไม่มีแท็บที่เปิดใช้งานประวัติของคุณจะล้มเหลว


0

โซลูชันนี้สร้าง div ที่ scrollTop จริงและลบออกหลังจากเปลี่ยนแฮช:

$('#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();
});

เป็นตัวเลือกทริกเกอร์เหตุการณ์สมอที่โหลดหน้าเว็บ:

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

0

ฉันมีวิธีที่ง่ายกว่าที่เหมาะกับฉัน โดยทั่วไปให้จำสิ่งที่แฮชจริงคือ HTML มันเป็นลิงค์เชื่อมโยงไปยังแท็กชื่อ นั่นเป็นสาเหตุที่มันเลื่อน ... เบราว์เซอร์พยายามเลื่อนไปยังจุดยึดลิงก์ ดังนั้นให้มัน!

  1. ข้างใต้แท็ก BODY ให้ใส่เวอร์ชั่นของคุณ:
 <a name="home"> </a> <a name="firstsection"> </a> <a name="secondsection"> </a> <a name="thirdsection"> </a>
  1. ตั้งชื่อ div ส่วนของคุณด้วยคลาสแทน ID

  2. ในโค้ดการประมวลผลของคุณให้ถอดเครื่องหมายแฮชแล้วแทนที่ด้วยจุด:

    var trimPanel = loadhash.substring (1); // แพ้แฮช

    var dotSelect = '.' + trimPanel; // แทนที่แฮชด้วยจุด

    $ (dotSelect) .addClass ( "activepanel") การแสดง (). // แสดง div ที่เชื่อมโยงกับแฮช

ในที่สุดลบ element.preventDefault หรือ return: false และอนุญาตให้ nav เกิดขึ้น หน้าต่างจะอยู่ด้านบนสุดแฮชจะถูกผนวกเข้ากับ URL ของแถบที่อยู่และแผงที่ถูกต้องจะเปิดขึ้น


0

ฉันคิดว่าคุณต้องรีเซ็ตการเลื่อนไปที่ตำแหน่งก่อนแฮชเชน

$(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

หากในหน้าของคุณคุณใช้ id เป็นจุดยึดจุดยึดและคุณมีสถานการณ์ที่คุณต้องการให้ผู้ใช้ผนวก #something ต่อท้าย URL และให้เลื่อนหน้าไปที่ส่วน #something โดยใช้ภาพเคลื่อนไหวที่คุณกำหนดเอง ฟังก์ชั่น javascript ผู้ฟังเหตุการณ์ hashchange จะไม่สามารถทำได้

หากคุณเพียงแค่ใส่ดีบักเกอร์ทันทีหลังจากเหตุการณ์แฮชเชนจ์สิ่งเช่นนี้ (ดีฉันใช้ jquery แต่คุณได้คะแนน):

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

คุณจะสังเกตเห็นว่าทันทีที่คุณเปลี่ยน URL ของคุณและกดปุ่ม Enter หน้าจะหยุดที่ส่วนที่เกี่ยวข้องทันทีหลังจากนั้นฟังก์ชั่นการเลื่อนที่กำหนดเองของคุณจะได้รับการทริกเกอร์ ที่เลวร้ายมาก.

คำแนะนำของฉันคือ:

  1. อย่าใช้ id เป็นจุดยึดของคุณไปยังส่วนที่คุณต้องการเลื่อนไป

  2. ถ้าคุณต้องใช้ ID เหมือนฉัน ใช้ฟังเหตุการณ์ 'popstate' แทนมันจะไม่เลื่อนไปยังส่วนที่คุณผนวกเข้ากับ url โดยอัตโนมัติแทนคุณสามารถเรียกใช้ฟังก์ชันที่คุณกำหนดเองภายในเหตุการณ์ popstate

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

ในที่สุดคุณต้องทำเคล็ดลับเล็กน้อยในฟังก์ชั่นการเลื่อนที่คุณกำหนด:

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

ลบ id บนแท็กของคุณและรีเซ็ต

สิ่งนี้ควรทำเคล็ดลับ


-5

เพิ่มรหัสนี้ใน jQuery บนเอกสารพร้อมเท่านั้น

Ref: 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;
      }
    }
  });
});
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.