«Это веселый сезон, а также захватывающее время для разработчика JavaScript. С появлением повального увлечения Web 2.0 родилась новая разновидность JavaScript-разработчика. Современные программисты на JavaScript очень серьезно относятся к своей профессии и считают себя членами «настоящих» программистов. Ключевым компонентом в арсенале программиста JavaScript является методология ненавязчивого JavaScript — идея о том, что поведение веб-страницы должно оставаться отдельным от ее структуры. Идея ненавязчивого JavaScript возникла в результате движения веб-стандартов, в котором предлагалось разделить веб-страницы на три уровня — структура (HTML), представление (CSS) и поведение (JavaScript) — и что каждый дополнительный слой должен улучшать предыдущий один.
Традиционно большинство, если не все, JavaScript на основе событий записывались непосредственно в разметку веб-страницы в форме атрибутов обработчика событий, таких как onclick
, onfocus
, onload
, onmouseover
и onmouseout
. Кроме того, вся динамически создаваемая разметка принимает форму операторов document.write
месте. Но ничто из этого не соответствует принципу ненавязчивого JavaScript.
Так же, как подарки — это не то, что представляет собой Рождество, так и JavaScript — это не то, чем является веб-страница. Страница должна быть функциональной без каких-либо сценариев, а скорее зависеть от нее. Вызовы функций JavaScript и инструкции, которые неразрывно связаны с разметкой, создают именно такую зависимость. Они также уменьшают переносимость HTML-документа и усложняют его обслуживание по мере увеличения числа страниц сайта. Но хуже всего то, что они попадут в список непослушных Санты — и никто не хочет быть там!
Покупка подарков
Ненавязчивый JavaScript диктует, что в идеале сценарии должны находиться в отдельном документе и подключаться к веб-странице с помощью id
HTML и атрибутов class
. Точно так же вся динамически генерируемая разметка должна быть вставлена в DOM после того, как она была построена с использованием специальных методов DOM. Таким образом, если страница уже функционирует до добавления JavaScript, поведенческий слой становится улучшением документа, а не зависимостью — что-то вроде глазури на торте или подарков на Рождество.
Теперь мы не живем в идеальном мире. Иногда мы сталкиваемся с проектами из нескольких поколений, которые не были хорошо документированы или не поддерживаются. В других случаях наш мандат (и, следовательно, бюджет) не распространяется на полное обновление или оптимизацию существующего кода, который нам предлагается изменить. Веб-страницы не всегда легкие, сети не всегда быстрые, и в условиях групповой разработки разработчики не всегда имеют полный контроль над всеми компонентами страницы. Имея это в виду, давайте посмотрим на неприятный побочный эффект реализации ненавязчивого JavaScript, когда условия не оптимальны.
Принося подарки домой через парадную дверь
Есть два способа загрузить JavaScript в HTML-документ. Традиционный подход заключается в размещении <script>
заголовке документа и запуске ваших функций с помощью события onload
объекта window. Мы будем называть это «фронтальной загрузкой», потому что сценарии загружаются до содержимого страницы в <head>
, до того, как будет создан DOM. Фронтальная загрузка не очень хорошая идея, потому что она уязвима к временным проблемам. Например, браузер загружает, анализирует и выполняет JavaScript, где бы он ни встречался в источнике веб-страницы, поэтому любой JavaScript в <head>
будет задерживать рендеринг страницы до тех пор, пока этот процесс не будет завершен. Что еще более важно, после того, как это сделано и страница отрисована, функции, связанные с событием onload
объекта окна, могут не запускаться сразу. Это происходит потому, что событие запускается только после того, как браузер завершил загрузку всех зависимостей страницы, включая изображения и другие мультимедийные материалы, которые часто встречаются на веб-страницах, в несколько сотен килобайт.
Фронтальная загрузка может вызвать нежелательный эффект, при котором посетитель видит полную страницу без JavaScript в течение периода, в течение которого он или она может нажимать на что-либо. Так, например, если бы якорь предназначался для запуска модального всплывающего окна ( div
для CSS-стероидов, маскирующегося под всплывающее окно), он не сделал бы этого в течение этого периода загрузки, потому что JavaScript, требуемый для настройки модального поведения, еще не будет выполнить, так как событие onload
объекта окна не сработало бы. Вместо этого, после щелчка по привязке, браузер просто отправит пользователя на URI, указанный в атрибуте привязки href
. Конечным результатом будет то, что страница не будет работать так, как задумано. Конечно, наличие действующего URI в привязке позволяет посетителю продолжать использовать сайт, но это не является желаемым или предполагаемым эффектом.
Вот как выглядит ненавязчивый скрипт с фронтальной загрузкой:
front-load.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Welcome</title> <script> function initSignin() { var signin = document.getElementById("signin"); signin.onclick = function () { /* * Sign-in code that creates a modal * popup goes here. */ alert('Pretend this is a modal popup...'); return false; // Stop the anchor's default behavior }; } window.onload = initSignin; </script> <link rel="stylesheet" type="text/css" media="all" href="style.css"> </head> <body> <p class="xmas"> <a href="/signin/" id="signin">Sign in</a> </p> <!-- 700 kilobytes worth of media goes here --> </body> </html>
Вы заметите, что выполнение нашей функции initSignin
откладывается до тех пор, пока содержимое страницы не будет загружено. В функции initSignin
мы останавливаем поведение якоря «Вход в систему» по умолчанию, возвращая ему значение false. Однако браузер не будет запускать событие onload объекта окна, пока не будет загружено семьсот килобайт мультимедиа. Таким образом, пока он не initSignin
получение этих файлов, initSignin
не будет работать, и поведение нашей ссылки не будет переопределено.
Красться подарки через заднюю дверь
Второй и идеальный способ загрузки JavaScript в HTML-документ — поместить все наши теги <script>
в самый конец документа, прямо перед закрывающим </body>
. Это позволяет нам быть уверенными в том, что DOM готов к действию, поскольку код загружается после загрузки всего HTML-кода <body>
в DOM. Это устраняет необходимость в обработчике события onload
объекта window
. Это также значительно уменьшает время ожидания между рендерингом страницы и выполнением нашего JavaScript, потому что его выполнение не зависит от события, которое запускается только после завершения загрузки всех зависимостей документа. В этом случае код для всплывающей ссылки будет выполняться намного раньше, и, вероятно, он уже будет создан до того, как посетитель даже подумает о нажатии на ссылку «Войти».
Вот как выглядит ненавязчивый скрипт с обратной загрузкой:
back-load.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Welcome</title> <link rel="stylesheet" type="text/css" media="all" href="style.css"> </head> <body> <p class="xmas"> <a href="/signin/" id="signin">Sign in</a> </p> <!-- 700 kilobytes worth of media goes here --> <script> var signin = document.getElementById("signin"); signin.onclick = function () { /* * Sign-in code that creates a modal * popup goes here. */ alert('Pretend this is a modal popup...'); return false; // Stop the anchor's default behavior }; </script> </body> </html>
Обратите внимание, что между нашей ссылкой и нашим кодом существует около семи сотен килобайт мультимедиа, но это не имеет значения, потому что браузер не загружает мультимедиа последовательно, как это делает JavaScript. Таким образом, он будет запускать несколько запросов на мультимедиа, но он будет выполнять JavaScript, даже когда эта операция выполняется.
Тем не менее, могут быть проблемы, даже с обратной загрузкой.
Скрыть подарки, пока не пришло время их раздавать
Может случиться так, что на вашей странице много JavaScript для обработки или что сервер, на котором размещены ваши скрипты, испытывает кратковременную задержку. Даже если вы вернетесь к загрузке своих сценариев, подобные ситуации могут помешать им сразу же начать работу. Это может привести к странному поведению, такому как вышеупомянутые ссылки, не переопределяемые во времени, или даже к проблемам с компоновкой. Последняя проблема возникает, когда вы изменяете DOM с помощью сценариев — например, если вы добавляете имена классов, которые будут вызывать применение правил CSS, вставляя элементы в DOM или корректируете положение или размеры существующего элемента. Если код JavaScript выполняется даже с небольшим опозданием, а изменения в DOM происходят после первоначального рендеринга, в результате элементы будут сдвигаться на странице, или, что еще хуже, текст будет отображаться на короткое время, прежде чем он будет скрыт запоздалым выполнением функция.
Техника для решения проблемы неизбежности этого сценария состоит в том, чтобы скрыть уязвимый контент до его визуализации. Это будет означать написание правила CSS по следующим направлениям:
.modal { visibility: hidden; }
Мы дадим modal
имя класса всем якорям на странице, которые должны вызывать модальное всплывающее окно. Затем мы напишем в нашей функции строку кода, которая переопределяет поведение якорей по умолчанию, так что, как только он выполнит свою работу, он устанавливает видимость якоря в видимый, например, так:
el.style.visibility = "visible";
Мы должны быть осторожны, чтобы не создавать новые проблемы при решении других. Устанавливая видимость всех ссылок с именем модального класса на странице скрытыми, мы рискуем заблокировать любого, у кого нет JavaScript. Этот риск существует, потому что механизмом, скрывающим ссылки, является CSS, а механизмом, который делает их видимыми, является JavaScript. Располагая двумя уровнями разделения, мы предполагаем, что «каждый, у кого есть CSS, также имеет JavaScript», что не всегда так. Итак, нам нужно создать правило модального стиля с использованием JavaScript. Таким образом, если JavaScript недоступен, правило никогда не создается, а ссылки никогда не скрываются. Это ситуация, когда обратная загрузка — плохая идея, потому что мы хотим, чтобы это правило было доступно как можно скорее. Вот как бы выглядела наша страница примера входа в систему, если бы мы использовали эту технику:
hide-content.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Welcome</title> <link rel="stylesheet" type="text/css" media="all" href="style.css"> <script> document.write('<style type="text/css">.modal {visibility: hidden;}</style>'); </script> </head> <body> <p class="xmas"> <a href="/signin/" id="signin" class="modal">Sign in</a> </p> <!-- 700 kilobytes worth of media goes here --> <script> var signin = document.getElementById("signin"); signin.onclick = function () { /* * Sign-in code that creates a modal * popup goes here. */ alert('Pretend this is a modal popup...'); return false; // Stop the anchor's default behavior }; signin.style.visibility = "visible"; </script> </body> </html>
Вы заметите, что я использовал document.write
, чтобы создать модальное правило стиля. Хотя я никогда не защищаю использование document.write
, это единственное место, где я готов сделать исключение. В этом примере используется блок <style>
, но я обычно использовал бы на реальном сайте внешний CSS-документ, который содержал бы все правила, которые невозможно отменить без JavaScript — например, visibility: hidden
. Написание <link>
который вызывает этот CSS-документ, с document.write
— это простое решение, состоящее из одной строки, позволяющее убедиться, что браузер вызывает этот файл, пока он еще обрабатывает содержимое <head>
(если JavaScript доступен).
Вы также заметите, что я добавил строку, которая сбрасывает видимость якоря сразу после того, как я назначил функцию его обработчику события onclick
. Другими словами, теперь, когда я уверен, что якорь будет вести себя так, как я хочу, я могу снова включить его.
Есть много способов показать и скрыть контент, и каждый из них действует в определенных контекстах. В этой ситуации я решил использовать visibility: hidden
потому что он сохраняет размеры элемента, скрывая его. Если бы я использовал display: none
, например, пространство, которое обычно занимает якорь, разрушилось бы, и включение его привело бы к небольшому смещению макета документа. Другой способ скрытия и отображения содержимого — установить absolute
позицию элемента, а его левое значение -3000px
, отправив его за левый край экрана. Вернуть его так же просто, как установить его положение на относительное или статическое или присвоить ему левое значение, которое вернет его в видимую область страницы.
Упаковка подарков
Итак, мы снова загрузили наш JavaScript и скрыли контент, на который влияет наш код, но просто выложить его на экран не очень изящно, и это не дает посетителю абсолютно никаких признаков того, что какой-то контент находится на его пути. Это как рождественские подарки: вы не храните их в распакованном виде и не кладете их в шкаф, пока не пришло время раздавать их. Вы оборачиваете их и оставляете так, чтобы люди знали, что у них есть что-то на пути. То же самое относится и к контенту, который вы обрабатываете, но который скрыт. Наиболее распространенный способ указать, что что-то идет, — это использовать анимированную графику в качестве визуальной подсказки.
Давайте добавим загрузчик на наш якорь «Вход»:
loader.css .modal { background: url(loading.gif) no-repeat center left; } .modal a { visibility: hidden; } loader.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Welcome</title> <link rel="stylesheet" type="text/css" media="all" href="style.css"> <script> document.write('<link rel="stylesheet" type="text/css" href="loader.css">'); </script> </head> <body> <div class="xmas"> <p class="modal"> <a href="/signin/" id="signin">Sign in</a> </p> <!-- 700 kilobytes worth of media goes here --> <script src="loader.js"></script> </div> </body> </html> loader.js var signin = document.getElementById("signin"); signin.onclick = function () { /* * Sign-in code that creates a modal * popup goes here. */ alert('Pretend this is a modal popup...'); return false; // Stop the anchor's default behavior }; signin.style.visibility = "visible"; signin.parentNode.style.background = "none";
Первое, что я здесь сделал, — это создание отдельных файлов CSS и JS, потому что наш пример вырос, и всегда лучше хранить CSS и JavaScript в отдельных файлах. Я добавил новое правило фона CSS, которое добавляет графический элемент-загрузчик к родительскому элементу нашего якоря «Вход». Таким образом, хотя якорь скрыт, его родительский элемент отображает вращающуюся графику, которая указывает, что что-то будет занимать это пространство на мгновение. Я также переместил имя модального класса до родительского элемента якоря, так как нам нужно, чтобы он содержал нашу загрузочную графику. Наконец, я добавил еще одну инструкцию в наш блок кода; он удаляет графику загрузчика, когда наша операция назначения по onclick
завершена.
Поскольку этот пример настолько мал, вы вряд ли когда-либо испытаете задержку, достаточную для того, чтобы вы могли увидеть графику загрузчика. По этой причине я собрал пример, который имитирует двухсекундную задержку, чтобы вы могли видеть загрузчик в действии.
Заворачивать все остальное
Эта техника не ограничивается только текстовым содержанием; мы также можем добавить загрузчики к изображениям. Однако вместо того, чтобы вручную переключаться с загрузчика на контент, мы настроим обработчик событий, который определяет, когда браузер заканчивает загрузку изображения. Мы сделаем это через обработчик события onload. Как только событие запускается браузером, наш код будет обрабатывать переключение.
В этом примере мы сделаем вещи немного по-другому, просто чтобы изучить различные возможности реализации. В предыдущих примерах мы манипулировали объектом стиля элемента напрямую через JavaScript. Этот подход не всегда может быть подходящим, так как разработчикам может потребоваться более прямой контроль над различными состояниями элемента с помощью CSS. Поэтому для этого примера мы определим имя класса загрузки, который будет назначен элементам, которые, ну, в общем, загружаются. Как только загрузка будет завершена, все, что мы сделаем, это удалим имя класса.
Давайте начнем с разметки:
loader-img.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html> <head> <title>Welcome</title> <link rel="stylesheet" type="text/css" href="loader-img.css"> <link rel="stylesheet" type="text/css" media="all" href="style.css"> <script> document.write('<link rel="stylesheet" type="text/css" href="loader-img-js.css">'); </script> </head> <body> <ul id="thumbnails"> <li class="loading"><img src="img1.jpg"></li> <li class="loading"><img src="img2.jpg"></li> <li class="loading"><img src="img3.jpg"></li> <li class="loading"><img src="img4.jpg"></li> <li class="loading"><img src="img5.jpg"></li> <li class="loading"><img src="img6.jpg"></li> <li class="loading"><img src="img7.jpg"></li> <li class="loading"><img src="img8.jpg"></li> <li class="loading"><img src="img9.jpg"></li> <li class="loading"><img src="img10.jpg"></li> <li class="loading"><img src="img11.jpg"></li> <li class="loading"><img src="img12.jpg"></li> <li class="loading"><img src="img13.jpg"></li> <li class="loading"><img src="img14.jpg"></li> <li class="loading"><img src="img15.jpg"></li> </ul> <script src="loader-img.js"></script> <p class="caption"><strong>Image Credit:</strong> <a href="http://www.sxc.hu/profile/danzo08/">Daniel Wildman</a></p> </body> </html>
У нас есть простой список изображений. Каждому элементу списка присваивается имя класса загрузки, поскольку мы знаем, что на момент создания DOM изображения еще не были загружены.
Мы также включили два файла CSS: один, содержащий основные правила компоновки, и другой, связанный с помощью оператора JavaScript document.write, который скрывает контент, который будет сделан видимым позже JavaScript:
loader-img.css #thumbnails { list-style-type: none; width: 375px; } #thumbnails li { width: 125px; height: 125px; float: left; } loader-img-js.css #thumbnails li.loading { background: url(loader-big.gif) no-repeat center center; } #thumbnails li.loading img { visibility: hidden; }
Наконец, и самое главное, вот скрипт, который реализует загрузчик для каждого из наших изображений:
loader-img.js var thumbs = document.getElementById("thumbnails"); if (thumbs) { var imgs = thumbs.getElementsByTagName("img"); if (imgs.length > 0) { for (var i = 0; imgs[i]; i = i + 1) { var img = imgs[i]; var newImg = img.cloneNode(false); img.parentNode.insertBefore(newImg, img); newImg.onload = function () { var li = this.parentNode; li.className = li.className.replace("loading", ""); }; newImg.src = img.src; img.parentNode.removeChild(img); } } }
Здесь мы берем элемент контейнера, который окружает наши миниатюры и все изображения внутри него. Теперь обычно мы просто перебираем изображения и назначаем обработчик события onload
для каждого из них. К сожалению, Firefox не onload
событие onload
для изображений, которые уже находятся в кэше, поэтому наш скрипт не будет работать при последующих посещениях страницы. Чтобы обойти эту проблему, мы просто клонируем изображение и заменяем оригинал его клоном. Вставка недавно клонированного изображения в документ гарантирует, что его событие onload сработает.
Следует также отметить, что Internet Explorer и Opera требуют, чтобы обработчик события onload
был назначен перед атрибутом src
. В противном случае они не будут onload
событие onload
. Когда событие инициируется, скрипт удалит загрузку имени класса из родительского элемента изображения. Это, в свою очередь, приводит к тому, что элемент списка теряет свое вращающееся фоновое изображение, а изображение теряет visibility: hidden;
декларация, которая скрывала это. На мой взгляд, манипулирование именами классов на сегодняшний день является наиболее элегантным способом переключения стиля элемента, поскольку он хранит всю информацию о презентации в отдельном файле, который посвящен задаче. Это также позволяет в будущем вносить изменения в правила стиля с помощью модификаций CSS — без необходимости открывать файл JavaScript.
Если этот пример выполняется слишком быстро для вас, я собрал другой пример, который имитирует случайную задержку , чтобы вы могли понять, как этот пример будет выглядеть при более медленном соединении.
Пост-триптофановые приключения
Мы исследовали несколько способов безопасного решения ненавязчивых проблем с синхронизацией JavaScript. Мы также рассмотрели способы решения проблем синхронизации в целом, включив загрузчики в качестве заполнителей для нашего скрытого контента. Я надеюсь, что этот урок побудил вас мыслить креативно и ненавязчиво (не забудьте скачать полный код ). Теперь возьмите то, что вы узнали здесь, и создайте удивительно ненавязчивые, изящно разлагаемые и радостные веб-сайты. Ho! Ho! Ho!