Memodifikasi location.hash tanpa menggulir halaman

148

Kami punya beberapa halaman menggunakan ajax untuk memuat konten dan ada beberapa kesempatan di mana kami perlu tautan dalam ke halaman. Alih-alih memiliki tautan ke "Pengguna" dan memberi tahu orang-orang untuk mengklik "pengaturan" lebih baik untuk dapat menautkan orang ke pengaturan user.aspx #

Untuk memungkinkan orang memberi kami tautan yang benar ke bagian (untuk dukungan teknis, dll.) Saya sudah mengaturnya untuk secara otomatis memodifikasi hash di URL setiap kali tombol diklik. Satu-satunya masalah tentu saja adalah ketika ini terjadi, ia juga menggulir halaman ke elemen ini.

Apakah ada cara untuk menonaktifkan ini? Di bawah ini adalah bagaimana saya melakukan ini sejauh ini.

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

Saya berharap return false;akan menghentikan halaman dari pengguliran - tetapi itu hanya membuat tautan tidak berfungsi sama sekali. Jadi itu baru saja dikomentari untuk sekarang sehingga saya dapat menavigasi.

Ada ide?

CBarr
sumber

Jawaban:

111

Langkah 1: Anda harus meredakan ID node, sampai hash telah ditetapkan. Ini dilakukan dengan menghapus ID dari node saat hash diatur, dan kemudian menambahkannya kembali.

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

Langkah 2: Beberapa browser akan memicu gulir berdasarkan tempat simpul ID'd terakhir kali dilihat sehingga Anda perlu sedikit membantu mereka. Anda perlu menambahkan ekstra divke bagian atas viewport, mengatur ID-nya ke hash, dan kemudian mengembalikan semuanya:

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

Langkah 3: Bungkus dalam plugin dan gunakan itu alih-alih menulis ke location.hash...

Borgar
sumber
1
Tidak, apa yang terjadi pada langkah 2 adalah bahwa div tersembunyi dibuat dan ditempatkan di lokasi gulir saat ini. Ini sama seperti posisi: tetap / atas: 0. Dengan demikian scrollbar "dipindahkan" ke tempat yang sama persis saat ini diaktifkan.
Borgar
3
Solusi ini memang bekerja dengan baik - namun baris: atas: $ .scroll (). Top + 'px' Seharusnya: top: $ (window) .scrollTop () + 'px'
Mark Perkins
27
Akan bermanfaat untuk mengetahui browser mana yang perlu langkah 2 di sini.
djc
1
Terima kasih Borgar. Itu membingungkan saya meskipun Anda menambahkan div fx sebelum menghapus id node target. Itu berarti ada instan di mana ada duplikat ID dalam dokumen. Sepertinya masalah potensial, atau setidaknya perilaku buruk;)
Ben
1
Terima kasih banyak, ini juga membantu saya. Tetapi saya juga melihat efek samping ketika ini sedang beraksi: Saya sering menggulir dengan mengunci "tombol tengah mouse" dan hanya menggerakkan mouse ke arah yang ingin saya gulir. Di Google Chrome ini mengganggu aliran gulir. Apakah mungkin ada solusi yang bagus untuk itu?
YMMD
99

Gunakan history.replaceStateatau history.pushState* untuk mengubah hash. Ini tidak akan memicu lompatan ke elemen terkait.

Contoh

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

Demo di JSFiddle

* Jika Anda ingin riwayat maju dan mundur dukungan

Perilaku sejarah

Jika Anda menggunakan history.pushStatedan Anda tidak ingin pengguliran halaman saat pengguna menggunakan tombol histori browser (maju / mundur) periksa scrollRestorationpengaturan eksperimental (hanya Chrome 46+) .

history.scrollRestoration = 'manual';

Dukungan Browser

HaNdTriX
sumber
5
replaceStatemungkin cara yang lebih baik untuk pergi ke sini. Perbedaannya adalah pushStatemenambahkan item ke riwayat Anda sementara replaceStatetidak.
Aakil Fernandes
Terima kasih @akilfernandes Anda benar. Saya baru saja memperbarui jawabannya.
HaNdTriX
Itu tidak selalu bodygulungan itu, kadang-kadang itu documentElement. Lihat intisari ini oleh Diego Perini
Matijs
1
ada ide tentang ie8 / 9 di sini?
user151496
1
@ user151496 periksa: stackoverflow.com/revisions/…
HaNdTriX
90

