Изменение location.hash без прокрутки страницы

148

У нас есть несколько страниц, использующих ajax для загрузки контента, и есть несколько случаев, когда нам нужна глубокая ссылка на страницу. Вместо того, чтобы ссылаться на «Пользователи» и предлагать людям нажимать «Настройки», полезно иметь возможность связывать людей с настройками user.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;это остановит прокрутку страницы - но это просто делает ссылку вообще неработающей. Так что это только что закомментировано, чтобы я мог ориентироваться.

Любые идеи?

CBarr
источник

Ответы:

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: Некоторые браузеры запускают прокрутку в зависимости от того, где последний раз видели узел ID, поэтому вам нужно немного помочь им. Вам нужно добавить дополнительный 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 здесь.
DJ
1
Спасибо Боргар. Меня удивляет, что вы добавляете fx div перед удалением идентификатора целевого узла. Это означает, что есть момент, когда в документе есть дубликаты ID. Похоже, потенциальная проблема, или, по крайней мере, плохие манеры;)
Бен
1
Большое спасибо, это также помогло мне. Но я также заметил побочный эффект, когда это происходит: я часто прокручиваю, блокируя «среднюю кнопку мыши» и просто перемещая мышь в направлении, которое я хочу прокрутить. В Google Chrome это прерывает поток прокрутки. Может быть, есть причудливый обходной путь для этого?
YMMD
100

Используйте 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';

Поддержка браузера

HaNdTriX
источник
5
replaceStateэто, вероятно, лучший способ пойти сюда. Разница заключается в том, что pushStateдобавляет элемент в вашу историю, а replaceStateнет.
Акил Фернандес
Спасибо @AakilFernandes, ты прав. Я только что обновил ответ.
HaNdTriX
Это не всегда то, bodyчто свитки, иногда это documentElement. Смотрите эту суть Диего Перини
Матейс
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_" и получаю фактический идентификатор элемента

CBarr
источник
5
Отличное решение. Самый безболезненный из всего.
Swader
Обратите внимание, что если вы уже по какой-то причине привержены URL page.aspx # elementID, вы можете отменить эту технику и добавить «btn_» ко всем вашим идентификаторам
joshuahedlund
2
Не работает, если вы используете: target псевдо-селекторы в CSS.
Джейсон Ферингем
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свойства все равно приведет к прокрутке страницы.
Джорданбакер
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;
});

Затем вы можете прослушивать как обычное событие hashchange, так и my.hashchange:

$(window).on('hashchange my.hashchange', function(e, newUrl, oldUrl){
  // @todo - do something awesome!
});
Максимум
источник
Конечно, my.hashchangeэто просто пример имени события
макс
2

Ладно, это довольно старая тема, но я подумал, что скину, поскольку «правильный» ответ не работает с CSS.

Это решение в основном предотвращает перемещение страницы по событию click, поэтому мы можем сначала получить позицию прокрутки. Затем мы вручную добавляем хэш, и браузер автоматически запускает событие hashchange . Мы фиксируем событие hashchange и возвращаемся к правильной позиции. Обратный вызов отделяет и предотвращает задержку вашего кода, сохраняя хэш-хак в одном месте.

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

если вы используете событие hashchange с анализатором хеша, вы можете предотвратить действие по умолчанию для ссылок и изменить местоположение. hash добавив один символ, чтобы иметь различие со свойством 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);
});
polkila
источник
Отлично! После пяти часов попыток исправить проблему с перезагрузкой хеша ...: / Это было единственное, что сработало !! Спасибо!!!
Игорь Триндаде
1

Добавление этого здесь, потому что все более важные вопросы были отмечены как дубликаты, указывающие здесь ...

Моя ситуация проще:

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

Таким образом, происходит прокрутка, и при обновлении местоположения прыжка не происходит.

ptim
источник
0

Другой способ сделать это - добавить div, который скрыт в верхней части области просмотра. Затем этому 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 вещь, и без нее ваши вкладки с историей не будут работать.

user1429980
источник
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 с классами вместо идентификаторов.

  2. В коде обработки удалите хэш-метку и замените ее точкой:

    var trimPanel = loadhash.substring (1); // теряем хеш

    var dotSelect = '.' + trimPanel; // заменить хеш точкой

    $ (DotSelect) .addClass ( "ActivePanel") шоу (). // показать div, связанный с хешем.

Наконец, удалите element.preventDefault или return: false и разрешите навигацию. Окно останется наверху, хеш будет добавлен к URL адресной строки, и откроется правильная панель.

ColdSharper
источник
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 );
    });
});
iProDev
источник
0

Если на вашей странице вы используете id как своего рода точку привязки, и у вас есть сценарии, в которых вы хотите, чтобы пользователи добавляли #something в конец URL-адреса и прокручивали страницу до этого раздела #something, используя свой собственный определенный анимированный Функция javascript, слушатель события hashchange не сможет это сделать.

Если вы просто поместите отладчик сразу после события hashchange, например, что-то вроде этого (ну, я использую jquery, но вы поняли):

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

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

Мое предложение:

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

удалить идентификатор в вашем теге и сбросить его.

Это должно сделать свое дело.

Shuwei
источник
-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;
      }
    }
  });
});
Сайгин Карахан
источник