Modifying location.hash without page scrolling
У нас есть несколько страниц, использующих 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;
остановил бы прокрутку страницы - но это просто заставляет ссылку вообще не работать. Так что это только что закомментировано, чтобы я мог ориентироваться.
Есть идеи?
20 ответов
Я думаю, что, возможно, нашел довольно простое решение. Проблема в том, что хэш в 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_" и получаю фактический идентификатор элемента
Использование history.replaceState
или же history.pushState
* изменить хеш. Это не вызовет переход к связанному элементу.
пример
$(document).on('click', 'a[href^=#]', function(event) {
event.preventDefault();
history.pushState({}, '', this.href);
});
* Если вы хотите историю вперед и назад поддержку
История поведения
Если вы используете history.pushState
и вы не хотите прокручивать страницу, когда пользователь использует кнопки истории браузера (вперед / назад), проверьте экспериментальный scrollRestoration
настройка (только Chrome 46+).
history.scrollRestoration = 'manual';
Поддержка браузера
Шаг 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
...
Я недавно строил карусель, которая опирается на 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!
});
Фрагмент вашего исходного кода:
$("#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")
});
У меня немного грубоватый, но определенно рабочий метод.
Просто сохраните текущую позицию прокрутки во временной переменной, а затем сбросьте ее после изменения хеша.:)
Итак, для оригинального примера:
$("#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);
});
Ладно, это довольно старая тема, но я подумал, что скинуть, так как "правильный" ответ не работает с 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!
});
Добавление этого здесь, потому что все более важные вопросы были отмечены как дубликаты, указывающие здесь...
Моя ситуация проще:
- пользователь нажимает на ссылку (
a[href='#something']
) - обработчик кликов делает:
e.preventDefault()
- функция плавной прокрутки:
$("html,body").stop(true,true).animate({ "scrollTop": linkoffset.top }, scrollspeed, "swing" );
- затем
window.location = link;
Таким образом, происходит прокрутка, и при обновлении местоположения прыжка не происходит.
Если вы используете событие 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);
});
Это сработало для меня, используя replaceState:
$('a[href^="#"]').click(function(){
history.replaceState({}, '', location.toString().replace(/#.*$/, '') + $(this).attr('href'));
});
- Сохраните положение прокрутки перед изменением фрагмента URL.
- Изменить фрагмент URL.
- Восстановить старую позицию прокрутки.
let oldScrollPosition = window.scrollY;
window.location.hash = addressFragment;
window.scrollTo(0, oldScrollPosition);
Это быстро, поэтому клиент ничего не заметит.
Я не думаю, что это возможно. Насколько я знаю, единственный раз, когда браузер не прокручивает document.location.hash
если хеш не существует на странице.
Эта статья не имеет прямого отношения к вашему вопросу, но в ней обсуждается типичное поведение браузера при изменении document.location.hash
Вот мой взгляд на это (через 14 лет после первоначального вопроса). Мне было нужно:
- URL-адрес обновлен без добавления состояний истории;
- Запретить прокрутку по умолчанию;
- CSS
:target
обновлено, гдеhistory.replaceState()
похоже, не работает.
Мой подход:
- Использовать
location.replace()
но сохраните текущую позицию прокрутки непосредственно перед этим; - Восстановите сохраненную позицию прокрутки (только один раз) либо в окне
hashchange
обработчик событий или документscroll
обработчик событий – в зависимости от того, что произойдет раньше.
Следующий пример делает это и реализует пользовательскую прокрутку (которую можно опустить для конкретной цели OP):
<!DOCTYPE html>
<style>
table { border-collapse: collapse }
td { border: thin solid;
min-width: 500px; height: 250px;
text-align: center;
vertical-align: middle }
td:target { outline: medium dashed green;
outline-offset: 2px }
</style>
<table>
<tr>
<td id="1-1">1-1</td>
<td id="1-2">1-2</td>
<td id="1-3">1-3</td>
<td id="1-4">1-4</td>
</tr>
<tr>
<td id="2-1">2-1</td>
<td id="2-2">2-2</td>
<td id="2-3">2-3</td>
<td id="2-4">2-4</td>
</tr>
<tr>
<td id="3-1">3-1</td>
<td id="3-2">3-2</td>
<td id="3-3">3-3</td>
<td id="3-4">3-4</td>
</tr>
<tr>
<td id="4-1">4-1</td>
<td id="4-2">4-2</td>
<td id="4-3">4-3</td>
<td id="4-4">4-4</td>
</tr>
<tr>
<td id="5-1">5-1</td>
<td id="5-2">5-2</td>
<td id="5-3">5-3</td>
<td id="5-4">5-4</td>
</tr>
</table>
<script>
(() => {
let savedScroll;
let scrollTarget;
window.addEventListener("hashchange", scrollTargetCellIntoView);
document.addEventListener("scroll", scrollTargetCellIntoView);
document.querySelectorAll("td[id]").forEach(cell => {
cell.onclick = evt => selectTargetCell(evt.target);
});
function replaceLocationHash(fragId) {
// Doesn't update CSS :target
//history.replaceState({}, "", "#" + cell.id);
savedScroll = { y: window.scrollY,
x: window.scrollX };
location.replace("#" + fragId);
}
function selectTargetCell(cell) {
scrollTarget = cell;
replaceLocationHash(cell.id);
}
function scrollTargetCellIntoView() {
if (savedScroll) {
window.scrollTo({ top: savedScroll.y,
left: savedScroll.x,
behavior: "instant" });
savedScroll = null;
}
if (scrollTarget) {
scrollTarget.scrollIntoView({ block: "nearest",
inline: "nearest",
behavior: "smooth" });
scrollTarget = null;
}
}
})();
</script>
Если на вашей странице вы используете id как своего рода точку привязки, и у вас есть сценарии, в которых вы хотите, чтобы пользователи добавляли #something в конец URL-адреса и прокручивали страницу до этого раздела #something, используя свой собственный определенный анимированный Функция javascript, слушатель события hashchange не сможет это сделать.
Если вы просто поместите отладчик сразу после события hashchange, например, что-то вроде этого (ну, я использую jquery, но вы поняли):
$(window).on('hashchange', function(){debugger});
Вы заметите, что как только вы измените свой URL и нажмете кнопку ввода, страница сразу остановится на соответствующем разделе, только после этого будет запущена ваша собственная определенная функция прокрутки, и она будет прокручиваться в этот раздел, который выглядит очень плохой.
Мое предложение:
не используйте id в качестве точки привязки к разделу, к которому вы хотите перейти.
Если вы должны использовать 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);
}
удалить идентификатор в вашем теге и сбросить его.
Это должно сделать свое дело.
Другой способ сделать это - добавить div, который скрыт в верхней части области просмотра. Затем этому div присваивается идентификатор хеша до того, как хеш добавляется в URL.... так что вы не получите прокрутку.
Это решение создает 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();
У меня есть более простой метод, который работает для меня. В основном, помните, что на самом деле хэш в HTML. Это якорная ссылка на тег имени. Вот почему он прокручивает... браузер пытается прокрутить до ссылки привязки. Так дайте это!
- Прямо под тегом BODY поместите свою версию этого:
Назовите свои разделы div с классами вместо идентификаторов.
В коде обработки удалите хэш-метку и замените ее точкой:
var trimPanel = loadhash.substring (1); // теряем хеш var dotSelect = '.' + trimPanel; // заменить хеш точкой. $(DotSelect).addClass("ActivePanel") шоу (); // показать div, связанный с хешем.
Наконец, удалите element.preventDefault или return: false и разрешите навигацию. Окно останется сверху, хеш будет добавлен к URL адресной строки, и откроется правильная панель.
Вот мое решение для вкладок с поддержкой истории:
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 вещь, и без нее ваши вкладки с историей не будут работать.
Я думаю, что вам нужно сбросить свиток в его положение перед заменой.
$(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 );
});
});
Добавляйте этот код в 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;
}
}
});
});