Изменение URL без перезагрузки страницы

С развитием WEB понятие о странице немного изменилось. Для конечного пользователя улучшением производительности и комфорта путешествия по сайту, для разработчиков — немного иной реализацией. Понятно, что страница целиком состоит из множества элементов, большая часть которых не изменяется при переходе на новую страницу. Зачем заставлять пользователя смотреть по 100 раз как рендерится один и тот же документ?

Конечно, было бы правильней изменять только ту часть, которая действительно изменятся. Это стало чуть ли не стандартом в WEB >= 2.0. А именно асинхронные переходы. Теперь можно делать различную анимацию между переходами, начиная от простых gif-loading, заканчивая 3D-transition.

Проблема

Ссылки являются основной точкой возврата к определенному состоянию сайта. И было бы хорошо, чтобы после открытия страницы асинхронно менялась ссылка, которую человек может сохранить, отправить другу и т.д. Но когда-то не было возможности просто так из JS менять ссылку без полной перезагрузки страницы.

Решение 1

Которое я использовал около года. Это хэши. В JS легко можно установить hash страницы, написать функцию, которая его обработает и т.д. Примерно было так:

function handleHash() {
    var h = window.location.hash.substring(1).replace(/&/, '');

    switch (h) {
        case 'profile':
            // ...
        case 'dashboard':
            // ...
    }
}

document.getElementById('profile-link').onclick = function() {
    window.location.hash = 'profile';
    handleHash();
}

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

Решение 2. Правильное.

Первая часть с асинхронной загрузкой остается такой же. А вот для изменения URL нам нужен HTML5 History API. Это путь-стандарт для управления историей браузера из JS. На самом деле, это всего лишь несколько методов в JS. Появился этот стандарт не так давно, но уже смело можно его использовать, кроме IE.

Firefox	4.0 +
Safari	5.0 +
Chrome	8.0 +
Opera	11.50 +
iPhone	4.2.1 +
IE	-
Android	-

Для браузеров, которые не поддерживают History API можно написать костыль с хэшами. Проверить, работает ли в вашем браузере эта фича можно так:

function isHhistoryApiAvailable() {
    return !!(window.history && history.pushState);
}

А теперь сделаем небольшое Демо. У нас будет страница с общим шаблоном (index.php):

Это общий шаблон, динамическое содержимое будет находиться в #content. Соответственно, страницы pageN.php не будут содержать в себе всего шаблона, а только контент. jQuery я подключил только для того, чтобы было меньше кода, так как будет ajax. Ниже приведен JS, который получает при клике на ссылку содержимое и вставляет его в #content.

$(document).ready(function() {
    $('a').click(function() {
        var url = $(this).attr('href');

        $.ajax({
            url:     url + '?ajax=1',
            success: function(data){
                $('#content').html(data);
            }
        });

        // А вот так просто меняется ссылка
        if(url != window.location){
            window.history.pushState(null, null, url);
        }

        // Предотвращаем дефолтное поведение
        return false;
    });
});

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

И еще небольшой кусочек кода, который делает рабочими кнопки “Вперед” и “Назад”.

$(window).bind('popstate', function() {
    $.ajax({
        url:     location.pathname + '?ajax=1',
        success: function(data) {
            $('#content').html(data);
        }
    });
});