27 мая 2012 г.

Плавное обновление страницы HTML

Реализовал интересное решение задачи обновления информации на странице, открытой в браузере. Задача: на открытой странице необходимо отображать актуальные данные с сервера (периодически обновлять данные), без лишних перерисовок, дёрганий, потери фокуса и тому подобных негативных эффектов.
Возможные решения задачи обновления информации на странице:

  1. Тег <meta http-equiv="refresh" content="5; url=http://example.org/page.html" /> - браузер самостоятельно перезагружает страницу каждые 5 секунд (естественно, интервал может быть любым).
    Плюсы: максимальная простота и надёжность; работает везде; не требует скриптов; не требует усложнения клиентского и серверного кода.
    Минусы: страница полностью перезагружается и перерисовывается даже если на сервере ничего не изменилось; во время перезагрузки страницы пользователь не может ничего делать; теряется фокус и положение полос прокрутки, теряется вообще всё состояние страницы (т.е. надо его запоминать/восстанавливать).
    Применение: подходит только для страниц пассивного наблюдения за небольшим объёмом информации (мониторинг серверов, курсов валют, погоды и т.п.).
  2. AJAX-запросы на сервер "надо ли обновлять страницу?" (new Ajax.Request()). При утвердительном ответе делаем обновление фрагмента страницы ещё одним AJAX-запросом (new Ajax.Updater()). Как вариант (образ мышления ExtJS и YUI), создание view и рендеринг выносится на клиент, с сервера передаётся только изменившийся JSON-объект.
    Плюсы: никаких негативных эффектов при отсутствии изменений на сервере; страница не перезагружается полностью; минимальный объём пересылаемых данных.
    Минусы: необходимо на сервере отслеживать изменения видимой конкретным клиентом страницы, то есть частично или полностью дублировать механизм рендеринга view, и запоминать и сравнивать два состояния view для каждого клиента (простое сравнение состояний модели не подойдёт: например, курс доллара изменился во втором знаке после запятой, а пользователь выбрал точность просмотра только до первого знака); необходимо реализовать обслуживание трёх типов запросов - запрос полной страницы при первой загрузке, запрос "надо ли обновлять?", запрос изменившегося фрагмента страницы; состояние обновляемого фрагмента надо восстанавливать после обновления (фокус на полях ввода, слушатели событий элементов - если  не используется делегирование).
    Применение: как подсказывает К.О. - обновление фрагментов страницы при изменении данных на сервере.
Когда я понял, что эти варианты никак не устраивают пользователей, я придумал свой принцип максимально плавного обновления любой страницы.
Отправная точка: сервер не знает и не должен знать об изменении отображаемых конкретным клиентом страниц - это дело клиента. Запросы на первое отображение страницы (фрагмента) и на его обновления никак не отличаются с точки зрения сервера ("сервер, дай мне weather.jsp с актуальными метеоданными, а я сам разберусь, что с ними делать"). Соответственно серверный код при введении обновления никак не усложняется.
Основная работа идёт в браузере-клиенте. Скрипт с заданной частотой запрашивает с сервера необходимый фрагмент документа HTML (пресловутый блок с прогнозом погоды). Далее полученный фрагмент сравнивается с уже существующим, показанным пользователю (с поддеревом DOM, которое надо обновить), т.е. выполняется синхронный обход двух поддеревьев и поиск возможных изменений:
  • Добавление узла. Если в новом поддереве есть узел, которого нет в старом (например, добавился DIV) - он со всем содержимым (вложенные узлы) копируется на нужное место в документ.
  • Удаление узла. Если в новом поддереве нет соответствия существующему узлу (например, удалён SPAN) - узел удаляется из документа.
  • Изменение атрибутов узла. Если изменились атрибуты узла-элемента (у DIV изменился CSS-класс) или значение узла-текста (в параграф добавили ещё больше букв), то соответствующим образом изменяются узлы в документе.
Вот вкратце и вся идея. Основной плюс её в том, что на странице происходят минимально возможные изменения (в том же прогнозе погоды в основном будет меняться только значение некоторых текстовых узлов), нет массированных модификаций DOM (удаление фрагмента - вставка фрагмента). Даже если внутри обновляемой области находились фокус ввода и элементы с overflow и полосами прокрутки - они ничего не почувствуют, если изменения их не касаются, фокус и прокрутка останутся неизменными без каких-либо дополнительных действий.

2 комментария:

  1. Victor помогите пожалуйста я имею код показывающий состояние скайп клиента, можно ли его обновлять автоматически без перезагрузки страницы? заранее благодарен за ответ

    ОтветитьУдалить
    Ответы
    1. Если кратко - скорее всего можно, главное, чтобы AJAX на сервер нормально ходил. Добавь меня в G+, там подробности обсудим.

      Удалить