Змінення location.hash без прокрутки сторінки


148

У нас є кілька сторінок, що використовують ajax для завантаження вмісту, і є кілька випадків, коли нам потрібно глибоко посилатись на сторінку. Замість того, щоб мати посилання на "Користувачі" та казати людям натискати "Налаштування", корисно мати можливість зв’язувати людей із налаштуваннями користувача.aspx #

Щоб дозволити людям надавати нам правильні посилання на розділи (для технічної підтримки тощо), я налаштував його автоматично змінювати хеш у 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. Потрібно знешкодити ідентифікатор вузла, поки хеш не буде встановлений. Це робиться шляхом видалення ідентифікатора з вузла під час встановлення хеша, а потім його додавання знову.

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

Крок 2: Деякі браузери запускають прокрутку залежно від того, де останній раз бачив вузол ідентифікатора, тож вам потрібно трохи допомогти. Вам потрібно додати додаткових даних divу верхню частину вікна перегляду, встановити його ідентифікатор у хеш, а потім повернути все назад:

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 створюється та розміщується в поточному місці прокрутки. Це візуально те саме, що і положення: фіксований / верхній: 0. Таким чином панель прокрутки "переміщується" на саме те місце, на якому вона знаходиться.
Боргар

3
Це рішення справді працює добре - проте рядок: top: $ .scroll (). Top + 'px' повинен бути: top: $ (window) .scrollTop () + 'px'
Марк Перкінс

27
Було б корисно знати, яким браузерам потрібен крок 2 тут.
djc

1
Дякую Боргар. Мені це спантеличує те, що ви додаєте fx div перед видаленням ідентифікатора цільового вузла. Це означає, що в документі є момент, у якому є дублікати ідентифікаторів. Схоже на потенційну проблему чи, принаймні, погані манери;)
Бен

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ні.
Акіл Фернандес

Дякую @AakilFernandes ви маєте рацію. Я щойно оновив відповідь.
HaNdTriX

Не завжди bodyце прокручується, іноді це documentElement. Дивіться цю суть Дієго
Періні

1
будь-яка ідея про ie8 / 9 тут?
користувач151496

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
Прекрасне рішення. Більшість безболісних із партії.
Швадер

Зауважте, що якщо ви вже прихильні до URL-адрес page.aspx # elementID з певних причин, ви можете змінити цю техніку і додати "btn_" до всіх своїх ідентифікаторів
joshuahedlund

2
Не працює, якщо ви використовуєте: цільові псевдоселектори в CSS.
Jason T Featheringham

1
Любіть це рішення! Ми використовуємо хеш URL-адрес для навігації на базі AJAX, передбачуючи ідентифікатори, /здається, логічним.
Коннелл

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 і веб-камери змусять прокручувати (навіть до невидимої цілі) з незручним ривком при запуску 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

Erm У мене є дещо сирий, але, безумовно, робочий метод.
Просто збережіть поточну позицію прокрутки у змінній temp, а потім скиньте її після зміни хеша. :)

Отже, для оригінального прикладу:

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

якщо ви використовуєте подію hashchange з аналізатором хешу, ви можете запобігти дії за замовчуванням на посиланнях та змінити location.hash, додавши один символ, щоб мати різницю з властивістю елемента елемента

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

Ідеально! Через п’ять годин намагаються виправити проблему з перезавантаженням хешу ...: / Це було єдине, що спрацювало !! Дякую!!!
Ігор Триндаде

1

Додайте це сюди, тому що більш відповідні питання були позначені як дублікати, що вказують тут…

Моя ситуація простіша:

  • користувач натискає посилання ( a[href='#something'])
  • обробник кліків робить: e.preventDefault()
  • функція smoothscroll: $("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
  • тоді window.location = link;

Таким чином, прокрутка відбувається, і під час оновлення місця немає стрибка.


0

Інший спосіб зробити це - додати div, прихований у верхній частині вікна перегляду. Потім цьому ділу присвоюється ідентифікатор хеша до того, як хеш буде доданий до 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. Це посилання на якір до тегу Name. Ось чому він прокручується ... браузер намагається прокрутити до якірного посилання. Отож, дайте це одне!

  1. Прямо під тегом BODY поставте свою версію цього:
 <a name="home"> </a> <a name="firstsection"> </a> <a name="secondsection"> </a> <a name="thirdsection"> </a>
  1. Назвіть діви розділів класами замість ідентифікаторів.

  2. У своєму коді обробки зніміть хеш-знак і замініть крапкою:

    var trimPanel = loadhash.substring (1); // втратити хеш

    var dotSelect = '.' + trimPanel; // замінити хеш крапкою

    $ (dotSelect) .addClass ("activepanel"). show (); // показати div, пов’язаний з хешем.

Нарешті, видаліть element.preventDefault або поверніть: 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

Якщо на вашій сторінці ви використовуєте ідентифікатор як якусь точку прив’язки, і у вас є сценарії, коли ви хочете, щоб користувачі додавали # щось до кінця URL-адреси, а сторінка прокручувала до цього # -щого розділу, використовуючи власний визначений анімований Функція javascript, слухач подій хеш-змін не зможе цього зробити.

Якщо ви просто встановите налагоджувач відразу після події хеш-зміни, наприклад, щось подібне (ну, я використовую jquery, але ви отримаєте точку):

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

Ви помітите, що як тільки ви зміните URL-адресу та натиснете кнопку введення, сторінка зупиняється на відповідному розділі негайно, лише після цього запуститься ваша визначена функція прокрутки, і вона начебто прокручується до того розділу, який виглядає дуже погано.

Моя пропозиція:

  1. не використовуйте id в якості точки прив’язки до розділу, до якого потрібно прокрутити.

  2. Якщо ви повинні використовувати ідентифікатор, як я. Використовуйте натомість слухача подій "popstate", він не прокручується автоматично до самого розділу, який ви додаєте до URL-адреси, натомість ви можете викликати власну визначену функцію всередині події.

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

видаліть ідентифікатор з тегу та скиньте його.

Це повинно зробити трюк.


-5

Додайте цей код у jQuery лише на готовому документі

Посилання: 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;
      }
    }
  });
});
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.