Как мне сохранить позицию прокрутки в MVC?
Я работаю над проектом в MVC и с удовольствием узнал об этом. Есть несколько растущих болей, но как только вы их выясните, это не плохо. Одна вещь, которая действительно проста в мире WebForms - это сохранение позиции прокрутки на странице. Все, что вам нужно сделать, это установить для свойства MaintainScrollPositionOnPostback значение true. Тем не менее, в MVC я не использую обратные передачи, так что это не будет работать для меня. Каков стандартный способ справиться с этим?
Изменить: Ajax является приемлемым, но мне также было интересно, как бы вы сделали это без AJAX.
11 ответов
Метод MaintainScrollPositionOnPostback работает так, что у него есть пара скрытых полей: __SCROLLPOSITIONX и __SCROLLPOSITIONY
На обратной передаче, он устанавливает эти,
function WebForm_GetScrollY() {
if (__nonMSDOMBrowser) {
return window.pageYOffset;
}
else {
if (document.documentElement && document.documentElement.scrollTop) {
return document.documentElement.scrollTop;
}
else if (document.body) {
return document.body.scrollTop;
}
}
return 0;
}
function WebForm_SaveScrollPositionSubmit() {
if (__nonMSDOMBrowser) {
theForm.elements['__SCROLLPOSITIONY'].value = window.pageYOffset;
theForm.elements['__SCROLLPOSITIONX'].value = window.pageXOffset;
}
else {
theForm.__SCROLLPOSITIONX.value = WebForm_GetScrollX();
theForm.__SCROLLPOSITIONY.value = WebForm_GetScrollY();
}
if ((typeof(this.oldSubmit) != "undefined") && (this.oldSubmit != null)) {
return this.oldSubmit();
}
return true;
}
и затем он вызывает RestoreScrollPosition:
function WebForm_RestoreScrollPosition() {
if (__nonMSDOMBrowser) {
window.scrollTo(theForm.elements['__SCROLLPOSITIONX'].value, theForm.elements['__SCROLLPOSITIONY'].value);
}
else {
window.scrollTo(theForm.__SCROLLPOSITIONX.value, theForm.__SCROLLPOSITIONY.value);
}
if ((typeof(theForm.oldOnLoad) != "undefined") && (theForm.oldOnLoad != null)) {
return theForm.oldOnLoad();
}
return true;
}
Но, как говорили большинство людей, MVC все равно следует избегать обратных передач.
Я решил это в JS:
$(document).scroll(function(){
localStorage['page'] = document.URL;
localStorage['scrollTop'] = $(document).scrollTop();
});
Тогда в документе готово:
$(document).ready(function(){
if (localStorage['page'] == document.URL) {
$(document).scrollTop(localStorage['scrollTop']);
}
});
На самом деле не существует стандартного способа справиться с этим, это был взлом Microsoft для поддержки их модели постбэк. Они нуждались в этом, потому что каждый элемент управления отправлял сообщение назад, а пользователь постоянно возвращался наверх страницы.
Рекомендация для использования с MVC состоит в том, чтобы пересылать большую часть ваших сообщений обратно на серверы, использующие AJAX. Чтобы страница не перерисовывалась, фокус не перемещается. JQuery делает AJAX действительно простым, и есть даже стандартные формы, такие как
<% Ajax.BeginForm(...) %>
Который позаботится о стороне AJAX для вас.
Вдохновленный WebForms и ответом Ричарда Гадсдена, другой подход с использованием javascript и коллекции форм может выглядеть примерно так:
@{
var scrollPositionX = string.Empty;
if(IsPost) {
scrollPositionX = Request.Form["ScrollPositionX"];
}
}
<form action="" method="post">
<input type="hidden" id="ScrollPositionX" name="ScrollPositionX" value="@scrollPositionX" />
<input type="submit" id="Submit" name="Submit" value="Go" />
</form>
$("#Submit").click(function () {
$("#ScrollPositionX").val($(document).scrollTop());
});
$("#ScrollPositionX").each(function () {
var val = parseInt($(this).val(), 10);
if (!isNaN(val))
$(document).scrollTop(val);
});
Код предоставлен для вдохновения и никоим образом не является предварительным. Вероятно, это можно сделать несколькими различными способами, я думаю, все сводится к тому, как вы решили сохранить значение scrollTop вашего документа в POST. Он полностью работает и должен быть безопасным для всех браузеров, так как мы используем jQuery для прокрутки. Я считаю, что приведенный код не требует пояснений, но я буду рад предоставить более подробное описание того, что происходит, просто дайте мне знать.
Я использовал атрибуты имени в тегах. Яваскрипт не используется.
Страница, на которую я хотел вернуться, содержала теги с атрибутом имени, например .
Страница (просмотр), которую я возвратил из использованного тега Back". Request.UrlReferrer используется для перехода на предыдущую страницу. #Testname прокручивает страницу положение для тега с именем "testname".
Мой собственный обходной путь использует некоторую информацию в ViewData
чтобы узнать, какая область должна отображаться в backnavigation, и немного javascript для позиционирования курсора страницы:
В представлении такой элемент:
<h3 id="tasks">
Contained tasks
</h3>
И JavaScript для изменения положения страницы:
<script type="text/javascript">
addOnLoad(goAnchor);
function goAnchor() {
var paging = <%= //Here you determine (from the ViewData or whatever) if you have to position the element %>;
if (paging == "True") {
window.location.hash = "tasks";
}
</script>
Вы могли бы использовать switch
чтобы определить, какой элемент со страницы просмотра вы должны переместить.
Надеюсь, поможет.
<%
if(!ViewData.ModelState.IsValid)
{
%>
window.location.hash = 'Error';
<%
}
%>
<a name="Error"></a>
Вот простое, чистое решение Javascript, которое я тестировал только в FF4 и IE9.
Идея состоит в том, что это решение должно изящно ухудшаться, возвращаясь к стандарту #anchor
теги на странице. То, что я делаю, это замена тех, #anchor
теги на лету с координатами X и Y, затем при загрузке я просто читаю эти значения из строки запроса и прокручиваю там. Если по какой-то причине это не удается, браузер все равно должен перейти к #anchor
позиция...
Разметка:
<a href="/somecontroller/someaction/#someanchor">My Link</a>
JQuery:
$(function() {
// RESTORE SCROLL POSITION
RestoreScrollPosition();
// SAVE SCROLL POSITION
$('a:not(a[href^="http"])').filter('[href$="#someanchor"]').each(function() {
$(this).click(function() {
var href = $(this).attr('href').replace("#someanchor","");
if (href.indexOf('?') == -1) {
href = href + '?x='
} else {
href = href + '&x='
}
href = href + window.pageXOffset;
href = href + '&y=' + window.pageYOffset;
$(this).attr('href', href);
});
});
}
Пара вспомогательных методов:
function RestoreScrollPosition() {
var scrollX = gup('x');
var scrollY = gup('y');
if (scrollX != null && scrollY != null) {
window.scrollTo(scrollX, scrollY);
return true;
}
return false;
}
function gup(name) {
name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regexS = "[\\?&]" + name + "=([^&#]*)";
var regex = new RegExp(regexS);
var results = regex.exec(window.location.href);
if (results == null)
return "";
else
return results[1];
}
Это соответствует моим потребностям, но может быть более общим / многократно используемым - я был бы рад, если бы кто-то улучшил это...:-)
Очень нехороший способ сделать это - использовать куки.
Если вы используете ОДНУ страницу в вашем MVC, которая обрабатывает другие страницы, вы можете добавить к ней фрагмент кода, который загружает каждую страницу, которая создает файл cookie (если он не существует) с именем "scrolltop". Есть способы сделать так, чтобы javascript автоматически обновлял этот файл cookie, когда пользователь прокручивает вверх или вниз, перехватывая эти события или просматривая значение scrollTop.
На новой странице вам просто нужно загрузить сохраненную позицию и сделать прокрутку представления там за 0 миллисекунд (с Mootools или любым Ajax-скриптом это должно быть возможно), и пользователь будет точно там, где он был.
Я не знаю много о asp, поэтому я не знаю, существует ли метод для привязки к текущей y-позиции. Javascript - это быстрый и простой способ. Якоря в HTMl могут быть опцией, если вы привязали каждый элемент и разместили привязку на других страницах.
10 лет спустя и другое решение JS. Другое решение JS ожидает прокрутки страницы и при загрузке страницы прокручивается до той позиции, которая была сохранена. Это нормально, вероятно, для большинства людей (хотя это не удаляет значение, поэтому страница всегда будет прокручиваться до этой позиции, когда вы просматриваете эту страницу...). Мое решение - дождаться отправки формы:
(Да, он использует jQuery, но на сайте его много...)
// OWNER'S FORM POSITION
if ($("[js-owner-request]").length) {
$("[js-owner-request]").on("submit", function() {
localStorage['owner-request__scrollTop'] = $(this).offset().top;
});
if (localStorage['owner-request__scrollTop'] !== "null") {
$(document).scrollTop(localStorage['owner-request__scrollTop']);
localStorage['owner-request__scrollTop'] = "null"; // set to null so we don't always scroll...
}
}
Это прокручивает обратно в верхнюю часть формы, таким образом, любые сообщения об ошибках будут видны, поскольку вы могли прокрутить за пределы сводки проверки.
Я использую.scrollTop, как показано ниже, очень просто, он работает даже с несколькими формами в представлении (у меня очень длинное представление, разбитое на несколько форм):
Сначала поместите это свойство внутри модели:
public string scrollTop { get; set; }
И в виде, внутри формы № 1:
@Html.HiddenFor(m => m.scrollTop, new {@id="ScrollForm1"})
внутри формы № 2:
@Html.HiddenFor(m => m.scrollTop, new {@id="ScrollForm2"})
внутри формы № 2:
@Html.HiddenFor(m => m.scrollTop, new {@id="ScrollForm3"})
а затем в нижней части вида:
$(document).ready(function () {
$(document).scrollTop(@Model.scrollTop);
$(document).scroll(function () {
$("#ScrollForm1").val($(document).scrollTop());
$("#ScrollForm2").val($(document).scrollTop());
$("#ScrollForm3").val($(document).scrollTop());
});
});
Ваша позиция прокрутки всегда сохраняется при обратной передаче, потому что поля @Html.HiddenFor хранят вашу текущую прокрутку и передают ее модели при публикации. И затем, когда страница появляется, она получает значение scrollTop из модели. В конце ваша страница будет вести себя как веб-форма, все останется нетронутым.
@{
}
<html>
<head>
<script type="text/javascript">
window.onload = function () {
var div = document.getElementById("dvScroll");
var div_position = document.getElementById("div_position");
var position = parseInt(@Request.Form("div_position"));
if (isNaN(position)) {
position = 0;
}
div.scrollTop = position;
div.onscroll = function () {
div_position.value = div.scrollTop;
};
};
</script>
</head>
<body>
<div id="dvScroll" style="overflow-y: scroll; height: 260px; width: 300px">
1. This is a sample text
<br />
2. This is a sample text
<br />
3. This is a sample text
<br />
4. This is a sample text
<br />
5. This is a sample text
<br />
6. This is a sample text
<br />
7. This is a sample text
<br />
8. This is a sample text
<br />
9. This is a sample text
<br />
10. This is a sample text
<br />
11. This is a sample text
<br />
12. This is a sample text
<br />
13. This is a sample text
<br />
14. This is a sample text
<br />
15. This is a sample text
<br />
16. This is a sample text
<br />
17. This is a sample text
<br />
18. This is a sample text
<br />
19. This is a sample text
<br />
20. This is a sample text
<br />
21. This is a sample text
<br />
22. This is a sample text
<br />
23. This is a sample text
<br />
24. This is a sample text
<br />
25. This is a sample text
<br />
</div>
<hr />
<form method="post">
<input type="hidden" id="div_position" name="div_position" />
<input type="submit" value="Cool" />
</form>
</body>
</html>
Вы можете использовать это для поддержания положения прокрутки после обратной передачи.
Источник: http://www.aspsnippets.com/Articles/Maintain-Scroll-Position-of-DIV-on-PostBack-in-ASPNet.aspx