Saya pikir saya mungkin telah menemukan solusi yang cukup sederhana. Masalahnya adalah bahwa hash dalam URL juga merupakan elemen pada halaman yang Anda gulir. jika saya hanya menambahkan beberapa teks ke hash, sekarang tidak lagi merujuk elemen yang ada!

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

Sekarang URL muncul sebagai page.aspx#btn_elementIDyang bukan ID asli pada halaman. Saya hanya menghapus "btn_" dan mendapatkan ID elemen yang sebenarnya

CBarr
sumber
5
Solusi bagus Paling tidak menyakitkan dari banyak.
Swader
Perhatikan bahwa jika Anda sudah berkomitmen untuk URL page.aspx # elementID karena alasan tertentu, Anda dapat membalikkan teknik ini dan menambahkan "btn_" ke semua ID Anda
joshuahedlund
2
Tidak berfungsi jika Anda menggunakan: target pseudo-selectors di CSS.
Jason T Featheringham
1
Cintai solusi ini! Kami menggunakan hash URL untuk navigasi berbasis AJAX, mengawali ID dengan /logis.
Connell
3

Cuplikan kode asli Anda:

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

Ubah ini menjadi:

$("#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")
});
Daniel
sumber
Ini tidak akan berhasil karena pengaturan hashproperti akan membuat halaman tetap bergulir.
jordanbtucker
3

Saya baru-baru ini membangun sebuah korsel yang mengandalkan window.location.hashuntuk mempertahankan keadaan dan membuat penemuan bahwa Chrome dan browser webkit akan memaksa pengguliran (bahkan ke target yang tidak terlihat) dengan brengsek canggung ketika window.onhashchangeacara dipecat.

Bahkan berusaha mendaftarkan pawang yang menghentikan propagasi:

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

Tidak melakukan apa pun untuk menghentikan perilaku browser default. Solusi yang saya temukan digunakan window.history.pushStateuntuk mengubah hash tanpa memicu efek samping yang tidak diinginkan.

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

Anda kemudian dapat mendengarkan acara hashchange normal dan my.hashchange:

$(window).on('hashchange my.hashchange', function(e, newUrl, oldUrl){
  // @todo - do something awesome!
});
maks
sumber
tentu saja my.hashchangehanya contoh nama acara
maks
2

Oke, ini adalah topik yang agak lama tapi saya pikir saya akan memasukkannya sebagai jawaban 'benar' tidak bekerja dengan baik dengan CSS.

Solusi ini pada dasarnya mencegah acara klik dari memindahkan halaman sehingga kita bisa mendapatkan posisi gulir terlebih dahulu. Kemudian kami secara manual menambahkan hash dan browser secara otomatis memicu acara hashchange . Kami menangkap acara hashchange dan gulir kembali ke posisi yang benar. Panggilan balik memisahkan dan mencegah kode Anda menyebabkan penundaan dengan menjaga peretasan hash Anda di satu tempat.

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!
});
Egor Kloos
sumber
Anda tidak memerlukan perubahan, cukup gulir kembali dengan segera
daniel.gindi
2

Eh saya punya metode yang agak kasar tapi pasti bisa.
Cukup simpan posisi gulir saat ini dalam variabel temp dan kemudian atur ulang setelah mengubah hash. :)

Jadi untuk contoh aslinya:

$("#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);
});
Jan Paepke
sumber
Masalah dengan metode ini adalah dengan Peramban Seluler Anda dapat melihat bahwa penggulirannya dimodifikasi
Tofandel
1

Saya rasa ini tidak mungkin. Sejauh yang saya tahu, satu-satunya waktu browser tidak gulir ke perubahan document.location.hashadalah jika hash tidak ada di dalam halaman.

Artikel ini tidak terkait langsung dengan pertanyaan Anda, tetapi membahas perilaku perubahan browser yang khasdocument.location.hash

Ben S
sumber
1

jika Anda menggunakan acara hashchange dengan parser hash, Anda dapat mencegah tindakan default pada tautan dan mengubah location.hash menambahkan satu karakter untuk memiliki perbedaan dengan properti id dari suatu elemen

$('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);
});
polkila
sumber
Sempurna! Setelah lima jam mencoba untuk memperbaiki masalah dengan hash reload ...: / Ini adalah satu-satunya hal yang berhasil !! Terima kasih!!!
Igor Trindade
1

Menambahkan ini di sini karena semua pertanyaan yang lebih relevan telah ditandai sebagai duplikat yang menunjuk ke sini ...

Situasi saya lebih sederhana:

  • pengguna mengklik tautan ( a[href='#something'])
  • klik penangan tidak: e.preventDefault()
  • fungsi smoothscroll: $("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
  • kemudian window.location = link;

Dengan cara ini, gulir terjadi, dan tidak ada lompatan ketika lokasi diperbarui.

ptim
sumber
0

Cara lain untuk melakukan ini adalah dengan menambahkan div yang tersembunyi di bagian atas viewport. Div ini kemudian diberi id hash sebelum hash ditambahkan ke url .... jadi Anda tidak mendapatkan gulungan.

Mike Rifgin
sumber
0

Inilah solusi saya untuk tab yang mengaktifkan riwayat:

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

Dan untuk menampilkan tab yang terakhir dipilih:

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

Perhatikan *=pada filterpanggilan. Ini adalah hal khusus jQuery, dan tanpanya, tab yang didukung riwayat Anda akan gagal.

pengguna1429980
sumber
0

Solusi ini menciptakan div di scrollTop yang sebenarnya dan menghapusnya setelah mengubah hash:

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

opsional, memicu peristiwa jangkar saat memuat halaman:

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

sumber
0

Saya memiliki metode yang lebih sederhana yang berfungsi untuk saya. Pada dasarnya, ingat apa hash sebenarnya dalam HTML. Ini tautan jangkar ke tag Nama. Itu sebabnya ia menggulir ... browser berusaha untuk menggulir ke tautan jangkar. Jadi, berikan satu!

  1. Tepat di bawah tag BODY, letakkan versi Anda tentang ini:
 <a name="home"> </a> <a name="firstsection"> </a> <a name="secondsection"> </a> <a name="thirdsection"> </a>
  1. Beri nama div bagian Anda dengan kelas, bukan ID.

  2. Dalam kode pemrosesan Anda, lepaskan tanda pagar dan ganti dengan titik:

    var trimPanel = loadhash.substring (1); // kehilangan hash

    var dotSelect = '.' + trimPanel; // ganti hash dengan dot

    $ (dotSelect) .addClass ("activepanel"). show (); // tunjukkan div yang terkait dengan hash.

Akhirnya, hapus element.preventDefault atau return: false dan biarkan nav terjadi. Jendela akan tetap di atas, hash akan ditambahkan ke url bilah alamat, dan panel yang benar akan terbuka.

ColdSharper
sumber
0

Saya pikir Anda perlu mengatur ulang gulir ke posisinya sebelum hashchange.

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

Jika pada halaman Anda menggunakan id sebagai semacam anchor point, dan Anda memiliki skenario di mana Anda ingin pengguna menambahkan #sesuatu di akhir url dan minta halaman gulir ke bagian #something dengan menggunakan animasi yang Anda tentukan sendiri fungsi javascript, pendengar acara hashchange tidak akan bisa melakukan itu.

Jika Anda cukup meletakkan debugger segera setelah acara hashchange, misalnya, sesuatu seperti ini (well, saya menggunakan jquery, tetapi Anda mendapatkan intinya):

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

Anda akan melihat bahwa segera setelah Anda mengubah url dan menekan tombol enter, halaman berhenti di bagian yang sesuai segera, hanya setelah itu, fungsi gulir yang Anda tentukan sendiri akan dipicu, dan itu semacam gulir ke bagian itu, yang terlihat sangat buruk.

Saran saya adalah:

  1. jangan gunakan id sebagai titik jangkar Anda ke bagian yang ingin Anda gulir.

  2. Jika Anda harus menggunakan ID, seperti yang saya lakukan. Sebagai gantinya, gunakan pendengar acara 'popstate', itu tidak akan secara otomatis menggulir ke bagian yang Anda tambahkan ke url, sebagai gantinya, Anda dapat memanggil fungsi yang Anda tentukan sendiri di dalam acara popstate.

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

Akhirnya Anda perlu melakukan sedikit trik dalam fungsi gulir yang Anda tentukan sendiri:

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

hapus id pada tag Anda dan setel ulang.

Ini harus melakukan trik.

Shuwei
sumber
-5

Hanya tambahkan kode ini ke jQuery pada dokumen yang siap

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;
      }
    }
  });
});
Saygın Karahan
sumber