В. В. ЯНЦЕВ JAVASCRIPT КАК ПИСАТЬ ПРОГРАММЫ Учебное пособие •САНКТПЕТЕРБУРГ•МОСКВА•КРАСНОДАР• •2022• УДК 004 ББК 32.973я73 Я 65 Янцев В. В. JavaScript. Как писать программы : учебное пособие для вузов / В. В. Янцев. — СанктПетербург : Лань, 2022. — 200 с. : ил. — Текст : непосредственный. ISBN 9785811485598 В книге рассматриваются все этапы написания сценариев на JavaScript: от появления идеи до финальных испытаний. Читатели узнают, как про исходят: подготовка среды разработки на персональном компьютере; фор мирование алгоритма выполнения проекта; освоение приемов написания качественных сценариев; работа с переменными, массивами, операторами, регулярными выражениями, функциями; тестирование, отладка и стан дартизация кода. Особое внимание уделено методам, приемам и навыкам, которые помогут разработчику упростить создание интернетпроектов. Кроме того, подробно разобраны примеры нескольких готовых сценариев. Рекомендовано в качестве дополнительной литературы для студентов вузов, обучающихся по направлению «Информатика и вычислительная техника». УДК 004 ББК 32.973я73 Обложка П. И. ПОЛЯКОВА © Издательство «Лань», 2022 © В. В. Янцев, 2022 © Издательство «Лань», художественное оформление, 2022 Оглавление 1. Введение ......................................................................................................................................... 5 1.1. О чем эта книга .................................................................................................................. 5 1.2. Особенности изложения материала ................................................................................. 6 1.3. Оформление сценариев ..................................................................................................... 7 1.4. Скромное напутствие ........................................................................................................ 8 1.5. Благодарности .................................................................................................................. 10 2. Программное обеспечение .......................................................................................................... 11 2.1. Установка сервера Apache .............................................................................................. 12 2.2. Установка PHP 7 .............................................................................................................. 16 2.3. Установка Python 3 .......................................................................................................... 18 2.4. Установка Perl 5 ............................................................................................................... 23 2.5. Установка Ruby 3 ............................................................................................................. 29 2.6. Устанавливать ли базу данных? ..................................................................................... 34 2.7. Редакторы кода ................................................................................................................ 37 3. Подготовительные работы .......................................................................................................... 48 3.1. Алгоритм действий.......................................................................................................... 48 3.2. Шаблон страницы ............................................................................................................ 49 3.3. Добавляем элементы ....................................................................................................... 51 3.4. Таблицы стилей ............................................................................................................... 54 3.5. Размещаем сценарий ....................................................................................................... 56 4. Некоторые особенности программирования ............................................................................. 57 4.1. События ............................................................................................................................ 57 4.2. Обработчики событий ..................................................................................................... 59 4.3. Остановка, удаление обработчика ................................................................................. 64 4.4. Глобальные и локальные переменные ........................................................................... 67 4.5. let или var (или const)? ..................................................................................................... 70 4.6. Меньше или больше переменных? ................................................................................ 74 4.7. Массивы............................................................................................................................ 76 4.8. Операторы ........................................................................................................................ 80 4.9. Условные операторы ....................................................................................................... 83 4.10. Операторы циклов ......................................................................................................... 88 4.11. Функции.......................................................................................................................... 92 4.12. Технология Ajax ............................................................................................................ 99 4.13. Подсказки и проверка данных .................................................................................... 105 4.14. Регулярные выражения ............................................................................................... 108 4.15. Обработка строк........................................................................................................... 113 4.16. Комментарии ................................................................................................................ 115 5. Тестирование сценариев ............................................................................................................ 119 5.1. Оптимизация кода ......................................................................................................... 120 5.2. Валидаторы .................................................................................................................... 125 5.3. Браузеры ......................................................................................................................... 129 5.4. Логические ошибки ....................................................................................................... 130 5.5. Кстати ............................................................................................................................. 140 3 6. Примеры сценариев ................................................................................................................... 143 6.1. Выбор картинки ............................................................................................................. 143 6.2. Увеличиваем рисунки ................................................................................................... 149 6.3. Галерея ............................................................................................................................ 156 6.4. Слайдер ........................................................................................................................... 161 6.5. Круговорот изображений .............................................................................................. 168 6.6. Пасьянс из картинок ...................................................................................................... 173 6.7. Проявление и «растворение»........................................................................................ 178 6.8. Рисунки по номерам ...................................................................................................... 183 6.9. Три галереи..................................................................................................................... 188 7. Подведем итоги .......................................................................................................................... 195 8. Об авторе..................................................................................................................................... 197 4 1. Введение Прежде чем купить какую-либо книгу по программированию, потенциальному читателю важно знать, для кого она написана. На этот вопрос я отвечу так: данная книга — для тех, кто уже изучил азы JavaScript и готов приступить к самостоятельному написанию сценариев для web-страниц. Поэтому советую перед ее прочтением ознакомиться с литературой, посвященной основам языка. Например, можете начать с книги А. В. Дикова «Web-программирование на JavaScript» (издательство «Лань», Санкт-Петербург, 2021). Или с любой другой, где рассказано о переменных, операторах, управляющих инструкциях, циклах и функциях. 1.1. О чем эта книга Чтобы у посетителей остались хорошие впечатления о вашем сайте, он должен быть функциональным, интерактивным и привлекательным. Достигается такой результат не только за счет красивого дизайна, но и, в первую очередь, за счет гармоничного и умелого внедрения сценариев на JavaScript. Книг о синтаксисе языка программирования JavaScript много. Я бы даже сказал: очень много. Конечно, каждая из них по-своему хороша, интересна и полезна, но в конечном счете все они об одном и том же. Есть ряд книг, в которых излагаются не только основы языка, но и даются примеры сценариев. Они могут объяснить начинающему разработчику, каким образом создаются реальные проекты. И все же в литературе по JavaScript есть некоторый изъян. По одной важной теме не написано почти ничего. Я программирую с 2003 г., прочитал множество изданий, посвященных JavaScript, и могу судить об этом не понаслышке. Много ли вы видели книг, в которых рассказывалось бы непосредственно о методах, технике и последовательности создания программ? Где излагались бы разные подходы, варианты и приемы написания скриптов? Из которых читатель узнал бы о секретах, хитростях и тонкостях «производства» сценариев на JavaScript? Познакомился бы с рекомендациями по оптимизации, настройке и отладке проектов? Повторюсь: таких книг почти нет. Частично я попытался решить проблему двумя предыдущими книгами: «JavaScript. Готовые программы» и «JavaScript. Обработка событий на примерах» (обе книги выпустило издательство «Лань», Санкт-Петербург, 2021). Од5 нако осталось еще много тем, о которых хотелось бы поведать читателям. Думаю, что некоторые аспекты программирования на JavaScript будут интересны не только начинающим, но и опытным разработчикам. Сомнительные практики Не спешите завершать свое образование в программировании на JavaScript прочтением 2–3 книг. Язык этот предоставляет разработчику настолько богатые и разнообразные возможности, что изучать их можно очень долго, а совершенствовать свои знания всегда полезно, особенно, если вы хотите стать настоящим профессионалом. 1.2. Особенности изложения материала Допустим вы решили создать свой первый сайт, в котором будет код на JavaScript. Что и в какой последовательности делать? Именно об этом и рассказывается в книге. В ней 5 основных глав. Глава 2 «Программное обеспечение». Здесь идет речь о том, как подготовить среду разработки на вашем персональном компьютере. Короткое содержание главы: 1) вам необходимо создать на компьютере локальный хостинг, чтобы проверять сценарии в действии. Для этого понадобится http-сервер. Подробные инструкции по его установке приведены в начале главы; 2) если ваши HTML-страницы должны взаимодействовать с серверными программами, необходимо установить интерпретатор какого-либо языка, пригодного для написания серверных скриптов. Я предложил вам 4 разных языка с инструкциями по установке их интерпретаторов на ваш ПК; 3) необходимо понять, насколько сложной будет первая разработка, будут ли присутствовать в проекте скрытые данные и, соответственно, необходимо ли установить базу данных. Даются примеры наиболее популярных БД; 4) в конце главы рассказано о нескольких редакторах кода, которые удобно и целесообразно использовать при написании HTML-страниц, таблиц стилей, сценариев на JavaScript и серверных программ. Глава 3. Довольно короткая. Здесь рассматриваются основные приемы создания шаблонов страниц, принципы компоновки элементов, добавление таблиц стилей и сценариев на JavaScript в документы. Глава 4. Самая большая. Основные положения данной книги изложены именно в ней. Вы узнаете: на какие события наиболее часто откликаются сценарии; что такое обработчики событий и какими способами их можно зарегистрировать; как остановить или удалить обработчик; 6 в чем различия между глобальными и локальными переменными; какой вариант объявления переменных лучше выбрать и чем эти варианты отличаются; что лучше — больше переменных или, наоборот, меньше; массивы, операторы, условия, циклы — их особенности; какие бывают функции и как передавать в них параметры; о пользе технологии Ajax; как помочь клиенту правильно заполнить форму; в чем польза регулярных выражений; о способах и приемах добавления комментариев к вашему коду. Глава 5 посвящена «шлифовке» сценариев. Мы последовательно разберем: 1) что такое оптимизация кода. На конкретных примерах увидим, какими способами можно реализовать этот процесс; 2) что такое валидаторы, какими они бывают и как помогают корректировать разметку, таблицы стилей и сценарии, чтобы «удержать» их в рамках стандартов; 3) как и в каких браузерах проверять работоспособность ваших программ; 4) логические ошибки (как они возникают и как их находить). Продемонстрируем данный процесс на конкретном примере, специально написанном для этой книги. Глава 6 содержит примеры законченных программ для работы с изображениями. То есть, мы используем полученные знания на практике. 1.3. Оформление сценариев Хочу рассказать вам о принципах, по которым примеры из книги создавались, отбирались и оформлялись. Главных принципа три. Первый. Все примеры написаны по самым современным стандартам, существующим в web-программировании. Второй. Чтобы сделать примеры кода в этой книге максимально простыми и понятными, из них удалены все лишние элементы. Оставлено только самое необходимое. Третий. Все примеры кода проверены в HTML-валидаторе Консорциума Всемирной паутины и JavaScript-валидаторе (я выбрал самый строгий валидатор из тех, что обнаружил в сети). Подробнее о валидаторах вы узнаете в главе 6 (раздел 6.2). На момент написания книги отклонений от стандартов HTML5, CSS3 и JavaScript в примерах не было. Также все примеры были проверены в наиболее популярных браузерах — Microsoft Edge, Google Chrome, Mozilla Firefox, Opera и Яндекс.Браузер. В перечисленных браузерах все примеры работали корректно. 7 Кроме того, сценарии имеют особое типографское оформление в соответствии с их размещением в тексте. Если сценарий выделен в отдельный блок, он оформлен моноширинным шрифтом, например так: let i=0; function func() { i++; alert ("Количество кликов: "+i); } Если фрагменты сценария внедрены непосредственно в текст, то в этом случае части кода выделены полужирным шрифтом, например так: {once: true} Обратите внимание: в некоторых примерах сделан перенос части кода на вторую строку (из-за недостатка ширины страницы). В реальном сценарии код записывается одной строкой. Запомните правило: все переносы строк кода существуют только в их типографском воспроизведении. Если вы в дальнейшем столкнетесь с подобной ситуацией, учитывайте данный аспект. Еще один принципиальный момент. Не все программисты внимательно следят за изменениями в стандартах HTML5. Особенно это касается написания тега <script>. Почти во всех, даже современных, книгах авторы по-прежнему указывают его неправильно. Обратите внимание! В данном теге тип type не указываем! Этому правилу уже несколько лет, но многие разработчики до сих пор не в курсе. Валидатор Консорциума Всемирной паутины воспринимает указание типа в теге <script> как ошибку и выдает предупреждение! Поэтому включение кода в страницу правильно оформлять так: <script> ... </script> И последнее дополнение к разговору о сценариях — вы можете скачать zip-архив со всеми примерами кода. Он находится по адресу https://testjs. ru/kpp/kpp.zip. Сценарии, которые есть в архиве, обозначены рисунком . 1.4. Скромное напутствие Если вы интересуетесь программированием в качестве хобби, естественно, что для окружающих не имеет никакого значения, как вы будете оформлять код, станете ли его оптимизировать, проверите ли результаты своей работы в одном браузере или в нескольких. Словом — что вышло, то и вышло, никаких претензий ни от кого вы не услышите. Главное — получать удовольствие от занятия интересным делом. 8 Однако предположу, что скорее всего вы решили научиться программированию, чтобы превратить этот увлекательный процесс в будущую профессию. А это уже совсем иная ситуация. Все, что вы делаете в качестве начинающего кодера, должно укладываться в определенные рамки, к которым необходимо приучать себя с первых шагов. Знакомясь с приемами написания сайтов и сценариев, примите во внимание несколько простых советов. 1. Во многих IT-компаниях существуют внутренние корпоративные правила оформления программ. Будьте готовы к тому, что в некоторых случаях ваш стиль написания кода придется немного подкорректировать. В разных студиях разные требования и соответствовать сразу всем невозможно. Отнеситесь к этому с пониманием. Ваша задача — создавать программы, с которыми легко и приятно иметь дело не только вам, но и другому специалисту, если ему придется в дальнейшем использовать ваши разработки. 2. Не хотите трудиться в коллективе, а предпочитаете быть фрилансером? Но даже в этом случае не все так просто. Есть немало заказчиков, которые не примут готовый «продукт» и не помашут вам рукой на прощание, пока досконально не проверят, что именно вы им сделали. Так что, чем строже вы оцениваете свою работу, тем больше вероятность написать качественный сценарий с первого раза и не тратить время на исправления и переделки. 3. Хорошим тоном в web-программировании считается подход, при котором вы отделяете разметку страницы от стилей и кода JavaScript. Более того, в корпоративной среде принято все сценарии и таблицы стилей размещать в отдельных файлах. В этой книге мы не станем придерживаться данного правила, но только по одной причине — чтобы упростить изложение. 4. Отступы, пробелы, расположение скобок. В некоторых случаях это важно, а в других — нет. Поясню. Одни разработчики выделяют блоки кода четырьмя пробелами слева, другие используют три и даже два: if(a<5) b=100; // Отступ в 4 пробела if(a<5) b=100; // Отступ в 3 пробела if(a<5) b=100; // Отступ в 2 пробела Замечу, что практика использования четырех пробелов наиболее распространенная. Некоторые программисты ставят первую скобку в одной строке с описанием функции, цикла или условия, другие переносят скобку на следующую строку: function func() { // Скобка в одной строке с именем функции ... } 9 function func() { // Скобка ниже имени функции ... } Первый вариант более распространен, но второй делает программу более читабельной, особенно при большом количестве вложенных условий и циклов. Однако эти различия могут оказаться непринципиальными. Дело в том, что большие сценарии для уменьшения «веса» часто пропускают через специальные программы, которые удаляют все лишние пробелы и переводы строк, сжимая код. В результате размеры отступов и положение скобок не будут иметь никакого значения. 1.5. Благодарности Мне нравится писать книги по программированию на JavaScript. Этот язык очень интересен. И не только своим изяществом, синтаксисом и мощью. Нравится, что JavaScript позволяет создавать потрясающие сценарии, которые делают web-страницы отзывчивыми к действиям посетителей. Нравится, что язык предоставляет разработчику поистине неограниченные возможности для реализации его идей и задумок. Словом, я пишу книги про замечательный язык программирования. Эти книги дошли до читателей благодаря неоценимой помощи многих людей, чьи имена вы не увидите на обложке. Поэтому я хотел бы особо отметить их. Большое спасибо: заведующей редакцией литературы по информационным технологиям и системам связи издательства «Лань» Ольге Евгеньевне Гайнутдиновой — это уже моя третья книга, которую подготовила Ольга Евгеньевна; ответственному редактору издательства «Лань» Наталье Александровне Кривилёвой — за подготовку макетов моих книг и терпеливое внесение исправлений во время этого процесса; моему давнему другу Василию Регентову за многолетнее предоставление «площадки» для моих компьютерных экспериментов и терпеливое решение возникающих — иногда непростых — проблем; братьям Василию и Дмитрию Зиновьевым, в далеком 2003 г. давшим мне путевку в профессию программиста. 10 2. Программное обеспечение Если вы хотите заниматься программированием на серьезном уровне, вам не обойтись без локального хостинга. Вообще хостинг — это, упрощенно говоря, компьютер, предназначенный для размещения на нем сайтов и обеспечивающий к ним доступ из Интернета. Локальный хостинг — набор программ на вашем персональном компьютере, которые позволяют имитировать реальный хостинг и проводить тестирование ваших сайтов (включая HTML-страницы и серверные скрипты) перед их размещением в сети. Кстати, обратите внимание: локальный хостинг работает без подключения к Интернету! Сомнительные практики Первая ошибка, которую допускают многие начинающие программисты, состоит в попытках создать локальный хостинг, следуя описаниям из Интернета, где предлагаются «навороченные» методы. Их авторы выделяют на диске C отдельные секторы, создают кучу дополнительных директорий, вносят большое количество изменений в конфигурационные файлы. В результате неопытный человек начинает путаться в этих сложных описаниях, ошибаться в настройках программного обеспечения и лишь путем многочисленных проб и консультаций с энной попытки наконец получает требуемый результат (а иногда так и не получает, из-за чего начинает искать другие описания на данную тему). Между тем создание хостинга на своем ПК — дело несложное. Если вы хотите тестировать только HTML-страницы с внедренными в них сценариями на JavaScript, то можно ограничиться установкой на свой компьютер http-сервера. Если планируете создавать страницы не просто загружаемые с сервера, а взаимодействующие с другими серверными скриптами (например, с программами обработки данных из форм), надо, кроме того, установить интерпретатор какого-либо языка, например PHP, Python, Perl или Ruby (любой из них на ваш выбор). Рекомендую начать с сервера Apache и одного из интерпретаторов. Описание технологии их установки — в следующих разделах. 11 Обратите внимание: все процедуры описаны для компьютера с операционной системой Windows 10. Итак, приступим к созданию локального хостинга. А начнем мы с установки сервера Apache. 2.1. Установка сервера Apache Первым делом выясните разрядность вашей операционной системы. Для этого щелкните на кнопке «Пуск», а затем на кнопке «Параметры» (рис. 2.1.1). В открывшемся окне выберите пункт меню «Система». Откроется следующая вкладка, в которой необходимо кликнуть на строку «О системе». После этого на вкладке «Характеристики устройства» посмотрите строку «Тип системы» (рис. 2.1.2). Скачайте и установите на свой компьютер Visual C ++ Redistributable. Адрес компилятора https://aka.ms/vs/16/re/release/VC_redist.x64.exe (для 64-разрядной системы) или https://aka.ms/vs/16/release/VC_redist.x86.exe (для 32-разрядной системы). Теперь скачайте на рабочий стол компьютера с сайта https:// www.apachelounge.com/download/ zip-архив, соответствующий разрядности вашей ОС (рис. 2.1.3). Для 64-разрядной системы — архив с пометкой Win64, для 32-разрядной — с пометкой Win32. Распакуйте архив. Скопируйте папку Apache24 (только ее) непосредственно на диск C, чтобы ее адрес был C:\Apache24 (рис. 2.1.4). Откройте приложение «Командная строка» от имени администратора. Для этого нажмите кнопку «Пуск», в меню выберите «Служебные — Windows», найдите пункт «Командная строка» и щелкните на нем правой (правой!) кнопкой мыши. В выпадающем списке выберите «Дополнительно», а затем «Запуск от имени администратора» (рис. 2.1.5). Откроется окно программы. Рис. 2.1.1. Кнопка «Параметры» в меню 12 Рис. 2.1.2. Разрядность или тип операционной системы Рис. 2.1.3. Сайт с zip-архивом сервера 13 Рис. 2.1.4. Копируем папку Apache24 на диск C Рис. 2.1.5. Запуск командной строки 14 Теперь надо инсталлировать, а затем запустить сервер. Для этого в окне командной строки сразу после C:\WINDOWS\System32> наберите C:\Apache24\bin\httpd.exe -k install Должно получиться C:\WINDOWS\System32>C:\Apache24\bin\httpd.exe -k install Нажмите «Enter». Когда процесс инсталляции закончится (рис. 2.1.6), в окне программы вновь появится строка C:\WINDOWS\System32> Рис. 2.1.6. Инсталляция сервера из приложения «Командная строка» Закройте окно командной строки и перезагрузите компьютер. ВНИМАНИЕ! Может открыться окно брандмауэра с запросом на разрешение работы «Apache». Разрешите доступ во всех сетях. Нам надо убедиться, что сервер заработал. Для этого откройте ваш браузер и введите в строке адреса http://localhost/. Если появилось сообщение «It works!», значит, все в порядке — сервер функционирует как положено (рис. 2.1.7). Рис. 2.1.7. Сервер подтверждает, что он работает 15 Осталось добавить, что файлы ваших проектов необходимо помещать в папку htdocs по адресу C:\Apache24\htdocs, а просматривать готовые страницы сайтов в браузере по адресу http://localhost/ или http://localhost/ имя_страницы.html, например http://localhost/primer.html или http:// localhost/test/primer.html. 2.2. Установка PHP 7 Скачайте на рабочий стол компьютера с сайта https://windows.php.net/ download/ zip-архив PHP 7, соответствующий разрядности вашей ОС. Для 64-разрядной системы — архив с пометкой x64, для 32-разрядной — с пометкой x86. Обратите внимание: так как мы будем устанавливать PHP в качестве модуля сервера Apache, скачивать надо дистрибутив Thread Safe (рис. 2.2.1). Распакуйте архив. Переименуйте распакованную папку на php и переместите ее непосредственно на диск C, чтобы ее адрес был C:\php (рис. 2.2.2). В папке C:\Apache24\conf с помощью текстового редактора «Блокнот» откройте файл httpd.conf (рис. 2.2.3). Рис. 2.2.1. Сайт с zip-архивом PHP 16 Рис. 2.2.2. Перемещаем папку php на диск C Рис. 2.2.3. Файл httpd.conf Найдите в файле httpd.conf строку DirectoryIndex index.html и добавьте к ней index.php Должно получиться DirectoryIndex index.html index.php Теперь в самый конец файла httpd.conf добавьте 2 строки: AddHandler application/x-httpd-php .php LoadModule php7_module "C:/php/php7apache2_4.dll" 17 Powered by TCPDF (www.tcpdf.org) Сохраните изменения, закройте файл и перезагрузите компьютер. Убедимся, что PHP заработал. Для этого в папке C:\Apache24\htdocs создайте текстовый файл и запишите в него следующий код (файл ph1.php в папке «Глава2» zip-архива): <?php echo "Good !"; Сохраните этот файл под именем, например ph1.php. Откройте ваш браузер и введите в строке адреса http://localhost/ph1.php. Если появилось сообщение «Good !», значит, все в порядке — PHP работает (рис. 2.2.4). Рис. 2.2.4. Проверяем работу PHP PHP-файлы ваших проектов необходимо помещать в папку htdocs по адресу C:\Apache24\htdocs, а просматривать готовые страницы сайтов в браузере по адресу http://localhost/ (для файла index.php) или http://localhost/ имя_страницы.php, например http://localhost/primer.php или http://localhost/ test/primer.php. 2.3. Установка Python 3 Скачайте на рабочий стол компьютера с сайта https://www.python.org/ downloads/windows/ установочный файл, соответствующий разрядности вашей ОС (рис. 2.3.1). Запустите его. Выберите вариант установки с настраиваемыми параметрами. Для этого нажмите ссылку «Customize installation» (рис. 2.3.2). В открывшемся окне ничего не меняйте, просто нажмите кнопку «Next» (рис. 2.3.3). Основные настройки выполняются в следующем окне. Сперва поставьте «галочку» напротив пункта «Install for all users», чтобы интерпретатор был доступен всем пользователям. В строке «Customize install location» вручную введите адрес папки для интерпретатора: C:\Python. После этого нажмите кнопку «Install» (рис. 2.3.4). Дождитесь окончания процесса (рис. 2.3.5). Нажмите кнопку «Close» (рис. 2.3.6). 18 Рис. 2.3.1. Сайт с файлами интерпретатора Python Рис. 2.3.2. Начало установки интерпретатора Python 19 Рис. 2.3.3. Здесь просто жмем «Next» Рис. 2.3.4. Ставим «галочку» напротив пункта «Install for all users», меняем имя папки, в которую будет установлен интерпретатор, и начинаем инсталляцию 20 Рис. 2.3.5. Процесс установки файлов интерпретатора Python Рис. 2.3.6. Завершение установки 21 Рис. 2.3.7. Файл httpd.conf В папке C:\Apache24\conf с помощью текстового редактора «Блокнот» откройте файл httpd.conf (рис. 2.3.7), найдите в нем строку DirectoryIndex index.html и добавьте к ней index.py Должно получиться DirectoryIndex index.html index.py Теперь найдите строку Options Indexes FollowSymLinks и добавьте в конце нее ExecCGI Должно получиться Options Indexes FollowSymLinks ExecCGI Дальше найдите строку #AddHandler cgi-script .cgi удалите в начале нее знак # и добавьте в конце строки .py 22 Должно получиться AddHandler cgi-script .cgi .py Сохраните изменения, закройте файл и перезагрузите компьютер. Теперь убедимся, что Python заработал. Для этого в папке C:\Apache24\ htdocs создайте текстовый файл и запишите в него следующий код (файл py1.py в папке «Глава2» zip-архива): #! C:/Python/python print ("Content-type: text/html\n\n") print ("YES !") Сохраните этот файл под именем py1.py. Откройте ваш браузер и введите в строке адреса http://localhost/py1.py. Если появилось сообщение «YES !», значит, все в порядке (рис. 2.3.8). Рис. 2.3.8. Проверяем работу Python Осталось добавить, что файлы ваших проектов необходимо помещать в папку htdocs по адресу C:\Apache24\htdocs, а просматривать готовые страницы сайтов в браузере по адресу http://localhost/ (для файла index.py) или http://localhost/имя_страницы.py, например http://localhost/primer.py или http://localhost/test/primer.py. Путь к интерпретатору (первая строка) во всех python-файлах на локальном хостинге должен записываться так: #! C:/Python/python 2.4. Установка Perl 5 Скачайте на рабочий стол компьютера с сайта http://strawberryperl.com/ установочный файл, соответствующий разрядности вашей ОС (рис. 2.4.1). Запустите его. Нажмите кнопку «Next» (рис. 2.4.2), после чего согласитесь с условиями лицензии, снова нажмите «Next» (рис. 2.4.3). Вам будет предложено установить интерпретатор в папку C:\Strawberry\. Вручную перепишите ее название на C:\Perl\ (рис. 2.4.4). Опять нажмите «Next», а затем «Install» (рис. 2.4.5). Дождитесь окончания процесса (рис. 2.4.6). 23 Рис. 2.4.1. Сайт с файлами интерпретатора Perl Рис. 2.4.2. Начало установки интерпретатора Perl 24 Рис. 2.4.3. Соглашаемся с условиями лицензии Рис. 2.4.4. Меняем имя папки, в которую будет установлен интерпретатор 25 Рис. 2.4.5. Начинаем инсталляцию Рис. 2.4.6. Процесс установки файлов интерпретатора Perl 26 Снимите галочку в строке «Read README file.» и нажмите «Finish» (рис. 2.4.7). Установка завершена. Рис. 2.4.7. Завершение установки Рис. 2.4.8. Файл httpd.conf 27 В папке C:\Apache24\conf с помощью текстового редактора «Блокнот» откройте файл httpd.conf (рис. 2.4.8), найдите в нем строку DirectoryIndex index.html и добавьте к ней index.pl Должно получиться DirectoryIndex index.html index.pl Теперь найдите строку Options Indexes FollowSymLinks и добавьте в конце нее ExecCGI Должно получиться Options Indexes FollowSymLinks ExecCGI И последнее дополнение. Найдите строку #AddHandler cgi-script .cgi удалите в начале нее знак # и добавьте в конце строки .pl Должно получиться AddHandler cgi-script .cgi .pl Сохраните изменения, закройте файл и перезагрузите компьютер. Нам надо убедиться, что Perl заработал. Для этого в папке C:\Apache24\ htdocs создайте текстовый файл и запишите в него следующий код (файл pe1.pl в папке «Глава2» zip-архива): #! C:/Perl/perl/bin/perl print "Content-type: text/html\n\n"; print "FINE !"; Сохраните этот файл под именем pe1.pl. Откройте ваш браузер и введите в строке адреса http://localhost/pe1.pl. Если появилось сообщение «FINE !», значит, все в порядке (рис. 2.4.9). 28 Рис. 2.4.9. Проверяем работу Perl Осталось добавить, что файлы ваших проектов необходимо помещать в папку htdocs по адресу C:\Apache24\htdocs, а просматривать готовые страницы сайтов — в браузере по адресу http://localhost/ (для файла index.pl) или http://localhost/имя_страницы.pl, например http://localhost/primer.pl или http://localhost/test/primer.pl. Путь к интерпретатору (первая строка) во всех perl-файлах на локальном хостинге должен записываться так: #! C:/Perl/perl/bin/perl 2.5. Установка Ruby 3 Скачайте на рабочий стол компьютера с сайта https://rubyinstaller.org/ downloads/ из раздела «WITHOUT DEVKIT» установочный файл, соответствующий разрядности вашей ОС. Для 64-разрядной системы — архив с пометкой x64, для 32-разрядной — с пометкой x86 (рис. 2.5.1). Запустите установочный файл. Рис. 2.5.1. Сайт с файлами интерпретатора Ruby 29 Рис. 2.5.2. Соглашаемся с условиями лицензии Рис. 2.5.3. Меняем имя папки, в которую будет установлен интерпретатор 30 Рис. 2.5.4. В этом окне оставляем все без изменения Рис. 2.5.5. Процесс установки интерпретатора Ruby 31 Рис. 2.5.6. Завершение установки Рис. 2.5.7. Файл httpd.conf Согласитесь с условиями лицензии и нажмите «Next» (рис. 2.5.2). В открывшейся вкладке вручную перепишите адрес папки с интерпретатором на C:\Ruby (рис. 2.5.3). Нажмите «Install». В следующем окне оставьте все без изменений и нажмите кнопку «Next» (рис. 2.5.4). Дождитесь окончания процесса установки (рис. 2.5.5). 32 На последней вкладке снимите «галочку» напротив строки «Run 'ridc insnall' to set up MSYS2 and development toolchain» (рис. 2.5.6). Завершите остановку нажатием кнопки «Finish». В папке C:\Apache24\conf с помощью текстового редактора «Блокнот» откройте файл httpd.conf (рис. 2.5.7), найдите в нем строку DirectoryIndex index.html и добавьте к ней index.rb Должно получиться DirectoryIndex index.html index.rb Теперь найдите строку Options Indexes FollowSymLinks и добавьте в конце нее ExecCGI Должно получиться Options Indexes FollowSymLinks ExecCGI И последнее дополнение. Найдите строку #AddHandler cgi-script .cgi удалите в начале нее знак # и добавьте в конце строки .rb Должно получиться AddHandler cgi-script .cgi .rb Сохраните изменения, закройте файл и перезагрузите компьютер. Нам надо убедиться, что Ruby заработал. Для этого в папке C:\Apache24\htdocs создайте текстовый файл и запишите в него следующий код (файл ru1.rb в папке «Глава2» zip-архива): #! C:/Ruby/bin/ruby puts "Content-type: text/html\n\n" puts "OK !" 33 Сохраните этот файл под именем ru1.rb. Откройте ваш браузер и введите в строке адреса http://localhost/ru1.rb. Если появилось сообщение «OK !», значит, все в порядке (рис. 2.5.8). Рис. 2.5.8. Проверяем работу Ruby Осталось добавить, что файлы ваших проектов необходимо помещать в папку htdocs по адресу C:\Apache24\htdocs, а просматривать готовые страницы сайтов — в браузере по адресу http://localhost/ (для файла index.rb) или http://localhost/имя_страницы.rb, например http://localhost/primer.rb или http://localhost/test/primer.rb. Путь к интерпретатору (первая строка) во всех ruby-файлах на локальном хостинге должен записываться так: #! C:/Ruby/bin/ruby 2.6. Устанавливать ли базу данных? В основе общепринятого подхода к созданию большинства сайтов лежит принцип хранения всех данных в базе, например популярной MySQL или других. Причем этот подход никак не зависит от типа хранимой информации. А зря. Один из критериев, по которым можно разделить сайты, — это наличие или отсутствие секретной информации: личных данных, логинов, паролей. Например, интернет-магазин скрывает множество конфиденциальной информации. А сайт поддержки моей книги «JavaScript. Готовые программы» вообще не имеет ни грамма секретов. Зададимся вопросом: нужна ли сайту поддержки база данных? Ведь вся его информация открыта и доступна любому посетителю, а значит, нет никакого смысла что-то скрывать. Так не проще ли на таком ресурсе хранить все данные в простых текстовых файлах? Ответ очевиден: конечно, проще! К чему я веду? По моему мнению, начинающим программистам (подчеркиваю — именно начинающим) в сайтах с открытой информацией для ее хранения лучше использовать текстовые файлы. Такая система хранения проще, не требует написания sql-запросов, не нуждается в подключении к БД, а значит, быстрее разрабатывается и внедряется. Подчеркну: для начинающего — это очень важный момент. В первых проектах неопытных программистов всегда ждет много сложностей: необходимо написать качественные страницы, добавить к ним таблицы стилей, «прикрутить» сценарии на JavaScript, создать пару серверных программ, которые будут взаимодействовать с отдельными страницами. Поэтому на первых порах не стоит усложнять себе жизнь — на начальном этапе освоения профессии web-разработчика делайте простые сайты с хра34 Powered by TCPDF (www.tcpdf.org) нением информации в текстовых файлах (если только речь не идет о наличии конфиденциальных данных). Освоитесь, наберетесь опыта — можно браться и за настоящие базы данных, и за сложные проекты. Если же ваш сайт, например, принимает заявки от посетителей, то эти заявки можно сразу отправлять электронной почтой администратору, минуя сохранение данных на хостинге. Рис. 2.6.1. Сайт СУБД MySQL Вы все-таки решили установить на своем компьютере базу данных? Могу посоветовать вам три наиболее популярных из них. 1. СУБД (система управления базами данных (БД)) — MySQL. Старейшая база данных, применяющаяся в web-проектах с 1995 г. Фактически в свое время задала стандарты для баз данных, используемых при создании сайтов. Разрабатывается и поддерживается корпорацией Oracle. Обладает множеством достоинств, функционирует на серверах практически всех хостинг-провайдеров. Но есть и один существенный недостаток — в последние годы данная БД совершенствуется и обновляется довольно медленно. Сайт, где можно скачать данную БД, — https://www.mysql.com/ (рис. 2.6.1). 2. Система управления базами данных PostgreSQL. На сегодняшний день один из лидеров по популярности среди разработчиков интернет-проектов. Создавалась как открытый проект в Калифорнийском университете в Беркли. Ра35 ботает во всех основных операционных системах, в том числе Windows. Для этой ОС существуют автоматические установщики. Необходимо отметить, что последние версии СУБД доступны только для 64-разрядной ОС Windows, а для 32-разрядной можно скачать версии не выше 10.16. Сайт БД — https:// www.postgresql.org/ (рис. 2.6.2). Рис. 2.6.2. Сайт СУБД PostgreSQL Рис. 2.6.3. Сайт СУБД SQLite 36 3. СУБД SQLite. Это библиотека на языке C, которая реализует полнофункциональный механизм базы данных SQL. Файлы, созданные SQLite, являются кроссплатформенными и обратно совместимыми. При этом разработчики обещают сохранить формат файлов в таком виде до 2050 г. Также разработчики утверждают, что их СУБД — самая часто используемая в мире. Впрочем, это заявление расходится со многими исследованиями по определению популярности баз данных. Сайт БД — https://www.sqlite.org/ (рис. 2.6.3). 2.7. Редакторы кода Полноценную среду разработки невозможно представить без установки на ваш компьютер настоящего текстового редактора, специально предназначенного для создания программ. Конечно, весь код можно писать в обычном «Блокноте». Но гораздо лучше и рациональнее пользоваться специализированным редактором. Такие приложения намного удобнее: они подсвечивают код, предлагают синтаксические подсказки, позволяют менять кодировку документов, сохраняют файлы в разных форматах. В нашем случае необходим редактор, который позволит: выполнять качественную разметку; создавать файлы с таблицами стилей; писать сценарии на JavaScript; делать серверные программы обработки данных на одном из языков, про интерпретаторы которых мы говорили в разделах 2.2–2.5. Этому принципу удовлетворяют многие редакторы. Рассмотрим несколько из них. Обращаю ваше внимание: процесс установки одного редактора и его возможности я опишу подробно, а об остальных расскажу более сжато. Notepad++ Скачать его можно по адресу https://notepad-plus-plus.org/downloads/ (рис. 2.7.1). Устанавливается редактор следующим образом. Запустите скачанный файл. В первую очередь появится окно выбора языка программы. Оставляем русский (рис. 2.7.2) и нажимаем кнопку «ОК». В следующем окне нажимаем кнопку «Далее» (рис. 2.7.3). Затем нажатием кнопки «Принимаю» соглашаемся с условиями лицензии (рис. 2.7.4). Теперь нам необходимо выбрать папку для установки программы. По умолчанию предлагается C:\Program Files\Notepad++. Думаю, такой вариант установки подходит всем, поэтому данную папку оставляем без изменений (рис. 2.7.5). Дальше будет окно с выбором компонентов, которые можно установить вместе с программой. Этот раздел предназначен, в первую очередь, для опытных программистов, каковыми мы пока не являемся. Поэтому здесь ничего не меняем, жмем кнопку «Далее» (рис. 2.7.6). 37 Рис. 2.7.1. Сайт с файлами редактора Notepad++ Рис. 2.7.2. Выбираем язык ПО 38 Рис. 2.7.3. Нажимаем кнопку «Далее» Рис. 2.7.4. Соглашаемся с условиями лицензии 39 Рис. 2.7.5. Выбираем папку для установки программы Рис. 2.7.6. Здесь ничего не меняем, жмем кнопку «Далее» 40 На последнем этапе, непосредственно перед началом установки, на вкладке появится пункт «Create Shortcut on Desktop» (рис. 2.7.7). То есть нам предлагают создать ярлык программы на рабочем столе. Делать это или нет — зависит от вашего желания. Замечу, что после установки редактора ссылка на него добавится в контекстное меню. Достаточно будет навести указатель мыши на файл, щелкнуть правой клавишей и в списке возможных действий вы увидите строку «Edit with Notepad++». Кликните по ней — и файл будет открыт в редакторе. Разобравшись с вопросом, необходим ли ярлык на рабочем столе или нет, нажмите кнопку «Установить». Дождитесь окончания процесса (рис. 2.7.8). После завершения установки вам нужно будет принять еще одно решение: сразу запустить программу или отложить это дело на потом. Для этого оставьте или снимите «галочку» в пункте «Запустить Notepad++» (рис. 2.7.9). Теперь нажмите кнопку «Готово». Поздравляю — отныне на вашем компьютере установлен профессиональный текстовый редактор! Познакомимся с ним поближе. Как выглядит редактор, вы можете видеть на рисунке 2.7.10. Notepad++ позволяет: выполнять поиск по файлу в разных направлениях; производить замену по шаблону; Рис. 2.7.7. Нажимаем кнопку «Установить» 41 Рис. 2.7.8. Дождитесь окончания процесса Рис. 2.7.9. Завершение установки 42 Рис. 2.7.10. Редактор Notepad++ с открытым в нем файлом устанавливать кодировки, в том числе необходимую для наших сайтов UTF-8; менять страницы, форматируя их по стандартам разных операционных систем — Windows, Unix или macOS; подсвечивать код, выделяя разные по назначению фрагменты, операторы, функции, переменные, комментарии и т. д.; выбирать синтаксис документа (благодаря чему меняются варианты подсветки кода), например HTML, CSS, JavaScript, а также PHP, Python, Perl и Ruby; сохранять файлы с разным расширением; менять стили оформления окна программы — вариантов достаточно, найдется любой по желанию; выполнять еще многие и многие операции. Как известно, на вкус и цвет товарищей нет, но лично мне Notepad++ кажется наилучшим вариантом для разработчика. Остальные редакторы, на мой взгляд, менее удобны и функциональны. Visual Studio Code Страница редактора — https://code.visualstudio.com/Download. Разработан в 2015 г. компанией Microsoft. Создатели позиционируют его, в первую очередь, как «легкий» редактор для web-проектов. 43 Скачивать лучше вариант «System Installer» (рис. 2.7.11). Эта версия программы позволяет установить ее для всех пользователей вашего компьютера. Выбирайте файл для скачивания в соответствии с разрядностью вашей операционной системы. О том, как ее определить, мы говорили в разделе 2.1 данной главы. Рис. 2.7.11. Страница загрузки редактора Visual Studio Code Рис. 2.7.12. Редактор Visual Studio Code с набранным в нем сценарием на JavaScript 44 Редактор (рис. 2.7.12) поставляется с английским языком интерфейса по умолчанию. Однако в программу встроена функция русификации. Правда, Visual Studio Code снабжена довольно причудливым механизмом поиска в ее собственных недрах инструкции по установке русского языка. Но вы можете найти многочисленные описания процесса, введя в поисковой системе, например Яндекс, следующий запрос: «visual studio code русский язык». К достоинствам программы можно отнести наличие автоматической проверки кода на его соответствие синтаксису. Кроме того, Visual Studio Code позволяет менять стили оформления окна программы. По умолчанию включена тема с черным интерфейсом. В редактор встроена поддержка всех необходимых нам синтаксисов: HTML, CSS, JavaScript, а также PHP, Python, Perl и Ruby. Для расширения функциональности Visual Studio Code существует множество плагинов. Atom Страница редактора — https://atom.io/. Вам сразу будет предложен файл, соответствующий вашей операционной системе и ее разрядности (рис. 2.7.13). Рис. 2.7.13. Сайт редактора Atom Программное обеспечение устанавливается очень просто. От пользователя требуется лишь запустить скачанный файл. Все остальные действия, в том числе и первый запуск, будут выполнены в автоматическом режиме. Как и предыдущий редактор, Atom (рис. 2.7.14) поставляется с английским языком интерфейса. Описывать русификацию этого ПО мы тоже не станем. Вы можете найти многочисленные инструкции по ее реализации, введя в поисковой системе следующий запрос: «Atom русский язык». 45 Рис. 2.7.14. Редактора Atom с введенным кодом Как и в Visual Studio Code, в редактор Atom встроена автоматическая проверка кода. По умолчанию включена тема с черным интерфейсом. Первая версия редактора вышла зимой 2014 г. Sublime Text Страница редактора — https://www.sublimetext.com/3. Скачивая программу (рис. 2.7.15), выбирайте файл в соответствии с разрядностью вашей операционной системы. О том, как ее определить, рассказано в разделе 2.1 данной главы. Рис. 2.7.15. Страница для выбора файла загрузки редактора Sublime Text 46 Процесс инсталляции довольно несложный. Можно оставить все настройки, предложенные установщиком, без изменений. Как выглядит окно редактора, показано на рисунке 2.7.16. Рис. 2.7.16. Окно редактора Sublime Text И уже по традиции, как и в двух предыдущих случаях, сообщаю вам, что: по умолчанию включена тема с темным интерфейсом; редактор поставляется с английским языком интерфейса, но в Интернете есть много описаний процесса русификации, надо только набрать в поисковой системе запрос «Sublime Text русский язык», чтобы найти необходимую информацию. Надеюсь, что приведенные описания помогут вам при выборе редактора кода. 47 3. Подготовительные работы Итак, вы приступаете к написанию первых сценариев для вашего будущего сайта. Изучите несколько простых советов о том, на что необходимо обратить внимание во время творческого процесса. Собственно, данная глава посвящена процессу создания документа, добавлению стилей, внедрению сценариев. Я расскажу вам о некоторых особенностях, нюансах и тонкостях, о которых вы прочтете далеко не в каждой книге по программированию на JavaScript, но которые весьма полезно знать начинающему разработчику. 3.1. Алгоритм действий Жизнь web-программиста очень облегчает наличие в его голове или на бумаге четко продуманного алгоритма создания проекта. Особенно если этот проект выполняется в одиночку. Рискну предположить, что свои первые сайты начинающие кодеры полностью делают самостоятельно. В отличие от web-студии, где каждый специалист отвечает за свой участок, у разработчика-индивидуала должны быть под контролем все участки сразу. А это создает определенные трудности — не имея опыта, сложно правильно распределить усилия и рабочее время. Попробую рассказать вам о рациональном плане выполнения того или иного проекта. На мой взгляд, наиболее оптимальным выглядит следующий порядок действий: 1) определите, какой сайт вы хотите создать; 2) составьте перечень будущих страниц и список материалов (контента), которые необходимо разместить на сайте; 3) решите, как будут выглядеть страницы и разработайте самостоятельно или с чьей-то помощью дизайн ресурса; 4) сверстайте все страницы, наполните их содержимым, пропишите в отдельных файлах стили; 5) внедрите в необходимые страницы код JavaScript или файлы со сценариями; 6) проверьте, как смотрится и работает ваш сайт в разных браузерах. Примерно так. Давайте рассмотрим этапы 4–6 более подробно. 48 3.2. Шаблон страницы Консорциум Всемирной Паутины — World Wide Web Consortium, или W3C, — устанавливает определенные правила написания и оформления кода HTML5. Поэтому, создавая шаблон для будущих страниц, необходимо придерживаться некоторых стандартов. Во-первых, первой строкой документа должен быть указан его тип: <!DOCTYPE html> Во-вторых, необходимо объявить начало разметки: <html> Лучше всего в этом же теге обозначить язык документа, например так: <html lang="ru"> Теперь можно разместить контейнер для заголовочного блока: <head> ... </head> Внутрь контейнера вы можете добавить различные метатеги — их можно назвать сопроводительными данными о странице. Одним из первых вставьте тег, указывающий кодировку документа. Например, вот так: <meta charset="utf-8"> Для поисковых систем очень важно, чтобы в заголовочной части были обязательно указаны теги description и keywords. Тег description содержит описание страницы, а тег keywords — список ключевых слов для текущего документа. «Операцию» добавления тегов можно выполнить следующим образом: <head> ... <meta name="description" content="описание страницы"> <meta name="keywords" content="ключевые слова"> </head> Естественно, список возможных метатегов не ограничивается этими тремя. Вы можете добавлять другие теги по мере необходимости. Например, viewport, который нужен для адаптации документа к браузерам мобильных устройств. Вариант его записи: <meta name="viewport" content="width=device-width, initial-scale=1"> 49 Впрочем, разных полезных метатегов немало — copyright, robots и другие. О них лучше посмотреть информацию на сайте Консорциума Всемирной Паутины: https://www.w3.org/. Наконец, в заголовочной части документа непременно разместите тег title: <head> ... <title>Заголовок страницы</title> </head> Он необходим для текста, который является заголовком или названием страницы, и выводится на ее вкладке в окне браузера. Под начальным формированием заголовочного контейнера пока можно подвести черту. У нас получился такой минимально возможный блок: <head> <meta charset="utf-8"> <meta name="description" content="описание страницы"> <meta name="keywords" content="ключевые слова"> <title>Заголовок страницы</title> </head> Теперь добавим тело документа. Поскольку страница у нас еще пустая, это будет выглядеть совсем просто: <body> </body> Когда все начальные компоненты готовы, можно собрать шаблон в единое целое (файл Шаблон.html в папке «Глава3» zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <meta name="description" content="описание страницы"> <meta name="keywords" content="ключевые слова"> <title>Заголовок страницы</title> </head> <body> </body> </html> Обратите внимание: завершает описание документа закрывающий тег </html>. Шаблон готов. Можно добавлять на страницу необходимые элементы: текст, рисунки, таблицы, ссылки и т. д. 50 3.3. Добавляем элементы Этот процесс описать сложнее всего, ведь вариантов размещения элементов бесконечное множество. И все-таки сделаем акцент на одном моменте: для начала необходимо решить вопрос с выбором метода позиционирования элементов. Тут тоже большое разнообразие способов. Рассмотрим всего несколько самых простых вариантов из огромного количества возможных приемов. Например, можно поместить в тело документа контейнер <div> </div> и уже внутри него позиционировать отдельные элементы страницы, а также сам контейнер относительно границ окна браузера: <div> элемент 1 элемент 2 элемент 3 и так далее </div> Расположение элементов и контейнера устанавливается в настройках таблицы стилей. Можно создать несколько контейнеров и группировать внутри каждого определенный набор элементов. <div id=”d1”> элемент 1 элемент 2 </div> <div id=”d2”> элемент 3 элемент 4 элемент 5 </div> <div id=”d3”> элемент 6 элемент 7 </div> и так далее Здесь получается комбинированное позиционирование: сначала элементы размещаются необходимым образом внутри контейнеров, а затем уже «расставляются» сами контейнеры (порядок действий может быть обратным). Расположение элементов внутри контейнеров и самих контейнеров также устанавливается настройками таблицы стилей. Примеры того, как можно позиционировать различные блоки, показаны на рисунке 3.3.1. 51 Powered by TCPDF (www.tcpdf.org) Рис. 3.3.1. Простое и сложное позиционирование блоков Иногда для группировки элементов используют в качестве базы не контейнеры, а ячейки таблицы. Примерно так: <table> <tr> <td>элемент 1</td> <td>элемент 2 элемент 3</td> </tr> <tr> <td>элемент 4 элемент 5 элемент 6</td> <td>элемент 7</td> </tr> и так далее </table> 52 Внутри ячеек элементы могут позиционироваться с применением настроек стилей, а могут просто располагаться один за другим. Кроме того, применяя атрибуты colspan и rowspan, вы способны получить более разнообразные варианты расположения ячеек, чем это показано в структуре таблицы выше. Примеры того, как по-разному можно располагать ячейки в таблице, изображены на рисунке 3.3.2. Рис. 3.3.2. Простые и сложные варианты размещения элементов в ячейках таблицы Добавлю, что эти три способа можно использовать как при верстке достаточно простых страниц, так и при компоновке сложных документов. К сожалению, способ размещения элементов в сегментах таблицы имеет существенный недостаток. Это «жесткий» каркас таблицы, в результате чего отсутствует возможность изменить расположение ячеек при уменьшении размеров окна браузера. На рисунке 3.3.3 показано, как меняется структура документа, созданного на основе блоков <div> </div> при просмотре страницы в браузере компьютера и в браузере мобильного устройства. Если на большом экране блоки располагаются горизонтально, то на маленьком они перестроятся 53 в вертикальную последовательность. Но такой результат невозможно получить, если применять табличную компоновку документа. Наконец, есть еще один способ последовательного расположения элементов, рассчитанный на самый простой случай: <body> элемент 1 элемент 2 элемент 3 и так далее </body> Здесь элементы не позиционируются, а просто следуют один за другим. Данный способ больше подходит для первых экспериментов начинающего программиста, а для серьезного сайта в подавляющем большинстве случаев не годится. Рис. 3.3.3. Расположение блоков при просмотре страницы в браузере компьютера и браузере мобильного устройства 3.4. Таблицы стилей Итак, мы добавили в тело документа необходимое содержимое. Настало время: правильно разместить элементы; оформить их внешний вид и придать необходимые размеры. Сделать это нам помогут таблицы стилей. Рассказывать о них я не стану — тема большая и многообразная, лучше прочитайте об этом специальную книгу, например «Клиентские технологии веб-дизайна. HTML5 и CSS» А. В. Дикова (издательство «Лань», Санкт-Петербург, 2019) или любую другую, посвященную данной теме. Я расскажу вам только о способах внедрения таблиц стилей в документ. 54 Сомнительные практики Мы не рассматриваем ситуации, когда стили указываются непосредственно в элементе разметки, например, так: <button style="...">. Считается хорошим тоном отделять стили от разметки, а показанный вариант не соответствует этому принципу. Способ первый — размещение таблицы в заголовочной части документа. Для этого внутри блока head вставьте теги style: <head> ... <style> </style> </head> И уже в контейнере <style> </style> запишите все необходимые настройки. Например, таким образом: <style> body {background: #FFFFFF; color: #000000;} a {text-decoration: none; color: #0000CC;} ... </style> Нужно признать, что данный способ имеет один серьезный недостаток: если настроек много, файл страницы получится очень большим и работать с ним программисту станет неудобно. Поэтому предпочтительней другой вариант — размещение таблиц стилей во внешних файлах. Подключить такие файлы к текущему документу очень просто. Для этого также существует 2 способа. Первый — с использованием директивы @import. В этом случае подключение необходимо выполнить внутри блока head таким образом: <style> @import url(css.css); </style> где css.css — адрес файла с таблицей стилей. Обратите внимание: любой внешний файл с таблицей стилей должен иметь расширение .css. Естественно, что внутри такого файла теги <style> </style> не указываются — описание стилей начинается прямо с первой строки. Второй способ — с применением элемента разметки link. В этом случае файл таблицы стилей привязывается к документу внутри блока head так: <link rel="stylesheet" href="css.css"> 55 Какой вариант избрать — с директивой @import или элементом link — решать вам. Замечу, что второй вариант применяется разработчиками гораздо чаще, чем первый. 3.5. Размещаем сценарий Ситуация с размещением сценариев на JavaScript очень похожа на то, что мы делали с таблицами стилей. Есть два подхода к внедрению кода скриптов в страницу. Первый способ — размещение кода в заголовочной части документа. С этой целью внутри блока head вставьте теги script: <head> ... <script> </script> </head> Теперь в контейнере <script> </script> запишите все необходимые переменные и функции. Вот пример такой записи: <script> window. addEventListener("load", function() { let w=screen.width; let h=screen.height; document.getElementById("si").innerHTML=w+" x "+h; }); </script> Так же как и в случае с таблицами стилей, данный способ имеет аналогичный недостаток: если код довольно объемный, файл страницы получится очень большим и работать с ним станет неудобно. Поэтому предпочтительней другой вариант — размещение сценариев на JavaScript во внешних файлах. Подключают такие программы к текущему документу следующим образом. Необходимо внутри блока head разместить теги <script> </script>, при этом в открывающем теге указать адрес необходимого файла: <script src="js.js"></script> где js.js — адрес файла со сценарием. Обратите внимание: любой внешний файл с программой на JavaScript должен иметь расширение .js. Естественно, что внутри такого файла теги <script> </script> не указываются — написание кода начинается прямо с первой строки. Итак, у нас все готово к тому, чтобы написать программу взаимодействия элементов документа с событиями, происходящими на странице. 56 4. Некоторые особенности программирования Эта глава не является учебником по программированию на JavaScript. Просто, по моим наблюдениям, во многих книгах есть одно важное упущение. В них излагаются основы языка, дается описание операторов, условий и циклов, рассказывается о разновидностях функций, перечисляются встроенные методы, но очень мало внимания уделяется применению полученных знаний на практике. А здесь есть множество нюансов, особенностей и тонкостей. Например, как одновременно зарегистрировать обработчик какого-либо события и запустить функцию, действия которой будут подготавливать данные для обработчика? В книгах вам об этом не сообщат, полагая, что, изучив соответствующую тему, вы сами напишете необходимый код. А это для начинающего не всегда просто. Другой пример. В обычной книге автор расскажет вам, что событие scroll возникает при прокручивании страницы в окне браузера. Но вряд ли добавит, что подобное событие происходит и во фрейме. Словом, думаю, вы понимаете, что таких примеров можно привести гораздо больше. Поэтому в данной главе я решил дать несколько полезных советов, а также ряд дополнительных сведений, которые могут пригодиться вам при написании сценариев. 4.1. События События на страницах сайта происходят почти непрерывно. Загружаете ли вы документ, нажимаете кнопку мыши, прокручиваете страницу или просто перемещаете указатель — все это для браузера события. Их принято идентифицировать по типу (иногда говорят не тип события, а имя события). Рассмотрим некоторые события, которые обрабатываются наиболее часто. Событие load происходит при загрузке страницы в браузер. Оно генерируется сразу после того, как html-разметка и рисунки (картинки, изображения, фото) полностью отображаются на экране компьютера. Такое же событие происходит и при получении ответа от сервера, на котором стоит программа, вызываемая с применением технологии Ajax. Событие click возникает при щелчке кнопкой мыши на элементе разметки страницы. Это могут быть ссылки, кнопки, изображения, текстовые блоки, слои 57 (событие click, как, впрочем, и некоторые другие, может также происходить даже на визуально пустом месте документа). Событие mouseover происходит, когда указатель мыши перемещается внутрь границ элемента. Событие mouseout противоположно предыдущему и возникает, когда указатель мыши покидает границы элемента. Событие mousemove генерируется в продолжение всего времени, пока указатель мыши перемещается над элементом. Событие mousedown происходит при нажатии кнопки мыши, когда ее указатель находится над элементом. Событие mouseup возникает при отпускании кнопки мыши (при этом указатель должен находиться над элементом, для которого генерируется событие). Событие dragstart происходит, когда посетитель начинает перетаскивать элемент по странице. Событие dragover возникает в процессе перетаскивания элемента по странице. Событие drop происходит в момент отпускания перетаскиваемого элемента в точке назначения. Событие resize возникает при изменении размеров окна браузера. Событие focus случается при получении элементом фокуса (например, когда курсор будет установлен в текстовом поле). Событие blur происходит, когда элемент теряет фокус. Событие submit сопровождает отправку формы, например нажатием кнопки. Обычно программисты к данному событию привязывают проверку данных, введенных пользователем. Событие change происходит на элементах <select>, <input> и <textarea>, когда меняются их значения. Событие scroll возникает при прокручивании страницы в окне браузера или во фрейме. Событие paste происходит в момент вставки содержимого буфера обмена в элемент документа в позицию курсора (например, в однострочное или многострочное текстовые поля). Событие keydown происходит в момент нажатия любой кнопки на клавиатуре. Событие keyup возникает в момент отпускания после нажатия клавиши. Событие keypress — это «симбиоз» двух предыдущих событий (клавиша нажата и отпущена). Событие select происходит в текстовых полях документа при выделении в них какого-либо фрагмента или всего текста в целом. Событие mouseenter очень похоже на mouseover. Отличие в том, что событие mouseenter не всплывающее и не отменяемое (из этого понятно, что mouseover всплывающее и отменяемое). 58 Событие mouseleave, соответственно, похоже на mouseout. Разница такая же, как и в случае, описанном в предыдущем определении. Событие mouseleave не всплывающее и не отменяемое. Разработчик сайта заранее выбирает, какие события на странице должны вызвать программный отклик, а какие — нет. 4.2. Обработчики событий Обработчик события — это некоторая функция, выполняющая определенные манипуляции с содержимым HTML-страницы в ответ на действия пользователя. Создавая приложение, необходимо связать событие на конкретном элементе разметки с конкретной функцией. Это называется «регистрацией обработчика». Поскольку в литературе о JavaScript, на мой взгляд, тема «Регистрация обработчиков» освещена недостаточно, остановимся на ней подробнее. Сразу оговорю один важный момент: я исповедую стиль программирования, при котором сценарии на JavaScript располагаются: либо в головной части документа; либо во внешнем файле, вызов которого помещается в головной части документа. Тем самым мы отделяем программный код от разметки. Помещать код JavaScript внутрь разметки считается в среде программистов дурным тоном. В этой связи возникают определенные особенности регистрации обработчиков. В частности, метод, показанный ниже, не даст никакого результата: <head> ... ... ... <script> регистрация обработчика, например, для клика на изображении программный код, обрабатывающий щелчок на изображении </script> </head> Почему? Дело в том, что анализатор браузера при загрузке документа считывает его «сверху вниз». Но у нас обработчик объявлен в заголовочной части, то есть еще до того, как построена объектная модель документа. В результате браузер еще не видит изображение, а мы уже пытаемся привязать к картинке какое-то событие. В итоге обработчик не будет зарегистрирован. Чтобы избежать таких неприятностей, я советую поступать следующим образом. Регистрировать сначала обработчик уровня окна браузера для события load, а уже внутри него записывать остальные обработчики. Сделать это можно следующим образом: 59 <head> ... ... ... <script> регистрация обработчика события load уровня окна { регистрация обработчика для клика на изображении } программный код, обрабатывающий щелчок на изображении </script> </head> При таком подходе браузер сначала загрузит весь документ, следом запустит обработчик события load окна, после чего и будут зарегистрированы необходимые обработчики событий на элементах web-страницы. Традиционные способы регистрации обработчиков (считаются устаревшими) Сразу оговорюсь, что данные способы все еще входят в стандарты, работают и долго будут работать во всех браузерах, проходят любую валидацию (об этом — в главе 5, раздел 5.2). Однако среди программистов считаются устаревшими. Почему же я все-таки решил посвятить им часть данной темы? Дело в том, что эти способы все еще применяются в некоторых программах и упоминаются в литературе по JavaScript. Чтобы вызвать обработчик в ответ на какое-либо событие, надо добавить к имени события префикс (приставку) on, например onload, onclick, onfocus, onsubmit и т. д. (так формируется имя свойства обработчика). А затем указать анонимную или именованную функцию, которая будет «откликаться» на то или иное событие. Обычно это делается так: window.onload=func; document.getElementById("im").onclick=func; или так window.onload=function() { ... }; document.getElementById("im").onclick=function() { ... }; 60 Сомнительные практики Мы не рассматриваем ситуации, когда вызов обработчика события помещается непосредственно в элемент разметки, например, так: <button onclick="func()">. Считается хорошим тоном отделять программный код от разметки, а размещение вызова функции внутри тега элемента не соответствует этому принципу. Регистрация обработчиков методом addEventlistener (современный способ) Метод принимает три аргумента. Два из них обязательные. Первый — тип события. Второй — функция, «реагирующая» на событие, или имя внешней функции. Третий аргумент — булево значение, которое допустимо не указывать. Например, третий аргумент может определять, в какой стадии — всплытия или погружения — будет вызван обработчик. Если оставить третий аргумент пустым или явно указать его значение false, то обработка произойдет в фазе всплытия. При значении true обработчик будет запущен в фазе погружения. Кроме того, в качестве третьего аргумента могут быть иные данные, например указание разрешать вызов обработчика не более одного раза (о чем я расскажу в следующем разделе). Обратите внимание: метод addEventListener обращается к любому событию без использования префикса on: надо писать load, click, focus, submit и т. д. 1. Обработчики событий окна load, scroll и resize зарегистрировать проще всего. Например, для события load это можно сделать так window.addEventListener("load", start); window.addEventListener("load", func); или так window.addEventListener("load", start); window.addEventListener("load", function() { ... }); 2. Регистрация обработчика как ответ на событие load. Например так: window.addEventListener("load", function() { document.getElementById("im").addEventListener("click", func); }); 61 или так: window.addEventListener("load", function() { document.getElementById("im").addEventListener("click", function() { ... }); }); 3. Регистрация сразу нескольких обработчиков. Пример: window.addEventListener("load", function() { document.getElementById("im").addEventListener("click", func); document.getElementById("im").addEventListener("mouseout", doc); document.getElementById("im").addEventListener("mouseover", res); }); Кстати, можно записать и вот так: window.addEventListener("load", function() { func(); doc(); res(); }); Правда, такой прием в практике разработчиков встречается крайне редко. Его единственное достоинство — более короткий код. 4. Комбинированная регистрация — когда обработчики одних событий регистрируются в начале, а другие — в процессе выполнения сценария. Может выглядеть следующим образом: window.addEventListener("load", function() { document.getElementById("im").addEventListener("click", func); }); function func() { ... document.getElementById("te").addEventListener("click", doc); } function doc() { ... } 5. Еще один комбинированный случай — регистрация обработчика и одновременный запуск внешней функции: window.addEventListener("load", function() { document.getElementById("im").addEventListener("click", func); sum(); }); Метод addEventListener позволяет комбинировать регистрацию обработчиков весьма разнообразно. 62 Еще один способ регистрации обработчика методом addEventListener Этот способ в определенном смысле проще. Чтобы понять его, посмотрите пример вот такой страницы (файл 4.2_1.html в папке «Глава4» zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Регистрация обработчика</title> </head> <body> ... <p id="te">Текст</p> ... </body> </html> <script> document.getElementById("te").addEventListener("click", doc); function doc() { ... } </script> Здесь сценарий расположен ниже тела документа. В этом случае для регистрации обработчика события click не нужно заключать в блок window.addEventListener("load", function() { ... }); так как обработчики регистрируются уже после загрузки всех элементов страницы. Сомнительные практики Расположение сценария в теле документа или после него я считаю спорной практикой. На мой взгляд, сценарии или вызовы внешних файлов со сценариями лучше всего располагать в заголовочной части документа. Но это только мое личное мнение. Добавлю, что, тем не менее, в некоторых примерах из этой книги сценарии расположены именно после тела документа. Сделано это исключительно для сокращения объема кода. 63 Главное достоинство метода addEventListener в том, что его можно вызывать сколько угодно раз и зарегистрировать с его помощью сколько угодно обработчиков для одного и того же события на одном и том же элементе страницы: window.addEventListener("load", function() { document.getElementById("im").addEventListener("click", func); document.getElementById("im").addEventListener("click", doc); document.getElementById("im").addEventListener("click", res); }); Еще одно замечание: есть ситуации, когда классические приемы бессильны и без метода addEventListener не обойтись. Дело в том, что этот метод позволяет привязать обработчик к любому элементу DOM, а не только к HTMLэлементам. Например, в случае регистрации обработчика для события DOMContentLoaded можно воспользоваться только методом addEventListener. Так что это еще один плюс в пользу применения addEventListener. И, наконец, последнее соображение. Если, например, вы пытаетесь модернизировать старую программу и внедрить в нее вместо классических способов регистрацию обработчиков методом addEventListener, будьте внимательны и осторожны: в очень редких случаях такая замена может несколько изменить результаты действия сценария. 4.3. Остановка, удаление обработчика У программиста часто бывают ситуации, когда необходимо зарегистрировать обработчик, вызывающийся ограниченное количество раз, а не бесконечно. На примере метода addEventListener рассмотрим 3 варианта, как можно поступить в этом случае. Вариант 1. Нам необходимо вызвать обработчик только один раз. Как я уже писал выше, метод addEventListener может третьим аргументом принимать указание реагировать на определенное событие на выбранном элементе однократно. Остальные аналогичные события на этом элементе не вызовут реакции сценария. Делается это, например, так (приведен упрощенный вариант разметки и размещения сценария; файл 4.3_1.html в папке «Глава4» zip-архива): <p id="te">ТЕКСТ</p> <script> document.getElementById("te").addEventListener("click", func, {once: true}); let i=0; function func() { i++; 64 alert ("Количество кликов: "+i); } </script> Здесь третьим аргументом {once: true} передано указание регистратору обработчика выполнить функцию func только один раз. В результате работы такой программы диалоговое окно откроется после первого клика, а все последующие клики на строке текста будут проигнорированы сценарием. Вариант 2. Необходимо вызвать обработчик несколько раз, например, 5. На такой случай есть метод removeEventListener, который удаляет ранее зарегистрированный обработчик при достижении определенного условия. Пример его использования: <p id="te">ТЕКСТ</p> <script> document.getElementById("te").addEventListener("click", func); // Код сценария ... ... ... // Если выполняется заданное условие, обработчик удаляется if(условие) document.getElementById("te"). removeEventListener("click", func); </script> Важно: содержимое круглых скобок при регистрации обработчика методом addEventListener должно в точности совпадать с содержимым круглых скобок при вызове метода removeEventListener: document.getElementById("te").addEventListener("click", func); ... document.getElementById("te"). removeEventListener("click", func); Действующий пример кода с использованием удаления обработчика (файл 4.3_2.html в папке «Глава4» zip-архива): <p id="te">ТЕКСТ</p> <script> document.getElementById("te").addEventListener("click", func); let a=0; function func() { a++; if(a==5) document.getElementById("te").removeEventListener("click", func); alert(a); } </script> 65 Вариант 3. Вновь необходимо вызвать обработчик несколько раз. Опять возьмем в качестве примера число 5. Способ, который продемонстрирован ниже, позволяет остановить обработчик, не удаляя его (файл 4.3_3.html в папке «Глава4» zip-архива): <p id="te">ТЕКСТ</p> <script> document.getElementById("te").addEventListener("click", func); let a=0; function func() { a++; if(a<6) alert(a); } </script> В этом сценарии выполнение функции func останавливается после 5 кликов на строке текста. Удобство данного метода в том, что на определенном этапе выполнения программы вы можете обнулить переменную a, и функция func возобновит свою работу при очередном клике на тексте. Посмотрим пример, демонстрирующий «восстановление» работоспособности функции func (файл 4.3_4.html в папке «Глава4» zip-архива): <p id="te">ТЕКСТ</p> <p id="xt">ЕЩЕ ТЕКСТ</p> <script> document.getElementById("te").addEventListener("click", func); document.getElementById("xt").addEventListener("click", doc); let a=0; function func() { a++; if(a<6) alert(a); } function doc() { a=0; } </script> Здесь клик на строке с текстом «ЕЩЕ ТЕКСТ» обнулит переменную a. Причем это может быть сделано на любом этапе: и после 5 кликов, и после 2–3. 66 Впрочем, аналогичный результат может быть достигнут и таким способом (файл 4.3_5.html в папке «Глава4» zip-архива): <p id="te">ТЕКСТ</p> <p id="xt">ЕЩЕ ТЕКСТ</p> <script> document.getElementById("te").addEventListener("click", func); document.getElementById("xt").addEventListener("click", doc); let a=0; function func() { a++; if(a==5) document.getElementById("te").removeEventListener("click", func); alert(a); } function doc() { a=0; document.getElementById("te").addEventListener("click", func); } </script> В последнем варианте после 5 щелчков на основном тексте обработчик будет удален, а после клика на дополнительном тексте зарегистрирован вновь. Если же вы щелкнули на основном тексте, допустим, 2 раза, а затем кликнули на строке «ЕЩЕ ТЕКСТ», обработчик будет не удален, а перезаписан. 4.4. Глобальные и локальные переменные В предыдущих разделах в некоторых примерах мы уже пользовались переменными. Поговорим о них подробнее. Как вы, наверное, помните из книг по синтаксису JavaScript, переменная — это участок памяти, предназначенный для хранения каких-либо значений или данных. Программа к такой переменной обращается по ее имени, которое может состоять из латинских букв, цифр и символов подчеркивания. При этом существует правило, по которому имя переменной не должно начинаться с цифры и содержать пробелы. Особенность переменных в том, что хранимая в них информация может неоднократно меняться, причем меняются не только значения, но и тип данных. Например, вы можете создать переменную и сохранить в ней на начальной стадии число, а затем поместить в переменную текстовую строку. Или наоборот. Перед использованием в сценарии переменную необходимо объявить. Например, так 67 let i=1; или так let i="ТЕКСТ"; или так let i; Здесь let — это оператор объявления переменных. В первых двух случаях мы сразу присваиваем переменной текущее значение, а в последнем случае это значение будет присвоено позже на каком-то из этапов выполнения программы. JavaScript позволяет объявить сразу несколько переменных, например так: let i, t, b; И даже вот так: let i, t=0, b; Поменять тип данных, сохраненных в переменной на предыдущем этапе, очень просто: let i=1; i="Строка"; или let i="Строка"; i=1; Если вам необходимо преобразовать числовое значение из переменной в строку, то сделать это можно, «сложив» число из переменной с пустой или заполненной строкой. Хотя бы вот так: let i=1; let b=i+" января - Новый год!"; Для преобразования строки в число применяйте специальную встроенную функцию языка JavaScript — parseFloat: let i="3.14"; let t=parseFloat(i); Если необходимо определить тип переменной, воспользуйтесь оператором typeof, например таким образом: <script> let a="Строка"; let b=typeof a; alert(b); </script> 68 Powered by TCPDF (www.tcpdf.org) Наконец, вспомним о том, что для переменных важен не только тип сохраненных данных. Не менее значимой является область видимости. Последний термин определим так: область видимости — это часть сценария, в которой значения данной переменной могут быть использованы программой. Глобальные переменные доступны в любой части сценария, для любой функции. Обычно глобальные переменные объявляются примерно так: <script> let i=1; let t=0; код сценария </script> В результате переменные i и t доступны в самых разных частях сценария. Учитывайте, что их значения могут быть изменены в теле любой функции, которая оперирует с этими переменными. Локальные переменные доступны только внутри функции, условия или цикла. Объявляются они так: function func() { let a=1; ... } В этом примере значение переменной a доступно только внутри функции func. Обращение к переменной a из другой функции закончится неудачей. Наконец, в «недрах» функции может быть своя иерархия переменных. То есть одни из них оказываются глобальными внутри данной функции, а другие — локальными внутри цикла или условия, являющихся частью данной функции. Для иллюстрации подобных ситуаций создайте HTML-файл и поместите в него такой код (файл 4.4_1.html в папке «Глава4» zip-архива): <script> func(); function func() { let a=1; if(a==1) { let v=0; alert(v); } alert(v); } </script> 69 Запустите страницу в вашем браузере. Что вы увидите? Откроется первое окно, на котором будет представлено значение переменной v — это число 0. Нажмите «ОК» (или «Закрыть» в Яндекс.Браузере). Все. Хотя у нас в сценарии есть еще вызов второго диалогового окна, оно не появится, так как переменная v — локальная для тела функции и видна только внутри условия if(a==1) {...}. Все то же самое будет происходить и с переменной, созданной внутри цикла (файл 4.4_2.html в папке «Глава4» zip-архива): <script> func(); function func() { for(let c=0; c<2; c++) alert(c); alert(c); } </script> В этом случае сначала откроется первое окно, на котором будет представлено начальное значение переменной c — число 0. Нажмите «ОК». Появится второе окно с новым значением переменной c — единицей. На этом выполнение цикла завершается. Снова жмем «ОК» и убеждаемся, что третье диалоговое окно не появилось, так как переменная c — локальная для тела функции и видна только внутри цикла for(...) {...}. 4.5. let или var (или const)? Давайте теперь обсудим еще одну тему: операторы объявления переменных. В предыдущих разделах вы видели, что все переменные в наших сценариях объявлялись оператором let. Однако во многих программах, написанных несколько лет назад, а также во многих даже современных книгах по программированию на JavaScript вы можете встретить операторы объявления переменных var. Дело в том, что многие годы у разработчиков существовал только один способ объявить переменную — оператором var. В 2015 г. к этому оператору добавился еще один — let, что породило некоторый разброс в написании программ. Так все-таки var или let? Как же правильно объявлять переменную? Дело в том, что заметить разницу между этими объявлениями в простых программах чаще всего невозможно. И в одном, и в другом случае сценарии, приведенные в главе 6 этой книги, будут работать одинаково, каким бы способом мы не объявляли переменные. Но в чем же принципиальное отличие? Вот что на эту тему сообщает Mozilla Foundation на сайте https://developer.mozilla.org/: «Область видимости переменной, объявленной через var, это ее текущий контекст выполнения. Который может ограничиваться функцией или быть глобальным для переменных, 70 объявленных за пределами функции». И еще: «Директива let позволяет объявить локальную переменную с областью видимости, ограниченной текущим блоком кода». Разница все равно не ясна? Тогда вот вам два коротких сценария, которые наглядно покажут это различие. Создайте два html-файла. В один запишите такой код (файл 4.5_1.html в папке «Глава4» zip-архива): <script> var i=1; if(true) { var i=2; } alert(i); </script> Во второй (файл 4.5_2.html в папке «Глава4» zip-архива): <script> let i=1; if(true) { let i=2; } alert(i); </script> Рис. 4.5.1. Результат выполнения сценария с переменными, объявленными оператором var 71 Рис. 4.5.2. Результат выполнения сценария с переменными, объявленными оператором let По очереди запустите эти сценарии в браузере. Что вы видите? В первом случае в диалоговом окне появится цифра 2 (рис. 4.5.1), а во втором — цифра 1 (рис. 4.5.2). А все потому, что вторая переменная let объявлена внутри блока if и ее значение — 2 — видно только внутри этого блока. Надеюсь, теперь главная и принципиальная разница ясна. Но эта разница не единственная. Посмотрим следующее отличие снова на примере двух коротких сценариев. Первый (файл 4.5_3.html в папке «Глава4» zip-архива): <script> alert(i); var i=1; </script> Второй (файл 4.5_4.html в папке «Глава4» zip-архива): <script> alert(i); let i=1; </script> Если по очереди запустить данные сценарии, то вы обнаружите, что в первом случае появится диалоговое окно с сообщением «undefined», что означает «переменная не определена, но она существует». А во втором диалоговое окно вообще не будет запущено. Это значит, что «переменная не существует». 72 Вывод: оператор let — более современный. Опытные разработчики советуют использовать объявления let. Я придерживаюсь аналогичного мнения и рекомендую везде применять let. Теперь вернемся к первым двум примерам, где у нас объявлялись одноименные глобальные и локальные переменные. Точнее, к примеру с объявлением переменной let: <script> let i=1; if(true) { let i=2; } alert(i); </script> Такой сценарий хорош только для демонстрации разницы между операторами var и let. Но в реальных программах я бы не советовал использовать одноименные глобальные и локальные переменные — велика вероятность допустить ошибку в коде, манипулируя такими переменными. Пусть лучше у каждой переменной будет свое уникальное имя. Наконец, поговорим про оператор объявления констант const. Он позволяет присвоить переменной какое-либо значение, но только один раз. Повторное присвоение значения переменной, объявленной оператором const, приведет к возникновению ошибки в программе. Оператор const удобно использовать для сокращения программы, если в ней, например, происходят многократные обращения к одному и тому же элементу разметки: const v=document.getElementById("im").style; v.width="100px"; v.opacity="0.5"; v.border="2px solid #0000CC"; Еще пример с данным оператором: for(let k=1; k<3; k++) { let s="im"+k; const v=document.getElementById(s).style; v.width="100px"; v.opacity="0.5"; v.border="2px solid #0000CC"; } В этом случае константа v создается каждый раз заново, поэтому в данном примере ошибка не возникнет. 73 Корректно будет работать и такой сценарий: function func(z) { let s="im"+z; const v=document.getElementById(s).style; v.width="100px"; v.opacity="0.5"; v.border="2px solid #0000CC"; } Как видите из этих примеров, константы тоже могут быть локальными и глобальными, как и переменные, объявленные операторами let или var. И еще два момента: при объявлении константы ей сразу должно быть присвоено значение; имя константы не может совпадать с именами функций или переменных let (и var) той же области видимости. 4.6. Меньше или больше переменных? Ответ на этот вопрос зависит от того, какой фрагмент кода мы возьмемся рассматривать. Помните, в предыдущем разделе у нас был пример с оператором const? Мы говорили, что его удобно использовать для сокращения программы, если в ней, например, происходят многократные обращения к одному и тому же элементу разметки: const v=document.getElementById("im").style; v.width="100px"; v.opacity="0.5"; v.border="2px solid #0000CC"; v. float="right"; Согласитесь, что вариант кода, приведенный ниже, более громоздкий: document.getElementById("im").style.width="100px"; document.getElementById("im").style.opacity="0.5"; document.getElementById("im").style.border="2px solid #0000CC"; document.getElementById("im").style .float="right"; Как мы видим, в первом случае создание необязательной переменной v полностью оправдано. Зато в следующем примере объявленные константы окажутся явно лишними: const v=document.getElementById("im1").style; v.width="100px"; const w=document.getElementById("im2").style; w.width="200px"; 74 Если в программе происходит только однократное обращение к элементу, проще переписать данный пример так: document.getElementById("im1").style.width="100px"; document.getElementById("im2").style.width="200px"; В таком варианте налицо явная экономия кода. Пример другого порядка. В теле функции есть два цикла: function func() { ... for(let d=0; d<5; d++) { ... } ... for(let f=0; f<3; f++) { ... } ... } Поскольку циклы работают по одному разу и последовательно друг за другом, получается, что переменная d после выполнения соответствующего блока кода уже не нужна, однако остается висеть в памяти компьютера. Так может, имеет смысл использовать ее снова — теперь уже во втором цикле? Реализуем данный подход, переписав тело функции: function func() { ... let d; for(d=0; d<5; d++) { ... } ... for(d=0; d<3; d++) { ... } ... } Тем самым мы чуть-чуть сэкономим на памяти, которая теперь занята не двумя переменными, а только одной. 75 Приведенные фрагменты сценариев показывают, как, грамотно манипулируя переменными, можно сделать код более лаконичным. Впрочем, приемам оптимизации у нас будет посвящен раздел 5.2 из главы 5. 4.7. Массивы О массивах написано достаточно много. Я лишь напомню некоторые «факты» и приведу один не совсем обычный пример использования массива. Итак, начнем. Массивы — это объектный тип данных. Объявить массив очень просто: let mas=[элемент 1, элемент 2, ... элемент n]; Можно также создать пустой массив, который будет заполнен в процессе выполнения сценария: let mas=[]; Для получения отдельного элемента используются записи следующего вида: mas[0] mas[1] ... mas[n] Чтобы узнать, сколько элементов в массиве, надо обратиться к свойству length: let d=mas.length; Обратите внимание: индексы элементов массива начинаются с 0, а их количество отсчитывается с 1. Добавить новый элемент массива тоже очень просто: mas[n+1]=элемент n+1; Начинающие программисты наиболее часто используют методы обработки массивов, которые приведены в таблице 4.7.1. Таблица 4.7.1 Методы обработки массивов Метод concat() indexOf() join() Описание Объединяет несколько исходных массивов и создает новый Выполняет поиск элемента в массиве Формирует из элементов массива текстовую строку с разделителем, переданным аргументом 76 Продолжение табл. 4.7.1 Метод pop() push() reverse() shift() sort() splice() unshift() Описание Удаляет последний элемент массива и возвращает этот элемент Добавляет новые элементы в конец массива и возвращает его новую длину Выполняет пересортировку массива в обратном порядке Удаляет элемент массива с индексом 0 и возвращает этот элемент Производит сортировку элементов массива по заданной функции сравнения Удаляет часть массива, а на его место добавляет новые элементы Добавляет элементы в начало массива Обычно массивы применяют для манипулирования различными данными в сценариях. Однако массив может пригодиться и для создания определенных визуальных эффектов. Примером служит страница, приведенная ниже (файл 4.7_1.html в папке «Глава4» zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Таймер загрузки</title> <style> div {text-align: center;} .tim {width: 15px; height: 10px; background: #0000CC; display: inline-block; margin: 1px;} </style> <script> addEventListener("load", race); let i=0; let d=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; function race() { let t=""; for(let a=0; a<=d[i]; a++) t+="<div class='tim'></div>"; document.getElementById("loa").innerHTML=t; i++; if(i==d.length) i=0; 77 setTimeout(race, 80); } </script> </head> <body> <div id="loa"></div> </body> </html> Такой сценарий можно использовать при создании индикатора процесса загрузки, например, при отправке данных со страницы или добавлении на нее нового контента. Работает программа очень просто. Есть контейнер для индикатора: <div id="loa"></div> Есть счетчик элементов массива let i=0; и сам массив let d=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; Для упрощения примера сценарий запускается сразу после загрузки документа: addEventListener("load", race); Функция race, управляющая индикатором, вызывается каждые 80 мс function race() { ... setTimeout(race, 80); } благодаря чему в контейнер с равными (и очень короткими) промежутками выводятся очередные показания: document.getElementById("loa").innerHTML=t; В исходном состоянии переменная t «пустая»: let t=""; 78 Ее заполнение производится в цикле for(let a=0; a<=d[i]; a++) t+="<div class='tim'></div>"; При каждом вызове функции race количество проходов цикла определяется числом из очередного элемента массива: a<=d[i] Внутри цикла переменная t заполняется кодом, который при выводе на страницу образует картинку из нескольких штрихов t+="<div class='tim'></div>"; определенного размера и цвета: .tim {width: 15px; height: 10px; background: #0000CC; display: inline-block; margin: 1px;} Количество штрихов равно очередному числу из массива. Повторимся: изображение показывается на странице после заполнения переменной t при каждом вызове функции race: document.getElementById("loa").innerHTML=t; после чего счетчик элементов массива увеличивается на единицу: i++; Когда функция переберет все элементы массива, счетчик i обнулится и процесс начнется заново: if(i==d.length) i=0; Рис. 4.7.1. Индикатор (таймер) загрузки 79 Поскольку значения элементов массива сначала идут по возрастающей, а достигнув числа 15, начинают убывать, индикатор выглядит как непрерывно бегущие от минимума к максимуму и обратно штрихи (рис. 4.7.1). Как видите, массив оказался полезен даже в таком необычном качестве. 4.8. Операторы Один из важнейших аспектов выполнения сценариев на JavaScript — операции с переменными. Для этого существуют несколько видов операторов. Арифметические операторы. Для начинающего наиболее важны те, что представленные в таблице 4.8.1. Таблица 4.8.1 Арифметические операторы Оператор Действие Пример x=1; x="Текст"; = Присваивание + Сложение x+y * / ** Вычитание Умножение Деление Возведение в степень x-y x*y x/y x**y Примечание Присваивает переменной данные любого типа Применяется также к переменным, содержащим строки; результатом будет новая строка Обычно результат выполнения операции присваивается либо новой переменной let z=x+y; либо одной из переменных, участвующих в данной операции: x=x+y; Для второго случая существуют сокращенные формы записи операций присваивания, представленные в таблице 4.8.2. Таблица 4.8.2 Сокращенная форма записи операций Сокращенная запись Аналог x+=y; x=x+y; x-=y; x*=y; x/=y; x**=y; x=x-y; x=x*y; x=x/y; x=x**y; Примечание Применяется также к переменным, содержащим строки; результатом будет новая строка 80 Последний сокращенный оператор x**=y; практически не упоминается в компьютерной литературе, но тем не менее он существует и прекрасно работает. Вот пример (файл 4.8_1.html в папке «Глава4» zip-архива): <script> let x=2; let y=4; x**=y; alert(x); </script> Еще две важные операции: инкремента и декремента. Разъяснения к ним даны в таблице 4.8.3. Таблица 4.8.3 Операторы инкремента и декремента Оператор Название ++ Оператор инкремента – Оператор декремента Действие Увеличение значения переменной на 1 Уменьшение значения переменной на 1 Пример x++; x–; В некоторых случаях с операторами инкремента и декремента надо обращаться очень аккуратно. Почему? Дело в том, что формы записи данных операторов могут иметь два вида. Например, оператор инкремента: x++; и ++x;. Оператор декремента: x--; и --x;. В каких ситуациях форма записи играет решающее значение? Когда вы хотите для сокращения кода выполнить сразу два действия: увеличить переменную и присвоить ее значение другой переменной. Здесь возможны следующие варианты. 1. Сначала присваиваем текущее значение первой переменной (назовем ее x) второй переменной (назовем ее y), а потом увеличиваем на единицу переменную x, то есть используем форму записи x++ (файл 4.8_2.html в папке «Глава4» zip-архива): <script> let x=1; let y=x++; alert(y+" "+x); </script> 2. Сначала увеличиваем текущее значение переменной x на единицу, то есть используем форму записи ++x, а потом присваиваем ее значение переменной y (файл 4.8_3.html в папке «Глава4» zip-архива): <script> let x=1; let y=++x; alert(y+" "+x); </script> 81 Как вы можете убедиться, запустив данные файлы в своем браузере, перемена мест оператора инкремента влияет на результат. Аналогично поведет себя сценарий, где используются операторы декремента. Пойдем дальше. Следующие у нас на очереди операторы сравнения, которые представлены в таблице 4.8.4. Таблица 4.8.4 Операторы сравнения Оператор == != > >= < <= === !== Проверяемое условие Равно Не равно Больше Больше или равно Меньше Меньше или равно Строго равно Строго не равно Пример x==y x!=y x>y x>=y x<y x<=y x===y x!==y Про последние два оператора хотелось бы рассказать подробнее. Назначение данных операторов можно продемонстрировать на четырех примерах. Пример 1 (файл 4.8_4.html в папке «Глава4» zip-архива): <script> let a=0; if(a==0) alert(typeof a); </script> Пример 2 (файл 4.8_5.html в папке «Глава4» zip-архива): <script> let a="0"; if(a==0) alert(typeof a); </script> Если по очереди запустить эти файлы в браузере, вы убедитесь: несмотря на различие типов данных программа считает, что в обоих случаях значения переменной x равно 0 и, соответственно, выводит подтверждающее сообщение. А все потому, что мы использовали оператор простого сравнения ==. Чтобы избежать подобных ошибок и существуют операторы строго сравнения. Применив один из них (===) в третьем и четвертом примерах, мы получим требуемый результат. Пример 3 (файл 4.8_6.html в папке «Глава4» zip-архива): <script> let a="0"; if(a===0) alert(typeof a); </script> 82 В этом примере диалоговое окно с сообщением о типе переменной не появится, так как условие строгого равенства не выполнено. Пример 4 (файл 4.8_7.html в папке «Глава4» zip-архива): <script> let a=0; if(a===0) alert(typeof a); </script> В этом случае диалоговое окно с сообщением о типе переменной, наоборот, откроется, так как условие строгого равенства выполнено. Наконец, завершающая таблица 4.8.5 содержит перечень логических операторов. Таблица 4.8.5 Логические операторы Оператор Действие && Логическое умножение (И) ! Логическое отрицание (НЕ) || Логическое сложение (ИЛИ) Возвращаемый результат true, если все операнды истинны и false, если хотя бы один операнд ложен true, если операнд ложен и false, если операнд истинен true, если хотя бы один операнд истинен и false, если все операнды ложны Теперь вернемся к операторам сравнения. Обращаю ваше внимание, что запись двойного сравнения, к которой мы привыкли в школе, в JavaScript не сработает. Так писать нельзя: 1<x<10. Правильная запись: x>1&&x<10. В этом варианте выполняется корректная проверка обоих условий, а затем логическое умножение, о котором мы говорили только что. 4.9. Условные операторы Условные операторы необходимы для проверки определенных данных на соответствие некоторым критериям. Вариант наиболее простого использования условного оператора выглядит так: if(условие) { инструкции } 83 Здесь инструкции будут выполняться в случае истинности условия. Если необходимо предусмотреть альтернативные инструкции на случай ложности условия, то используют следующую форму записи: if(условие) { инструкции 1 } else { инструкции 2 } Инструкции блока else будут выполняться в случае ложности условия. Бывает, надо проверить несколько условий. В этой ситуации используют такую конструкцию: if(условие 1) { инструкции 1 } else if(условие 2) { инструкции 2 } ... else if(условие n) { инструкции n } else { инструкции на случай ложности всех условий } Иногда требуется проверить несколько условий, но при этом не выполнять никаких действий, если все условия окажутся ложными. Представим, что у нас есть переменная t, которая может принимать разные числовые значения, но для нас важны только три из них: 1, 2 и 3. В этом случае условные выражения можно записать так if(t==1) { инструкции 1 } if(t==2) { инструкции 2 } if(t==3) { инструкции 3 } 84 и даже так: if(t==1) { инструкции 1 } else if(t==2) { инструкции 2 } else if(t==3) { инструкции 3 } Еще один способ проверки — оператором выбора switch. Он может быть полезен как альтернатива рассмотренному выше случаю проверки нескольких условий. Выражения с использованием данного оператора выглядят так: switch (выражение) { case значение 1: инструкции 1 break; case значение 2: инструкции 2 break; ... case значение n: инструкции n break; default: инструкции на случай отсутствия совпадений } Здесь значение из каждого блока последовательно сравнивается с заданным выражением. В случае совпадения выполняются инструкции соответствующего блока. На случай отсутствия совпадений предусмотрены инструкции в блоке default. Если при отсутствии совпадений никакие действия не нужны, то блок default можно не использовать: switch (t) { case 1: инструкции 1 break; case 2: инструкции 2 break; case 3: инструкции 3 } 85 Powered by TCPDF (www.tcpdf.org) Кроме того, блоки можно объединять, если при разных значениях должны выполняться аналогичные инструкции: switch (t) { case 1: инструкции 1 break; case 2: case 3: инструкции 2 } Если у вас после проверки условия выполняется только одна инструкция, то можно использовать сокращенную форму записи выражения: if(условие) инструкция или if(условие) инструкция 1 else инструкция 2 или if(условие 1) инструкция 1 else if(условие 2) инструкция 2 ... else if(условие n) инструкция n else инструкция на случай ложности всех условий Иногда удобнее выполнять проверку условий тернарным оператором. Он принимает три операнда: условие, инструкцию, которая выполняется, если условие истинно, и инструкцию, которая выполняется, если условие ложно. Его запись выглядит так: let a=условие ? инструкция 1 : инструкция 2; Как видите, результат выполнения тернарного оператора можно присвоить переменной. Пример реальной записи: let a=b<5?0:1; И еще одно интересное замечание. В качестве проверяемых выражений в условные операторы можно передавать возвращаемые значения из внешних 86 функций. Такой пример показан ниже (файл 4.9_1.html в папке «Глава4» zipархива): <input type="button" value="TRUE" id="bu1"> <input type="button" value="FALSE" id="bu2"> <script> document.getElementById("bu1").addEventListener("click", but); document.getElementById("bu2").addEventListener("click", but); function b(a) { if(a==1) return true; if(a==2) return false; } function but(ev) { let t=parseFloat(ev.target.id.substr(2)); if(b(t)) alert(true); } </script> Документ содержит две кнопки <input type="button" value="TRUE" id="bu1"> <input type="button" value="FALSE" id="bu2"> которые запускают одну и ту же функцию but: document.getElementById("bu1").addEventListener("click", but); document.getElementById("bu2").addEventListener("click", but); Первым делом из id выделяется число: let t=parseFloat(ev.target.id.substr(2)); А затем проверяется условие if(b(t)) выражение которого вычисляется путем вызова функции b function b(a) { If(a==1) return true; if(a==2) return false; } 87 с полученным числом в качестве аргумента: b(t) Таким образом, фактически на истинность проверяется значение, возвращаемое из функции. Если запустить в браузере указанную страницу, вы увидите, что диалоговое окно появится только после нажатия кнопки «TRUE». Если нажать кнопку «FALSE», функция b вернет false, и условие станет ложным. Читатели должны понимать, что пример этот демонстрационный и не имеет практической пользы, так как проверку значения, полученного из id, рациональнее было бы производить внутри основной функции but. Но данный сценарий разъясняет принцип, которым можно воспользоваться в реальных программах, если это необходимо. 4.10. Операторы циклов Операторы циклов необходимы для перебора некоторых значений в заданных пределах и поочередного выполнения определенных инструкций, иногда зависящих от значения на очередном проходе, а иногда нет. Их удобно использовать в тех случаях, когда какую-то часть программного кода надо выполнить многократно. Наиболее универсален оператор for. Его структура обычно выглядит так: for(исходное значение; условие; приращение) { инструкции } В реальном сценарии цикл оформляют, например, следующим образом: for(let a=1; a<7; a++) { let b="im"+a; document.getElementById(b).style.width=500+"px"; } Если в цикле выполняется только одна инструкция, то можно использовать сокращенную форму записи: for(let a=1; a<7; a++) document.getElementById("im"+a).style.width=500+"px"; В цикле for допустимо объявлять сразу несколько переменных-счетчиков и одновременно производить над ними различные операции (файл 4.10_1.html в папке «Глава4» zip-архива): for(let a=1, b=5; a<7; a++, b--) alert(a+" "+b); 88 Кроме того, при необходимости легко применить множественные условия (файл 4.10_2.html в папке «Глава4» zip-архива): for(let a=1, b=5; a<7&&b>2; a++, b--) alert(a+" "+b); Еще один полезный оператор цикла — while. Он устроен несколько проще: while(условие) { инструкции } Пример записи цикла в реальном сценарии: let a=1; while (a<7) { let b="im"+a; document.getElementById(b).style.width=500+"px"; a++; } Наконец, третий оператор цикла: do-while. Этот оператор имеет одно принципиальное отличие от while. В цикле while сначала проверяется условие, а потом, если оно истинно, выполняются инструкции. Таким образом, если условие изначально ложно, ни одного прохода не состоится. Оператор do-while сначала выполняет инструкции, а потом проверяет условие. Поэтому даже при изначально ложном условии инструкции будут выполнены один раз. Форма записи оператора do-while: do { инструкции } while(условие) Пример из реального сценария: let a=1; do { let b="im"+a; document.getElementById(b).style.width=500+"px"; a++; } while(a<7) 89 Как и условные операторы, циклы for и while могут получать условия и приращения из внешних функций. Вот пример на данную тему (файл 4.10_3.html в папке «Глава4» zip-архива): <input type="button" value="Пуск" id="bup"> <script> document.getElementById("bup").addEventListener("click", but); let a=1; let b=function() { a++; } function but() { for(a=1; a<7; b()) alert(a); } </script> Если нажать кнопку «Пуск» шесть раз подряд, откроются диалоговые окна, поочередно демонстрирующие цифры от 1 до 6. Этим процессом управляет функция but function but() { ... } в теле которой есть оператор цикла for: for(a=1; a<7; b()) alert(a); Значение приращения на каждом проходе цикл получает из функции b: let b=function() { a++; } При каждом обращении к ней функция b увеличивает значение счетчика a на единицу. Вызов функции происходит шесть раз (пока значение счетчика меньше 7): a<7 Обычно один цикл работает в одном направлении: значение счетчика либо увеличивается, либо уменьшается. Получение приращения из внешней 90 функции может быть полезно, чтобы «заставить» работать один и тот же цикл в разных «направлениях». Вот код страницы, демонстрирующей такой прием (файл 4.10_4.html в папке «Глава4» zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Передача приращения в двунаправленный цикл</title> <script> addEventListener("load", function() { document.getElementById("bup").addEventListener("click", but); document.getElementById("bum").addEventListener("click", but); }); let a=1; let c; let b=function() { if(c=="bup") a++; if(c=="bum") a--; }; function but(ev) { c=ev.target.id; for(a; a>0&&a<7; b()) alert(a); if(a==7) a=6; if(a==0) a=1; } </script> </head> <body> <input type="button" value="Вперед" id="bup"> <input type="button" value="Назад" id="bum"> </body> </html> В новом примере у нас две кнопки <input type="button" value="Вперед" id="bup"> <input type="button" value="Назад" id="bum"> привязанные к одному обработчику — функции but: document.getElementById("bup").addEventListener("click", but); document.getElementById("bum").addEventListener("click", but); 91 Первая инструкция функции определяет id нажатой кнопки и помещает его значение в глобальную переменную c: c=ev.target.id; После этого запускается цикл for(a; a>0&&a<7; b()) alert(a); который получает приращение из функции b: let b=function() { if(c=="bup") a++; if(c=="bum") a--; }; Эта функция в зависимости от id нажатой кнопки либо увеличивает значение счетчика, либо уменьшает. Чтобы глобальная переменная a не вышла за рамки, ограничивающие количество проходов в обоих направлениях, в функции but предусмотрены два условных выражения: if(a==7) a=6; if(a==0) a=1; Запустим страницу в браузере и нажмем кнопку «Вперед». После этого шесть раз подряд откроются диалоговые окна, поочередно демонстрирующие цифры от 1 до 6. Теперь нажмем кнопку «Назад». Снова поочередно откроются шесть диалоговых окон, но теперь на каждом из них цифры будут идти по убывающей — от 6 до 1. Понятно, что данная программа не имеет практического значения, а только демонстрирует принцип. Позднее в главе 6 в разделе 6.7 мы увидим, что такая технология вполне применима в реальных сценариях. 4.11. Функции Одни из самых важных «действующих лиц» любой программы на JavaScript — функции. Именно они являются обработчиками событий, в них выполняется большинство операций, вычислений, преобразований и т. д. Перефразируя известную песню, скажем: функции бывают разные. 92 Например, по назначению их можно разделить на два основных вида. Первый — функции, выполняющие действия, связанные с изменением состояния и параметров различных элементов документа. Пример: function func() { document.getElementById("flo").style.width="200px"; document.getElementById("photo").src="im.jpg"; } Задача такой функции — увеличить одно изображение и поменять адрес другого. Вторая разновидность — функции, необходимые для вычислений: let a=0; function func() { a++; } В этом примере функция при каждом вызове увеличивает значение переменной a, которое затем может быть использовано в других операциях сценария. Естественно, что бывают и смешанные варианты, когда выполняемые инструкции направлены на манипулирование элементами страницы и производство требуемых вычислений. По методу обращения к ним функции можно разделить на 3 группы. 1. Именованные — имеющие имя, по которому они могут быть вызваны: // Описание функции function func() { ... } // Вызов функции func(); или // Вызов функции как обработчика события document.getElementById("te").addEventListener("click", doc); // Описание функции function doc() { ... } 2. Анонимные функции, то есть не имеющие имени и «приписанные» непосредственно к регистратору обработчика. Пример: 93 document.getElementById("im").addEventListener("click", function() { ... }); 3. Анонимные функции, изначально присвоенные какой-либо переменной. Допустим, вот так: let v=function(x) { ... } Вызов этой функции может выглядеть следующим образом: let z=v(8); И даже еще проще: v(8); Правда, оговорюсь, что, на мой взгляд, такие функции все же необходимо считать «условно анонимными», ведь к ним можно обратиться по имени переменной. Однако я не уверен, что со мной согласятся все программисты. Есть еще один интересный случай — функции-стрелки. Их особенность — более короткая и простая запись. Функции-стрелки объявляются двумя последовательными символами =>. Один из возможных вариантов такой функции: let h=0; let y=(g)=> { h++; let f=g+" - переданное число<br>"+h+" - количество кликов"; return f; }; Вызвать данную функцию можно так: document.getElementById("p1").addEventListener("click", function() { document.getElementById("p2").innerHTML=y(10); }); Чтобы пример был понятнее, напишем упрощенный вариант страницы с полным кодом (файл 4.11_1.html в папке «Глава4» zip-архива): <p id="p1">СТРОКА</p> <p id="p2"></p> <script> let h=0; 94 let y=(g)=> { h++; let f=g+" - переданное число<br>"+h+" - количество кликов"; return f; }; document.getElementById("p1").addEventListener("click", function() { document.getElementById("p2").innerHTML=y(10); }); </script> Здесь после каждого клика на первой строке — во второй будет появляться переданное в функцию число и меняющийся показатель количества щелчков. Функции-стрелки могут быть записаны гораздо проще, если выполняют всего одну операцию. Вот пример (файл 4.11_2.html в папке «Глава4» zipархива): let k=()=>25**3; Вызовем эту функцию: document.getElementById("p1").addEventListener("click", function() { document.getElementById("p2").innerHTML=k(); }); Часто программисту необходимо использовать рекурсивные функции — то есть те, что вызывают сами себя. Это можно сделать двумя способами. Допустим, мы хотим вычислить сумму квадратов чисел от 1 до 5. Напишем такой сценарий (файл 4.11_3.html в папке «Глава4» zip-архива): let x=1; let t=0; func(5); function func(i) { t+=x**2; x++; if(x<=i) func(i); } В этом примере переменная x — счетчик повторов функции, t — переменная для хранения результатов вычислений. Пока x меньше значения аргумента, функция вызывается вновь: if(x<=i) func(i); 95 Результаты вычислений можно вывести на страницу: document.getElementById("kv").insertAdjacentHTML("beforeend", t); Для этого в теле документа у нас есть строка: <p id="kv">Сумма квадратов чисел от 1 до 5: </p> Создавая рекурсивную функцию, обязательно предусмотрите предел ее выполнения. Если этого не сделать, процесс станет бесконечным и страница с функцией зависнет. В случае нашего примера такой предел установлен — количество повторов равно 5. Еще один способ создания рекурсивной функции — использование таймера setTimeout. Рассмотрим вариант программы с таймером. А для этого в предыдущий пример внесем некоторые изменения. Теперь сценарий будет выглядеть так (файл 4.11_4.html в папке «Глава4» zip-архива): let x=0; let t=0; func(); function func() { x++; t+=x**2; if(x<=5) { window.setTimeout(func, 1000); document.getElementById("kv").innerHTML=t; } } Строка для демонстрации результатов вычислений: <p>Сумма квадратов чисел от 1 до 5: <span id="kv"></span></p> Если запустить такую страницу в браузере, вы увидите, как показания в строке «Сумма квадратов чисел от 1 до 5:» станут изменяться 1 раз/с. Теперь о передаче параметров в функцию. Как вы уже видели из упомянутых примеров, это можно сделать разными способами. 1. Передать данные в качестве аргумента (или аргументов, если их много). Вот так: func(15); function func(i) { // инструкции, выполняемые в зависимости от значения аргумента i } 96 2. Использовать для передачи данных значение глобальной переменной: let a=15; function func() { // инструкции, выполняемые в зависимости от значения переменной a } 3. Еще один вариант, который мы пока не упоминали: передача в качестве аргумента функции — объекта события (с последующим извлечением данных, например id). Рассмотрим этот случай подробнее. Пусть документ содержит несколько изображений: <img id="i1" src="..." alt="Фото"> ... <img id="in" src="..." alt="Фото"> Станем фиксировать щелчки мышью на странице: window.addEventListener("click", res); Обработчиком у нас назначена функция res. Первоначально она получает данные из интерфейса event (его еще называют объектом события): function res(ev) { ... } Сначала проверим, где произошло событие. Нам важны щелчки на изображениях: if(ev.target.tagName=="IMG") { ... } Если условие верно, определим id рисунка: ev.target.id и «вычленим» из него число: let p=ev.target.id.substr(1); Теперь, в зависимости от полученного результата, выполняются какие-то инструкции: if(условие, зависящее от значения переменной p) { инструкции } 97 Как видите, по сути функция res получает аргументы из id. Запишем функцию целиком (файл 4.11_5.html в папке «Глава4» zip-архива): function res(ev) { if(ev.target.tagName=="IMG") { let p=ev.target.id.substr(1); if(условие, зависящее от значения переменной p) { инструкции } } } 4. Наконец, допустим смешанный вариант — это когда одна и та же функция может получать аргументы совершенно разных типов. Например, число или данные из интерфейса event. Частный случай такой функции показан ниже (файл 4.11_6.html в папке «Глава4» zip-архива): <p id="p1">СТРОКА</p> <script> document.getElementById("p1").addEventListener("click", func); func(5); function func(ev) { if(typeof ev==="number") alert(ev); else alert(ev.target.id); } </script> В этом примере кроме кода JavaScript у нас есть еще текстовая строка, которая нужна для полноценной демонстрации работы функции. Сразу после загрузки страницы функция получает в качестве аргумента число, которое выводится в диалоговом окне. Если после его закрытия щелкнуть мышью на текст, то откроется новое окно с сообщением, что событие произошло на элементе, id значение которого — p1. Раз в функцию можно передавать какие-то параметры, значит, можно и получать результаты работы функции. Покажем два способа. 1. Прямое указание в теле функции возвращаемого значения. Для этого используется оператор return. Выше мы говорили о стрелочных функциях и проиллюстрировали рассказ таким примером: let y=(g)=> { h++; let f=g+" - переданное число<br>"+h+" - количество кликов"; return f; }; 98 Как видите, здесь прямо указано, что функция возвращает значение return f; которое можно использовать в других частях сценария. 2. Результатом работы функции бывает изменение глобальной переменной. Естественно, что новое значение этой переменной также может использоваться в других операциях. let a=0; function func() { // операции с переменной a } Здесь новое значение переменной a условно можно считать возвращаемым. Итак, мы «квалифицировали» функции JavaScript по самым разным критериям. Предполагаю, что у начинающих разработчиков первые сценарии будут сравнительно простыми. Поэтому надеюсь, что этого рассказа о функциях на первых порах будет достаточно. Окунетесь в программирование глубже — тогда и познакомитесь с другими нюансами создания и применения функций. 4.12. Технология Ajax Напомню, что Ajax расшифровывается как «Асинхронный JavaScript и XML» и представляет собой передовую технологию взаимодействия между страницей, загруженной в браузер, и сервером. Как происходило такое взаимодействие раньше? Клиент отправлял информацию, а сервер передавал в браузер новую HTML-страницу с измененными данными или сообщениями о результате отправки данных из формы. Ключевой момент ранних технологий — загрузка новой страницы вместо исходной. Это увеличивает время ожидания ответа сервера, повышает интернет-трафик. Такие факторы особенно чувствительно ощущались во времена подключения к Интернету через модемы. Потом разработчики придумали хитроумный способ обходиться без новой страницы. Чтобы выводить необходимую информацию, текущий документ снабжался невидимым фреймом. Когда появлялась нужда в фоновом режиме получить какие-то данные с сервера, у фрейма программным способом менялся адрес загруженного файла на новый в соответствии с запросом клиента. Таким образом, во фрейме появлялись затребованные данные, после чего происходило, опять же на программном уровне, считывание полученного файла и добавление информации на страницу. Однако и этот метод имел определенные ограничения. 99 И только создание технологии Ajax позволило делать сайты, где информация добавлялась на страницу без лишних выкрутасов и очень быстро. А учитывая, что современные посетители сайтов любят, когда все происходит без лишних задержек, автор советует web-программистам использовать Ajax при любой возможности. Рассмотрим некоторые варианты применения Ajax в web-разработках. Один из наиболее типичных примеров — отправка клиентом заполненной формы. В этом случае результаты проверки данных отображаются прямо на текущей странице. Нажал кнопку «Отправить» — и тут же получил ответ «Ваша заявка зарегистрирована» или «Вы указали некорректные сведения». Рассмотрим конкретную программу. Пусть у нас имеется форма с несколькими полями, в том числе и для загрузки файлов. Присвоим форме имя dat (файл 4.12_1.html в папке «Глава4» zip-архива): <form name="dat" enctype="multipart/form-data"> Ваше имя: <input type="text" name="F1"><br> Телефон: <input type="text" name="F2"><br> E-mail: <input type="text" name="F3"><br> Сообщение: <textarea name="F4"></textarea><br> Фото: <input type="file" name="F5"><br> <input id="bu" type="button" value="Отправить"> </form> Добавим на страницу место вывода ответа сервера. Для этого используем пустой слой div, расположив его под формой: <div id="att"></div> Зарегистрируем обработчик для клика на кнопке отправки данных: window.addEventListener("load", function() { document.getElementById("bu").addEventListener("click", ret); }); Напишем функцию ret, предназначенную для отправки формы: function ret() { let dt=new FormData(document.forms.dat); let re=new XMLHttpRequest(); re.open("POST", "rec.php", true); re.send(dt); re.addEventListener("load", show); } Здесь rec.php — серверная программа, обрабатывающая данные полей формы. Просто для примера мы указали, что она написана на PHP. Но с таким же успехом это может быть файл Python, Perl или Ruby. 100 Powered by TCPDF (www.tcpdf.org) Напомню некоторые определения. 1. FormData — прикладной интерфейс, создающий тело запроса, состоящего из нескольких частей. Такими частями могут быть текст или файлы. Необходим для создания запроса с данными в формате multipart/form-data. 2. XMLHttpRequest — это класс прикладного интерфейса, каждый экземпляр которого предоставляет свойства и методы, позволяющие формировать запрос к серверу и получать данные из ответа. 3. Метод open формирует запрос к серверной программе. 4. Метод send отправляет запрос. После загрузки ответа будет запущена функция show: re.addEventListener("load", show); Задача функции show — вывести ответ сервера на текущую страницу: function show() { let ans=this.responseText; if(ans==1) document.getElementById("att").innerHTML="СООБЩЕНИЕ ДОСТАВЛЕНО"; else document.getElementById("att").innerHTML="НЕКОРРЕКТНЫЕ ДАННЫЕ"; } Свойство responseText предоставляет доступ к ответу сервера. Значение responseText — текстовая строка. Если программа rec.php передала в качестве ответа число 1, значит, сообщение успешно обработано. Если число 2, то в отправленных данных были некорректно заполненные поля. Соответствующие предупреждения появятся у нас в слое div. Обратите внимание: в серверных программах, используемых в реальных проектах, необходимо обязательно предусмотреть меры безопасности, чтобы перекрыть недоброжелателям любые возможности нанести урон вашему сайту. Контроль на стороне сервера должен быть очень строгим, ведь именно здесь ставится барьер для недоброжелателей. Надо обязательно проверять объем входящей информации, типы файлов, наличие исполняемого кода и запрещенных символов, соответствие ссылок требуемому формату и разрешенным адресам. Мы вывели на страницу ответ сервера в виде текстового сообщения, расположенного под формой. Однако это не единственный вариант. Для демонстрации ответа сервера можно использовать слои с заранее подготовленными фразами и показывать эти слои, например поверх формы. Рассмотрим такой вариант. Есть два слоя: <div id="att1">СООБЩЕНИЕ ДОСТАВЛЕНО</div> <div id="att2">НЕКОРРЕКТНЫЕ ДАННЫЕ</div> 101 В исходном состоянии они не видны: <style> div {visibility: hidden;} </style> В этом случае функция show будет записана так (файл 4.12_2.html в папке «Глава4» zip-архива): function show() { let ans="att"+this.responseText; document.getElementById(ans).style.visibility="visible"; } Тот или иной слой показывается в зависимости от числа, переданного в ответе сервера. Кстати, не забудьте предусмотреть на каждом слое какойнибудь значок, по щелчку на котором сообщение будет скрываться. Наконец, могу предложить еще один вариант демонстрации ответа сервера — креативный. Можно писать сообщения непосредственно на кнопке отправки данных. Вот конкретный пример. В исходном состоянии кнопка имеет текст «Отправить»: <input id="bu" type="button" value="Отправить"> Поменяем кое-что в функции show (файл 4.12_3.html в папке «Глава4» zip-архива): function show() { let ans=this.responseText; if(ans==1) document.getElementById("bu").value="СООБЩЕНИЕ ДОСТАВЛЕНО"; else document.getElementById("bu").value="НЕКОРРЕКТНЫЕ ДАННЫЕ"; } В результате при положительном ответе сервера на кнопке появится текст «СООБЩЕНИЕ ДОСТАВЛЕНО», а при отрицательном — «НЕКОРРЕКТНЫЕ ДАННЫЕ». Примеры вывода сообщений от сервера показаны на рисунке 4.12.1. Еще вариант применения технологии Ajax — добавление контента на страницу по мере ее прокрутки вниз. Подобные технологии используются, в частности, при разработке социальных сетей. Упрощенная программа бесконечной ленты, в которую информация добавляется по частям, есть в моей книге «JavaScript. Обработка событий на примерах». Посмотреть сценарий в действии можно на сайте поддержки книги: https://testjs.ru/p24.html. 102 Рис. 4.12.1. Варианты вывода предупреждений от сервера: исходный вид формы; сообщение в строке ниже формы; сообщение на специально оформленном слое; сообщение на кнопке отправки данных Несколько изменив ее код, покажу, как можно реализовать загрузку по частям. Пусть у нас в теле документа будет слой, который изначально содержит какое-то количество графических элементов и текста: <div id="pic"> ... </div> Зарегистрируем обработчик события прокрутки страницы: window.addEventListener("scroll", scro); Функция scro (файл 4.12_4.html в папке «Глава4» zip-архива): function scro() { if(window.pageYOffset+window.innerHeight>=document.body. clientHeight) { let re=new XMLHttpRequest(); re.open("GET", "ajax.php", true); re.send(); re.addEventListener("load", demo); } } 103 Сначала мы проверяем, не достиг ли ползунок конца документа: if(window.pageYOffset+window.innerHeight>=document.body. clientHeight) Если это случилось, формируем запрос, дожидаемся ответа и загружаем новые данные, полученные от серверной программы ajax.php: function demo() { document.getElementById("pic").insertAdjacentHTML("beforeend", this.responseText); } Как мы видим, информация добавляется на слой в самый конец перед закрывающим тегом </div>: insertAdjacentHTML("beforeend", this.responseText); Страница тем самым «удлинится», и появится пространство для дальнейшей прокрутки. Если вновь сместить ползунок в самый низ, опять запустится функция scro и загрузятся очередные данные. Таким образом и достигается эффект бесконечной ленты. Третий пример использования технологии Ajax — замена одних данных в текущем документе на другие. Вариант совсем простой: регистрируем обработчик события, в результате которого должна загружаться новая информация; по аналогии с предыдущими случаями пишем функцию, формирующую и отправляющую запрос на сервер — думаю, вы уже в состоянии написать ее самостоятельно; заменяем существующие данные новыми. Последнюю операцию можно выполнить так. Опять представим, что у нас есть слой с картинками и текстом: <div id="pic"> ... </div> Пишем несколько измененную версию функции demo (файл 4.12_5.html в папке «Глава4» zip-архива): function demo() { document.getElementById("pic").innerHTML=this.responseText; } Здесь существующая информация полностью заменяется на новую. 104 Наконец, завершая рассказ, хочу отметить, что с помощью технологии Ajax можно не только взаимодействовать с серверными программами, но и просто загружать файлы, например текстовые. Сделать это можно так (файл 4.12_6.html в папке «Глава4» zip-архива): re.open("GET", "text.txt", true); В этом случае мы обращаемся к файлу, содержащему простой текст или HTML-разметку, которая будет преобразована на странице в соответствующие элементы: document.getElementById("pic").insertAdjacentHTML("beforeend", this.responseText); или document.getElementById("pic").innerHTML=this.responseText; 4.13. Подсказки и проверка данных Как утверждает народная мудрость, никто не застрахован от ошибок. В том числе и посетители вашего сайта, заполняющие форму отправки сообщений. Часто казусы возникают, когда клиент не знает, в каком виде необходимо ввести информацию или каким пределом ограничен ее объем. Во избежание подобных ошибок дальновидные разработчики внедряют на страницы подсказки для клиентов. Рассмотрим один из вариантов таких подсказок. Напишем сценарий, проверяющий количество символов, введенных в текстовое поле, и сообщающий, сколько знаков еще можно добавить. Итак, есть текстовое поле: <textarea id="tx" maxlength="100"></textarea> А над ним табло для вывода сообщений: <div id="att">Можно ввести 100 знаков</div> Регистрируем обработчик события нажатия любой клавиши для ситуации, когда курсор установлен в текстовом поле: window.addEventListener("load", function() { document.getElementById("tx").addEventListener("keyup", func); }); Пишем функцию (файл 4.13_1.html в папке «Глава4» zip-архива): function func() { let t=100-document.getElementById("tx").value.length; 105 const a=document.getElementById("att"); a.innerHTML="Осталось: "+t; if(t<=20) a.style.color="#CC0000"; else a.style.color="#006600"; } Здесь предусмотрен не только подсчет оставшегося количества знаков, но и смена цвета сообщений, когда до конца остается менее 20 символов. Как такие подсказки выглядят на реальной странице, видно по рисунку 4.13.1. Рис. 4.13.1. Заполняя текстовое поле, клиент видит, сколько знаков еще можно ввести Если вам уже приходилось читать в книгах по web-программированию о работе с формами, вы могли заметить, что во всех случаях авторы обязательно заводят речь о проверке информации, поступающей от посетителей. Как и в случаях с подсказками, вновь констатируем факт: никто не застрахован от ошибок. Из-за спешки или невнимательности посетители могут отправить вам не те данные, что вы ожидаете. Или вообще случайно пропустить и оставить незаполненным какое-то поле. Добросовестным клиентам обязательно надо помочь. Для этого разработчики используют не только подсказки, но и проверку формы перед ее отправкой. Рассмотрим ситуацию с проверкой данных, введенных клиентом в поле, и выводом сообщений о допущенных нарушениях, если они есть. Допустим, в форме предлагается ввести адрес электронной почты: <form> Ваш e-mail: <input type="text" name="F1" id="F1" required><br> <input type="submit" value="Отправить"> </form> 106 Напишем сценарий, который станет проверять корректность e-mail. Самый короткий адрес электронной почты обязан содержать знак @ («собака» или коммерческое «эт»). Кроме того, должна присутствовать точка, разделяющая доменные имена первого и второго уровня. Она будет отстоять от первого символа адреса как минимум на пять позиций (два символа в начале + @ + два символа доменного имени второго уровня). Также адрес должен иметь длину не менее 8 символов: два в начале + @ + два символа доменного имени второго уровня + точка + два символа доменного имени первого уровня. Будем выполнять проверку исходя из перечисленных требований. Зарегистрируем обработчик для события изменения значения поля формы: window.addEventListener("load", function() { document.getElementById("F1").addEventListener("input", test); }); Теперь сама функция (файл 4.13_2.html в папке «Глава4» zip-архива): function test() { let na=document.forms[0].F1; if((na.value.length<8)||(na.value.indexOf("@")<2)|| (na.value.indexOf(".")<5)) na.setCustomValidity("В e-mail допущена ошибка!"); else na.setCustomValidity(""); } Проверяем введенную строку на предмет ее соответствия ранее перечисленным параметрам. Если допущена ошибка, выводим методом setCustomValidity сообщение о данном факте. На рисунке 4.13.2 показан случай с пропущенным знаком @. Рис. 4.13.2. Сообщение об ошибке в адресе электронной почты Завершая этот раздел, хочу добавить, что организовать подсказки и проверку данных в полях формы позволяет и ряд специальных методов, встроенных в современные браузеры. В последнем примере мы добавляли полю input атрибут required. Даже если бы на странице не было описанного выше сцена107 рия, мы все равно не смогли бы отправить форму, если поле адреса электронной почты осталось бы незаполненным. Напишем такую страницу (файл 4.13_3.html в папке «Глава4» zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Проверка</title> </head> <body> <form> Ваш e-mail: <input type="text" name="F1" required><br> <input type="submit" value="Отправить"> </form> </body> </html> Как видите, здесь «подключен» только атрибут required. Нажмем кнопку «Отправить», не заполняя формы. Отправка не состоится, а в браузерах Microsoft Edge, Google Chrome и Opera рядом с полем возникнет предупреждение (рис. 4.13.3) «Заполните это поле» («Пожалуйста, заполните это поле» в браузере Mozilla Firefox и «Вы пропустили это поле» в Яндекс.Браузере). Так работает встроенная в браузеры простейшая проверка. Кстати, она может быть гораздо сложнее — и опять без использования JavaScript. Рис. 4.13.3. Проверка без использования сценария на JavaScript Однако опытные программисты считают браузерную проверку не совсем исчерпывающей и советуют применять для этого сценарии на JavaScript. 4.14. Регулярные выражения В предыдущем разделе мы проверяли наличие в адресе электронной почты точки, знака @, а также положение этих элементов в строке и ее длину. Однако такая проверка не гарантирует, что e-mail будет введен правильно. Мы не проверили адрес на предмет запрещенных символов. Решить данную проблему нам поможет регулярное выражение. 108 Вы уже, наверное, знаете, что регулярное выражение — это некий шаблон, по которому можно выполнить поиск на наличие или, наоборот, отсутствие совпадающих фрагментов в небольшой строке или объемном тексте. Также с помощью регулярных выражений проверяют данные на их соответствие определенным требованиям. В общем, это то, что нам нужно для оценки корректности e-mail перед отправкой формы. Попробуем написать регулярное выражение для проверки адреса почты, зарегистрированной в зоне .ru. Вообще, корректный e-mail может содержать самые разнообразные символы. Но мы будем ориентироваться на требования, которые предъявляют к адресам российские почтовые сервисы. А они заметно снижают перечень таких символов. Например, в Яндекс.Почте установлены следующие требования: разрешены буквы латинского алфавита, цифры, точки, дефисы; адрес не должен начинаться с цифры, дефиса или точки; адрес не может заканчиваться точкой или дефисом. Есть правила и для доменных имен второго уровня: они могут содержать латинские буквы, цифры и дефис; имя может начинаться с цифры или буквы; имя не может завершаться дефисом. Итак, наше регулярное выражение должно отслеживать выполнение данных условий. Начнем по порядку. Адрес не должен начинаться с цифры, дефиса или точки. Этому требованию удовлетворяет вот такой фрагмент выражения: [a-z] То есть первый символ — только буква. Затем идет некоторое количество произвольных символов из тех, что разрешены — буквы, цифры, дефис, точка: [a-z\d-\.]+ Знак + означает, что символов может быть более одного. Наконец, завершающий символ — буква или цифра: [a-z\d] Дальше используем почтовый знак @. Теперь проверка доменного имени. Оно начинается с буквы или цифры: [a-z\d] Следом могут идти буквы, цифры и дефис: [a-z\d-]+ Завершающий символ — буква или цифра: [a-z\d] 109 Имя домена второго уровня отделяется точкой от имени домена первого уровня: \.ru Напомню, что регулярное выражение заключается в слеши, а для обозначения конца и начала ввода используются специальные знаки ^ и $: /^...$/i; Добавлю, что благодаря наличию символа i сценарий игнорирует регистр при сопоставлении шаблона с адресом, введенным в поле формы. У нас получилось вот такое регулярное выражение: /^[a-z][a-z\d-\.]+[a-z\d]@[a-z\d][a-z\d-]+[a-z\d]\.ru$/i; Заменим в функции test старую систему проверки на новую. Вот что у нас выйдет (файл 4.14_1.html в папке «Глава4» zip-архива): function test() { let na=document.forms[0].F1; let rv=/^[a-z][a-z\d-\.]+[a-z\d]@[a-z\d][a-z\d-]+[a-z\d]\.ru$/i; if(rv.test(na.value)==false) na.setCustomValidity("В e-mail допущена ошибка!"); else na.setCustomValidity(""); } Здесь метод test внутри одноименной функции выполняет сопоставление регулярного выражения с указанной строкой: rv.test(na.value)==false Результаты проверки адреса почты новым сценарием можно видеть на рисунке 4.14.1. Рис. 4.14.1. Сообщение об ошибке на основе проверки адреса электронной почты регулярным выражением 110 Хочу обратить внимание, что, хотя описанный выше сценарий довольно неплохо проверяет адреса электронной почты, но это все-таки упрощенный вариант. Сделан он таковым для «облегчения» примера. В идеале необходимо добавить проверку: длины адреса — а она, как вы знаете, ограничена; наличие таких нарушений, как два дефиса или две точки подряд (а также дефиса и точки рядом в любом порядке). Пойдем дальше. Применение регулярных выражений, конечно, не ограничено только проверкой почты. На самом деле вариантов использования регулярных выражений очень много. Рассмотрим еще один пример — подсвечивание ссылок. Допустим, на нашу страницу загружаются данные из файла, где в виде простого текста есть адрес ссылки, не «обрамленный» соответствующими тегами a. Наша задача «подсветить» ссылку, так чтобы она стала рабочей, ведущей на конкретную страницу некоего сайта. Задача эта не совсем простая — и вот по какой причине. Хорошо, если ссылка отделена пробелами от других слов или знаков загружаемого текста. Тогда «подсветить» ее довольно просто. Но она может быть расположена в конце предложения, допустим, вот так: «Вы были на сайте https://testjs.ru?». Или так: «Есть много ресурсов с примерами кода (один из них — сайт https://testjs.ru)». Более того, по ошибке ссылка может вообще не иметь пробелов между соседними словами и выглядеть так: «В тексте есть вот такаяhttps://testjs.ruссылка». Подобное нередко случается при наборе текста со смартфона. Как в таком случае отделить ссылку? Для этого существуют разные способы. Я хочу продемонстрировать вам довольно оригинальный вариант, в котором проверяется не сама ссылка, а знаки в начале и в конце нее. Сценарий такой проверки содержит всего одно короткое регулярное выражение (файл link.html zip-архива): /[а-яА-Я\(\),!;\.:\?]/ Начну с некоторых оговорок. Чтобы упростить код, в сценарии приняты некоторые ограничения: по умолчанию считается, что адрес ссылки — корректный; ссылка должна вести на домен в зоне .ru; ссылка должна начинаться с https://. Если не соблюсти эти ограничения, программа работать не будет. Но, как вы понимаете, наша задача рассмотреть общий принцип действия в аналогичной ситуации и показать еще один вариант применения регулярного выражения. Для наглядности чуть изменим условия и представим, что мы загружаем в браузер текст, введенный пользователем в форму. Пусть данный текст включает адрес ссылки без тегов. Что мы можем получить из формы? Текст со знаками препинания в конце ссылки. Текст, в котором по ошибке ссылка не отделена пробелами от соседних слов. Или текст, в котором ссылка выделена пробелами. 111 Программа начинает с того, что разбивает загруженный текст по пробелам на отдельные слова. Если в начале слова обнаружено объявление протокола https, а в конце символы .ru, программа считает, что это ссылка, и добавляет к ней: открывающий тег a с атрибутом href, значение которого — адрес вычисленной ссылки; закрывающий тег a. После чего текст выводится на страницу. Если имя протокола не отделено от предыдущего слова или знака пробелом, то скрипт отделяет ссылку, используя разбиение текста по https://: split('https://') Все, что «до», воспринимается сценарием, как обычный текст. Если после .ru следуют еще какие-то символы, то программа начинает искать конец ссылки. После имени домена первого уровня .ru может быть: слеш и адрес конкретной страницы; знаки препинания — скобка, точка, запятая, точка с запятой, двоеточие, многоточие, вопросительный и восклицательный знаки; слово из букв кириллицы, по ошибке не отделенное пробелом. Возможны также смешанные варианты, когда после .ru идет адрес страницы, а за ним знак препинания или слово на кириллице. Итак, представим ситуацию: после .ru следуют еще символы. Как мы уже узнали, в этом случае программа начинает искать конец ссылки. Здесь как раз и необходимо регулярное выражение /[а-яА-Я\(\),!;\.:\?]/ Последовательность действий такова: берется последний знак из слова и сравнивается с шаблоном. Если совпадений нет, то все, что находится до этого символа, включая его, является адресом ссылки. Если происходит совпадение, то выполняется сравнение второго знака с конца слова. Если второй знак не удовлетворяет условиям шаблона, то все, что находится до этого символа, включая его, является адресом ссылки. Если происходит совпадение, то выполняется сравнение третьего знака с конца слова и т. д. В каком случае поиск совпадений будет прекращен? Когда сценарий обнаружит в конце слова латинскую букву, цифру или слеш, т. е. те знаки, что отсутствуют в шаблоне. Оставшаяся часть слова, включая эту букву, цифру или слеш, и будет ссылкой. Дальше программа добавит к ней соответствующие теги a и атрибут href. Как и в ситуации с проверкой адреса электронной почты, обращаю ваше внимание, что данный пример не является на сто процентов действенным, ведь он не проверяет адрес на протокол http или адрес, в котором вообще не указан протокол. Кроме того, комбинация знаков после доменного имени первого уровня иногда бывает настолько причудливой, что данный скрипт может дать 112 ошибку. Однако в качестве заготовки для серьезной программы он вполне подходит. Кстати, если вы хотите расширить рамки действия сценария, то можете добавить в него массив существующих доменных имен первого уровня и сравнивать его элементы с доменным именем из ссылки. Поскольку сценарий довольно объемный, я не стал приводить его код. Вы можете посмотреть действующую программу здесь: https://testjs.ru/kpp /link.html. Зайдите на страницу. В исходном состоянии вы увидите текстовое поле с надписью, содержащей ссылку, «застрявшую» в словах без пробелов. Кликните мышью по текстовому полю — и увидите, что ссылка будет определена правильно (рис. 4.14.2). Чтобы посмотреть код сценария, щелкните правой кнопкой мыши на любом свободном участке страницы с примером и в открывшемся меню выберите пункт «Посмотреть исходный код» (Microsoft Edge), или «Просмотр кода страницы» (Google Chrome), или «Исходный код страницы» (Opera и Mozilla Firefox), или «Посмотреть код страницы» (Яндекс.Браузер). Рис. 4.14.2. Результат выполнения сценария «подсвечивания» ссылки 4.15. Обработка строк Раз уж мы заговорили о регулярных выражениях, с помощью которых проверяли текст и находили ссылки, имеет смысл вспомнить о методах, используемых для работы с текстом. А если быть точнее — со строковыми объектами. Методы, которые наиболее часто применяются для обработки строк, перечислены в таблице 4.15.1. Таблица 4.15.1 Методы обработки строк Метод «строка».charAt() «строка 1».concat("строка 2") Описание Возвращает символ с номером n из строки. Другой способ получения данного результата: «строка»[n] Объединяет две строки и возвращает новую строку. Другой способ для получения такого результата: «строка 1»+№строка 2» 113 Продолжение табл. 4.15.1 Метод Описание Определяет, находится ли одна строка внутри другой. «строка».includes() Возвращает true или false Возвращает индекс первого вхождения в строке «строка».indexOf() указанных символов или –1, если вхождений нет Возвращает индекс последнего вхождения в строке «строка».lastIndexOf() указанных символов или –1, если вхождений нет «строка».match() Выполняет сопоставление регулярного выражения строке Возвращает строку, состоящую из n повторений «строка».repeat(n) исходной строки В случае совпадения части строки с искомой подстрокой «строка».replace() заменяет участок совпадения новой подстрокой Выполняет поиск совпадения регулярного выражения «строка».search() со строкой «строка».slice() Извлекает часть строки и возвращает новую строку Разбивает строку на несколько строк с помощью «строка».split() заданного разделителя «строка».substr(n) Возвращает все символы из строки, начиная с позиции n Возвращает все символы из строки, начиная с позиции n «строка».substring(n, m) до m, но не включая символ из позиции m «строка».toLocaleLowerCase() Переводит символы в строке в верхний регистр «строка».toLocaleUpperCase() Переводит символы в строке в нижний регистр «строка».trim() Удаляет пробелы в начале и конце строки Кроме того, есть еще очень полезное свойство length, которое возвращает длину строки: "строка".length. Чтобы посмотреть использование этих методов и свойства length на практике, напишем вот такой сценарий (файл 4.15_1.html в папке «Глава4» zipархива): <script> console.log("кот".length); console.log("кот"[2]); console.log("кот".charAt(0)); console.log("кот".concat(" Васька")); console.log("кот"+" Васька"); console.log("кот".includes("от")); console.log("кот Васька".indexOf("а")); console.log("кот Васька".lastIndexOf("а")); console.log("кот Васька".match(/[a-z]/)); console.log("кот ".repeat(3)); console.log("кот Васька".replace("Васька", "Мурзик")); 114 console.log("кот Васька".search("ась")); console.log("кот Васька".slice(4, 7)); console.log("кот Васька".split(" ")); console.log("кот Васька".substr(4)); console.log("кот Васька".substring(2, 5)); console.log("кот Васька".toLocaleLowerCase()); console.log("кот Васька".toLocaleUpperCase()); console.log(" кот Васька ".trim()); </script> Запустим сценарий в браузере, после чего откроем пункт меню «Средства разработчика» (в некоторых браузерах «Инструменты разработчика»), щелкнем на вкладке «Консоль» (в некоторых браузерах «Console») и посмотрим, что у нас вышло (рис. 4.15.1). Как видите, применяя методы обработки строк, можно получить интересные и полезные результаты. Рис. 4.15.1. Результат работы сценария, демонстрирующего методы обработки строк 4.16. Комментарии В языке JavaScript существуют два способа размещать комментарии непосредственно в теле сценария. Однострочный комментарий выделяется двумя наклонными чертами — слешами: // Это комментарий 115 Например: function func() { // Объявляем счетчик кликов на изображении let d=0; ... } Многострочный обрамляется слешем со звездочкой в его начале и звездочкой со слешем в конце: /* Это комментарий, /* который состоит /* из нескольких строк */ Например: function func(t) { let m=0; /* Запускаем цикл из 4 проходов и проверяем /* переданное в функцию значение. Если оно /* меньше текущего значения d, /* увеличиваем счетчик m на единицу */ for(let d=0; d<5; d++) { if(t<d) { m++; } } ... } Как вы понимаете, комментарии размещают в коде программы, чтобы пояснить ее отдельные фрагменты. Также комментарии могут использоваться в качестве различных временных заметок, необходимых в процессе написания сценария. Такие заметки по завершении разработки обычно удаляют. Пояснение фрагментов кода целесообразно по двум причинам: 1) по прошествии времени разработчику иногда трудно вспомнить, что делают те или иные части сценария. Наличие комментариев упрощает этот процесс; 2) если вы работаете в IT-компании, необходимо, чтобы ваш код понимали не только вы, но и другой программист, которому, вполне возможно, придется использовать его в качестве составной части какого-либо проекта. Дам несколько советов по использованию комментариев. Не загромождайте код комментариями к тем частям, назначение которых понятно без лишних слов. 116 Например: // Регистрируем обработчик щелчка на изображении document.getElementById("im").addEventListener("click", func); Здесь комментарий явно лишний — любой программист, увидев данную строку, сразу поймет ее назначение. 2. Если код достаточно сложный, можно дать комментарии не только к отдельным фрагментам, но и ко всей программе в целом, поместив соответствующие разъяснения в самом начале сценария. 3. Старайтесь вставлять комментарии к блокам программы, а не к каждой ее строке. Пример неудачного расположения комментариев: // Объявляем функцию func function func(t) { // Регистрируем счетчик кликов let m=0; // Запускаем цикл из 4 проходов for(let d=0; d<5; d++) { // Проверяем переданное в функцию значение if(t<d) { // Если переданное значение меньше d, увеличиваем счетчик на единицу m++; } } } В этом фрагменте комментируется каждое действие, в результате чего строки кода «теряются» среди строк пояснений. А это, в свою очередь, усложняет восприятие программы. Лучше сгруппировать все пояснения в многострочный комментарий и расположить его перед объявлением функции: /* Объявляем функцию func и регистрируем счетчик кликов m. /* Запускаем цикл из 4 проходов и проверяем переданное /* в функцию значение. Если оно меньше текущего значения d, /* увеличиваем счетчик m на единицу */ function func(t) { let m=0; for(let d=0; d<5; d++) { if(t<d) { m++; } } } 117 Powered by TCPDF (www.tcpdf.org) При таком «построении» и тело функции «как на ладони», и сами комментарии вполне понятны. Другой вариант — добавлять комментарии в конце строк кода: function func(t) // Объявляем функцию func { let m=0; // Регистрируем счетчик кликов for(let d=0; d<5; d++) // Запускаем цикл из 4 проходов { if(t<d) // Проверяем переданное в функцию значение { // Если переданное значение меньше d, m++; // увеличиваем счетчик кликов на единицу } } } Получилось очень удобно: и функция выглядит понятно, и комментарии привязаны к нужным строкам. 118 5. Тестирование сценариев Допустим, вы зашли на какой-то сайт. Он вам нравится — красиво оформлен, интересные эффекты, все работает. Однако это только внешняя сторона. К своему удивлению, проведя несложный «аудит» этого сайта с применением специальных, но очень простых методов, вы можете обнаружить, что за красивым фасадом скрываются многочисленные ошибки в разметке, стилях и коде JavaScript. Порой количество ошибок и недочетов переваливает за 100 и даже за 200 только на одной странице. А суммарно они иногда исчисляются тысячами. Причем такие показатели обнаруживаются даже у корпоративных ресурсов студий web-дизайна, которые рекламируют услуги по созданию сайтов (в разделе 5.2 этой главы я продемонстрирую один пример из великого множества подобных нелепиц, см. рис. 5.2.5). Сайты с ошибками в коде страниц — это бракованный товар! «Грязный» код ухудшает ранжирование сайтов в поисковых системах. А это — потерянные посетители, клиенты, покупатели, заказчики. Сомнительные практики Очень придирчиво относитесь к выбору популярных платформ, движков и CMS для создания сайтов. Да, такие системы очень облегчают труд дизайнера и верстальщика. Но почти любая из них содержит в своем «ядре» многочисленные ошибки, которые потом перекочевывают в ваши разработки. Если вам необходимо выполнить крупный проект — смиритесь с неизбежным: без платформы, движка или CMS, скорее всего, не обойтись. Если проект небольшой и не нуждается в системе управления контентом, попробуйте самостоятельно написать его «от корки до корки». Так вы можете создать ресурс с абсолютно чистым кодом. Есть еще один вариант действий: выберите CMS, которая вам нравится больше всего, скачайте дистрибутив на свой компьютер и попробуйте исправить его исходный код. Работа большая и трудоемкая, зато у вас появится исправленный экземпляр CMS, на копиях которого вы в дальнейшем станете делать «чистые» сайты. 119 Надеюсь, вы не хотите оказаться в рядах горе-программистов? Тогда проверяйте разметку, стили и сценарии ваших проектов самым тщательным образом. Вот перечень самых необходимых действий: оптимизация кода; проверка кода с помощью валидаторов и устранение ошибок; проверка работоспособности сценариев в различных браузерах, в том числе достижение идентичности результатов работы сценариев в различных браузерах; устранение логических ошибок (если они обнаружены в процессе тестирования) в скриптах. Рассмотрим этот перечень подробнее. 5.1. Оптимизация кода Оптимальной можно считать программу, которая: максимально лаконична; занимает минимально возможное дисковое пространство; максимально быстро работает. Современные посетители сайтов любят, чтобы все делалось как можно быстрее, поэтому оптимизация кода играет очень важную роль. Долго грузится страница? Самые нетерпеливые разочарованно покинут ваш ресурс. И наоборот, быстрая загрузка уже на начальном этапе создаст у клиента приятное впечатление. На чем можно сэкономить при написании кода? На очень многом. Покажу вам это на ряде примеров. Допустим, у вас есть переменная a и вам надо увеличить ее значение на единицу. Можно записать данную операцию так: a=a+1; А можно иначе: a++; Понятно, что вторая запись более лаконична — по сравнению с первой мы сэкономили 2 символа. Теперь представим, что у вас есть цикл со счетчиком. Его можно записать так: let i; for(i=0; i<5; i++) { ... } 120 То есть сначала объявляем переменную i, а потом в описании условий цикла присваиваем ей начальное значение. Ясно, что следующий вариант данного цикла более краток: for(let i=0; i<5; i++) { ... } И опять экономия составила два символа. Кстати, обратите внимание, что правильный выбор оператора цикла между следующими вариантами — for, while, do while — также может сократить ваш код. Используйте досрочный выход из цикла посредством оператора break, если вы уже получили требуемый результат, но предельное условие для цикла еще не достигнуто. Пойдем дальше. Помните, в главе 4 в разделе 4.2 у нас были примеры одновременной регистрации обработчиков событий? Вернемся к этим примерам. Допустим, вам надо зарегистрировать три обработчика для одного события: document.getElementById("im").addEventListener("click", func); document.getElementById("im").addEventListener("click", doc); document.getElementById("im").addEventListener("click", res); Это слишком «тяжелая» запись. Ее можно сделать более «легкой»: const v=document.getElementById("im"); v.addEventListener("click", func); v.addEventListener("click", doc); v.addEventListener("click", res); А в некоторых случаях можно сделать еще проще: document.getElementById("im").addEventListener("click", function() { func(); doc(); res(); }); Раз уж мы упомянули обработчики, вот еще пример сокращения кода: при регистрации обработчиков событий уровня окна можно не указывать объект window. Вместо window.addEventListener("load", func); window.addEventListener("scroll", scro); можно написать addEventListener("load", func); addEventListener("scroll", scro); 121 Вспомним про еще один способ оптимизации — путем уменьшения количества переменных. В разделе 4.11 у нас был пример с функцией, которая содержала два цикла. Вместо двух переменных, являющихся счетчиками — каждая своего цикла, — мы использовали только одну переменную, тем самым экономя на памяти: function func() { ... let d; for(d=0; d<5; d++) { ... } ... for(d=0; d<3; d++) { ... } ... } Здесь у нас после завершения первого цикла переменная d обнуляется, после чего приступает к работе во втором цикле. Уменьшить объем кода можно, используя сокращенные формы условных выражений. Так, если у вас после проверки истинности или ложности условия должна выполняться только одна инструкция, это лучше оформить следующим образом. Вместо if(a<5) { b=0; } написать if(a<5) b=0; Вместо if(a<5) { b=0; } else { b=1; } 122 использовать укороченную запись: if(a<5) b=0; else b=1; Или вообще для проверки таких условий применять тернарный оператор: let b=a<5?0:1; Как вы помните, сократить можно и оформление оператора цикла, если внутри него выполняется только одна инструкция. Вместо for(let c=0; c<2; c++) { alert(c); } можно написать for(let c=0; c<2; c++) alert(c); Экономить удастся даже на способах «внедрения» результатов работы сценария на HTML-страницы. Конкретный пример. Допустим, у нас есть страница, на которую сразу после загрузки выводятся квадраты чисел от 1 до 5. Напишем ее (файл 5.1_1.html в папке «Глава5» zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Квадраты чисел</title> <script> window.addEventListener("load", function() { let t="<table><tr><td>Число</td><td>Его квадрат</td></tr>"; for(let i=1; i<6; i++) t+="<tr><td>"+i+"</td><td>"+i**2+"</td></tr>"; t+="</table>"; document.getElementById("di").insertAdjacentHTML("beforeend", t); }); </script> </head> <body> <div id="di">Квадраты чисел</div> </body> </html> И сравним с более «легкой» версией этой же программы (файл 5.1_2.html в папке «Глава5» zip-архива): 123 <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Квадраты чисел</title> <script> window.addEventListener("load", function() { let t="<table><tr><td>Число</td><td>Его квадрат</td></tr>"; for(let i=1; i<6; i++) t+="<tr><td>"+i+"</td><td>"+i**2+"</td></tr>"; document.getElementById("di").innerHTML="Квадраты чисел"+t+ "</table>"; }); </script> </head> <body> <div id="di"></div> </body> </html> В чем разница между версиями? В первом случае текст «Квадраты чисел» уже размещен на странице. Поэтому добавлять результаты расчетов нам приходится в самый конец контейнера div методом insertAdjacentHTML, который выглядит несколько громоздко. К тому же мы сначала добавляли к переменной t завершающий тег таблицы t+="</table>"; и только потом выводили на страницу готовый фрагмент разметки. В результате у нас получился файл размером 501 байт. Во втором случае контейнер div мы изначально оставили пустым, что позволило использовать для вывода результатов более компактное свойство innerHTML. Кроме того, мы окончательно сформировали необходимый фрагмент разметки прямо во время его вывода на страницу: document.getElementById("di").innerHTML="Квадраты чисел"+t+ "</table>"; В результате второй вариант оказался легче первого на 25 байт. В ряде случаев для «облегчения» кода и ускорения работы программы можно использовать функции-стрелки, с которыми мы познакомились в главе 4, разделе 4.11. Ускоряет «запуск» сайта в браузере пользователя и такая вещь, как загрузка контента по частям. Если у вас «длинная» страница с большим объемом текста и рисунков, разбейте данные на части, которые будут подгружаться по мере прокрутки документа вниз. Для этого удобно воспользоваться технологией Ajax, о которой говорилось в главе 4, разделе 4.12. Примеры таких программ вы можете посмотреть в моих уже упоминавшихся книгах «JavaScript. Готовые программы» и «JavaScript. Обработка событий на примерах». 124 5.2. Валидаторы Валидатор — это специальная программа для проверки кода, написанного на одном из языков, предназначенном для создания web-проектов. Задача такой программы — найти ошибки, если они есть, и выдать их перечень разработчику, или сообщить, что код безупречный, если ошибок нет. Можно сказать и по-другому: назначение того или иного валидатора — проверять код на соответствие стандартам языка программирования. Стандарты HTML и CSS разрабатывает Консорциум Всемирной Паутины. Он же предоставляет и валидаторы для проверки разметки страниц и написания таблиц стилей. Их адреса в сети: валидатор HTML5 — https://validator.w3.org/ (рис. 5.2.1); валидатор CSS3 — http://jigsaw.w3.org/css-validator/ (рис. 5.2.2). Данные валидаторы очень удобны. Они позволяют проверять разметку и таблицы стилей: страниц, уже загруженных в сеть; файлов, загруженных в валидатор с вашего компьютера; в коде, скопированном из вашего файла и помещенном в специальное текстовое поле. Например, после обработки кода разметки все ошибки и предупреждения с комментариями на английском языке появятся на странице HTML-валидатора в ее нижней части. При этом ошибки будут сопровождаться надписью Error на розовым фоне, а предупреждения — надписью Warning на желтом фоне. Рис. 5.2.1. Валидатор HTML5 125 Рис. 5.2.2. Валидатор CSS3 Рис. 5.2.3. Чистый код Консорциум Всемирной Паутины предлагает отметить своеобразным «знаком качества» 126 Например, строка кода <script type="application/javascript"> получит предупреждение, так как по современным стандартам указывать тип скрипта не надо, но в то же время данная ошибка некритична, так как не мешает нормальному отображению страницы. Зато следующая строка <img src="img/ im0.jpg"> будет отмечена надписью Error, так как у рисунка пропущен обязательный, согласно стандартам HTML5, атрибут alt. У HTML-валидатора есть еще одна полезная функция: по завершении проверки он показывает, сколько времени было потрачено на загрузку вашей страницы. Если ошибок нет, валидаторы сообщат вам об этом. Любопытно заметить, что о безупречности HTML-кода вы получите простое текстовое подтверждение. А вот качественную таблицу стилей Консорциум Всемирной Паутины предлагает отметить специальным знаком (рис. 5.2.3) — что-то вроде знака качества, который вам будет предложено разместить на странице сайта (по крайней мере, так было на момент написания книги). Что касается валидатора JavaScript, то здесь ситуация несколько иная. Таких валидаторов много. Я опробовал несколько и выбрал из них вариант, в котором реализован самый строгий подход к оценке кода. Его адрес: валидатор JavaScript — https://beautifytools.com/javascript-validator.php (рис. 5.2.4). Рис. 5.2.4. Валидатор JavaScript Он позволяет вставить в специальное текстовое поле ваш код, после чего сразу, без нажатия каких-либо кнопок, начинается проверка скрипта. Единственное неудобство этого валидатора, обнаруженное мною на момент написания книги, — сообщения об ошибках, предупреждения и рекомендации он выдает в общем списке, не выделяя пункты этого списка по степени важности. Особенность всех упомянутых валидаторов в том, что они находят все ошибки в разметке, в таблицах стилей, в коде программ, в том числе пропущенные символы, опечатки (но не в обычном тексте), необъявленные перемен127 ные и т. д. Понятно, что задача хорошего программиста — исправить все ошибки и получить идеально чистый и правильный код. Вообще код, полностью отвечающий всем стандартам — очень большая редкость на просторах Интернета. Даже такие монстры, как Яндекс, Google и YouTube, имеют множество ошибок в своих страницах. Хотите убедиться — проверьте. Рис. 5.2.5. Пример некачественной разметки 128 Мне осталось, как и обещал, привести случай некачественной разметки. Я проверил код случайно выбранного сайта первой попавшейся мне студии web-дизайна (рис. 5.2.5). Как видите, валидатор Консорциума Всемирной Паутины обнаружил на одной странице ровно 100 нарушений стандартов HTML5. 5.3. Браузеры Один из важнейших этапов тестирования сценариев — проверка их работоспособности в разных web-обозревателях. Написав сценарий и убедившись, что он корректно выполняется в вашем любимом браузере, не останавливайтесь на достигнутом. Ведь может оказаться, что в другом браузере программа будет работать совсем не так, как вы ожидаете. Чем в большем количестве web-обозревателей вы проведете испытания, тем лучше для вашего проекта. Это гарантирует, что все посетители вашего сайта увидят на его страницах именно то, что вы хотели продемонстрировать. На мой взгляд, обязательный набор разработчика должен включать минимум 5 браузеров. Если вы решили серьезно заниматься программированием на JavaScript, советую установить на свой компьютер эти браузеры. Расскажу о них по порядку. 1. Браузер Microsoft Edge. Он входит в состав операционной системы Windows 10, поэтому не нуждается в скачивании и установке. Многие опытные программисты не любят данный браузер и игнорируют его при проверке сценариев. Я считаю, что это серьезная ошибка. Дело в том, что Microsoft Edge является браузером по умолчанию в операционной системе Windows 10. Многие пользователи, не такие продвинутые, как программисты, просто используют то, что у них изначально установлено на ПК. Следовательно, большой процент посетителей Интернета прибегают к услугам Microsoft Edge. 2. Google Chrome. На мой субъективный взгляд — лучший браузер. И, судя по некоторым опросам, наиболее популярный. Он моложе некоторых своих конкурентов, но снабжен самыми передовыми решениями и очень быстро развивается. И это неудивительно, ведь за ним стоит такой гигант, как компания Google. Скачать ПО можно здесь: https://www.google.ru/chrome/. 3. Яндекс.Браузер. После его создания компания Яндекс вложила заметные средства в рекламу и популяризацию своей разработки. Поэтому данный браузер стоит у многих владельцев компьютеров с ОС Windows. Этих людей обязательно надо учитывать. Скачать дистрибутив можно здесь: https://browser. yandex.ru/. 4. Opera — один из старейших браузеров, существовавших еще во времена борьбы за лидерство между Netscape Navigator и Internet Explorer. В России у него много поклонников. Недаром среди российских пользователей Интернета показатель его популярности в несколько раз выше, чем общемировой уровень. Кстати, браузер Opera был одним из первых, кто начал поддерживать таблицы стилей. Скачать программное обеспечение можно здесь: https://www.opera. com/ru. 129 5. Mozilla Firefox. В этом браузере сценарии необходимо проверять в обязательном порядке. У него достаточно высокий уровень популярности. При этом есть ряд отличий в обработке кода по сравнению с четырьмя перечисленными выше браузерами. Что-то Mozilla Firefox обрабатывает аналогично остальным браузерам, а что-то по-своему. Во всяком случае, я неоднократно сталкивался с ситуациями, когда код, отлично работавший в других браузерах, начинал «капризничать» в Mozilla Firefox. Скачать браузер можно здесь: https://www.mozilla.org/ru/firefox/. Неплохо установить на свой смартфон мобильные версии перечисленных браузеров. Тогда вы после размещения сайта на удаленном хостинге сможете проверить, как работают ваши сценарии на мобильных устройствах. И уж совсем идеальной можно считать проверку, которую, кроме всего прочего, удалось провести на устройствах Apple с операционными системами macOS и iOS и браузером Safari. Думаю, что цель таких масштабных испытаний с использованием группы разных браузеров понятна: необходимо добиться совершенно одинаковой работы ваших программ во всех браузерах. Только получив этот результат, можно считать свои программы вполне качественными. 5.4. Логические ошибки Начну с общих рассуждений. Позволяя клиенту выполнять какие-то действия на сайте, обязательно проверяйте, не содержит ли его функционал не предусмотренных вами возможностей. Объясню ситуацию на примере из моей практики. Однажды я запускал интернет-портал, в работе которого могли принимать участие зарегистрированные пользователи. Они имели возможность оставлять на своих страницах разные сообщения, а также редактировать последнее из них. Для этого предназначались две кнопки: «Добавить сообщение» и «Редактировать последнее сообщение». По моим представлениям, каждый человек должен был интуитивно понимать, что сначала надо что-то написать, а потом уже редактировать написанное. Поэтому обе кнопки изначально присутствовали на странице. Такое решение оказалось в корне неверным. В качестве первых участников портала я пригласил нескольких своих знакомых. И тут меня ожидал сюрприз. Двое из них начали с того, что попытались отправить текст через форму «Редактировать последнее сообщение», что привело к возникновению ошибок в работе портала. Пришлось менять подход — кнопка редактирования стала появляться только после написания первого поста. Этот опыт научил меня всегда придерживаться принципа: если посетитель может что-то делать на сайте, необходимо так направлять его действия, чтобы он двигался только одним, единственно верным путем. Мы рассмотрели пример ошибки при создании интерфейса. Впрочем, главная тема этого раздела — логические ошибки в коде сценариев. Они возникают, когда вы упускаете какие-то нюансы или неверно применяете математические алгоритмы. Казалось бы, проверка в валидаторах убеждает нас в полном 130 соответствии кода стандартам. Тем не менее наличие такой ошибки обнаруживается, когда ваш сценарий начинает работать не так, как вы ожидали. При этом очень часто бывает, что вычислить логическую ошибку совсем непросто. Я расскажу вам о некоторых элементарных приемах поиска, обнаружения и исправления ошибок. Конечно, для проверки кода лучше всего использовать специальные инструменты разработчика, встроенные во многие браузеры. Однако тема эта достаточно большая и сложная, формат данной книги не позволяет досконально рассказать о всех возможностях таких инструментов. Поэтому мы рассмотрим более простые способы. Способ первый. В те блоки программы, в которых логика работы нарушается, поочередно добавляйте в разные точки вызов метода alert(). Например, у вас есть «сомнительный» фрагмент: ... let c=a+b; let t=v/d; if(c<t/2) { n++; document.getElementById("pict").style.left=n+"px"; } else { n=0; c=document.getElementById("img").offsetLeft; } ... Вставьте в разные точки этого блока вызов alert(), например так: ... let c=a+b; alert(c); let t=v/d; ... Потом так: ... let t=v/d; alert(t); if(c<t/2) ... А потом вот так: ... if(c<t/2) { n++; alert(n); 131 document.getElementById("pict").style.left=n+"px"; } ... Или сразу во все три точки. Таким образом, вы последовательно увидите, как меняются ваши переменные, и обнаружите, где происходит отклонение от заданной линии поведения скрипта. Второй способ предпочтительнее в ситуации с быстро меняющимися параметрами, например при перемещении указателя мыши по странице. Чтобы отследить поток значений, внедрите в разметку страницы элемент визуального отображения этих значений. Например, так: <div id= "test "><div> Найдите фрагмент, вызывающий сомнения, например: ... function coor() { let h=event.pageX; let v=event.pageY; let sh=document.getElementById("bat").offsetLeft; let sv=document.getElementById("bat").offsetTop; dh=h-sh; dv=v+sv; } ... Добавьте в код сценария команду выведения данных: ... dh=h-sh; dv=v+sv; document.getElementById("test").innerHTML=dh+" "+dv; } ... Анализируя показания координат, вы сразу обнаружите, что переменная dv растет вместо того, чтобы оставаться постоянной. А значит, в выражении dv=v+sv; знак плюс нужно поменять на минус. Чтобы приблизить эти рассуждения к реалиям, рассмотрим возникновение ошибки и способ ее поиска на конкретном примере. Уже неоднократно упоминавшаяся книга «JavaScript. Обработка событий на примерах» имеет сайт поддержки, на котором представлены различные сценарии. В том числе, ресурс содержит две страницы, не упоминаемые в книге. Страницы эти созданы специально для иллюстрации рассуждений данного раздела. 132 Итак, зайдем на первую из них: https://testjs.ru/e_p11.html. После загрузки документа мы видим на странице только рамку. Пока указатель мыши не попадает внутрь рамки, ничего не происходит. Теперь медленно проведите курсором внутри рамки слева направо. Вы обнаружите, что на белом фоне плавно возникнет фотография старинного автомобиля (рис. 5.4.1). Рис. 5.4.1. Пример страницы с логической ошибкой в сценарии Рис. 5.4.2. Чтобы обнаружить ошибку, проведите по странице указателем мыши так, как показано на рисунке 133 Проведите курсором в обратном направлении — справа налево. Фотография также плавно «растворится». Наилучший эффект достигается именно при медленном движении мыши. Если быстро провести указателем по рабочей области, фотография проявится или растворится только частично. То же произойдет, если указатель начал двигаться не от боковых сторон рамок, а, например, от середины изображения. Не правда ли, интересный пример? Но вот беда: несмотря на кажущееся благополучие в сценарии допущена логическая ошибка (естественно, сделано это умышленно). Причем ошибку можно обнаружить не сразу. В чем же она? Давайте еще поэкспериментируем. Проведите указателем мыши по рамке так, как это показано на рисунке 5.4.2. Сначала слева направо по периметру рамки, а затем ниже рисунка — когда вы возвращаете указатель справа налево. Проделайте так трижды, а на четвертый раз медленно возвращайте указатель мыши теперь уже по рисунку. Вы думаете, фото автомобиля плавно растворится? А вот и нет. Снимок по-прежнему будет ярким и сочным. В чем же дело? Посмотрим код страницы: <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Событие mousemove</title> <style> #pic {border: 1px solid #000000; width: 500px;} #car {opacity: 0;} </style> <script> window.addEventListener("load", function() { document.getElementById("car").addEventListener("mousemove", opa); }); let d=0; let i=0; function opa() { let h=event.pageX; if(h-d>3) { i+=0.01; document.getElementById("car").style.opacity=i; d=h; } if(d-h>3) { i-=0.01; document.getElementById("car").style.opacity=i; d=h; } } 134 Powered by TCPDF (www.tcpdf.org) </script> </head> <body> <div id="pic"><img src="pict/oldcar.jpg" id="car" alt="Фото"> </div> </body> </html> Как работает программа? В документе есть слой с фотографией автомобиля: <div id="pic"><img src="pict/oldcar.jpg" id="car" alt="Фото"> </div> В исходном состоянии автомобиль не виден: #car {opacity: 0;} В теле сценария у нас объявлены две переменные: let d=0; let i=0; Переменная d предназначена для хранения предыдущей по отношению к текущей координаты мыши по оси X. Переменная i — счетчик уровня непрозрачности. Как только событие mousemove произойдет над рисунком (т. е. указатель мыши начнет движение над рисунком), будет выполнен запуск обработчика. Вот его код: function opa() { let h=event.pageX; if(h-d>3) { i+=0.01; document.getElementById("car").style.opacity=i; d=h; } if(d-h>3) { i-=0.01; document.getElementById("car").style.opacity=i; d=h; } } 135 Давайте считать, что сейчас курсор двигается слева направо. Первым делом проверяется условие if(h-d>3). В начальный момент d имеет значение 0, поэтому разность между h и d удовлетворяет условию. Таким образом, при попадании указателя мыши на фото будут выполняться инструкции первого блока if: i+=0.01; document.getElementById("car").style.opacity=i; d=h; В первой строке счетчик увеличивается на 0.01. Во второй строке его значение присваивается свойству непрозрачности изображения. В третьей выполняется операция присвоения текущей координаты мыши переменной d. Смотрим, что произойдет дальше. Курсор мыши продолжает движение вправо, а значит, вторично запускается функция opa. Опять выполняется проверка условия if(h-d>3). Как видите, теперь сравнивается новая координата указателя со старой, полученной на предыдущем шаге и помещенной в переменную d. Если условие истинно, показатель непрозрачности опять увеличится. Продолжаем вести мышь слева направо — значение переменной i все увеличивается и увеличивается. В какой-то момент уровень непрозрачности достигнет 1 и фотография станет видна полностью. Теперь разберемся, что будет происходить во время движения указателя мыши по картинке в обратном направлении. Как раз вторая часть функции и предназначена для обработки таких действий посетителя: if(d-h>3) { i-=0.01; document.getElementById("car").style.opacity=i; d=h; } В данном случае проверяется обратное условие: if(d-h>3) То есть если предыдущая координата мыши больше текущей, значит, указатель двигается в обратном направлении, и показатель непрозрачности уменьшается на 0.01: i-=0.01; document.getElementById("car").style.opacity=i; d=h; По достижении переменной i нулевой «отметки» рисунок полностью исчезает. Вроде бы все правильно, как в первом эксперименте, но результат нашего второго эксперимента неожиданный. 136 Чтобы разобраться в проблеме, давайте используем метод поиска логических ошибок, о котором говорилось выше. Так как за показатель непрозрачности отвечает переменная i, проверим, что с ней происходит «за кадром». Лучше всего внедрить в страницу элемент визуального отображения значений этой переменной. Например, вот так: <div id="pic"><img src="pict/oldcar.jpg" id="car" alt="Фото"> </div> <div id="ind">0</div> Теперь у нас под фотографией есть слой, в котором будут показываться значения переменной i. Станем выводить ее показатели следующим образом: function opa() { let h=event.pageX; document.getElementById("ind").innerHTML=i; ... Рис. 5.4.3. Данная версия сценария позволила нам обнаружить ошибку — неконтролируемый рост значений переменной i Для демонстрации работы новой версии программы у нас есть другая страница: https://testjs.ru/i_p11.html. Повторим теперь на ней ход второго эксперимента, проводя указателем мыши по картинке слева направо и под картинкой — в обратном направлении. И мы тут же обнаружим, что, достигнув значения 1, программа не останавливается и переменная i продолжает расти (рис. 5.4.3). У возможных значений этой переменной вообще нет ограничений. Вот в чем ошибка! Проведя 4 раза указателем мыши по рисунку, мы подняли значение i выше предельного значения для показателя непрозрачности. В результате, 137 после того, как мы один раз провели мышью в обратном направлении, показатель непрозрачности по-прежнему остался выше 1 и картинка не растворилась. То есть мы ошиблись с логикой работы программы, не учтя, что посетитель может повести себя на странице не так, как мы от него ожидаем. Как исправить недочет? Очень просто: надо ограничить рост и снижение значений переменной i. Ее показания при любом развитии событий не должны быть выше 1 и ниже 0. На основе полученных данных внесем изменения в сценарий: function opa() { let h=event.pageX; if(h-d>3) { if(i<=1) { i+=0.01; document.getElementById("car").style.opacity=i; d=h; } else { i=1; } } if(d-h>3) { if(i>=0) { i-=0.01; document.getElementById("car").style.opacity=i; d=h; } else { i=0; d=0; } } } Здесь мы ввели проверку двух дополнительных условий: if(i<=1) и if(i>=0) Если они нарушаются, то выполняется такой блок инструкций: else { i=1; } 138 Или такой: else { i=0; d=0; } Теперь программа будет работать как надо. Ее правильную версию вы можете увидеть в действии на странице https://testjs.ru/p11.html. Надеюсь, рассмотрение этого случая полезно — как пример алгоритма поиска логических ошибок в программах. Наконец, еще один прием, который может вам пригодиться при отладке программ. Если какой-либо фрагмент сценария или разметки вызывает у вас сомнения, можно временно «отключить» его функционирование. Допустим, на странице необходимо «изъять» некий элемент. Это можно сделать, применив дескриптор комментария HTML: <!-- ... --> Вот конкретный пример временного удаления фотографии из разметки: <!-- <img src="im1.png" id="im1"> --> Отключить часть сценария можно, поставив перед необходимым фрагментом символы комментариев, о которых мы говорили в разделе 4.16. Вот так: // document.getElementById("im1").addEventListener("click", func); Или так: /* document.getElementById("im1").addEventListener("click", func); */ Естественно, что первый вариант комментирования строки в сценарии более рационален, так как символы // добавляются и удаляются быстрее, чем во втором варианте. Если временно требуется отключить несколько элементов разметки, то вновь используйте дескриптор комментария HTML: <!-- <img src="im1.png" id="im1"> <img src="im2.png" id="im2"> <img src="im3.png" id="im3"> --> Отключить несколько строк в сценарии можно теми же способами, что и в случае с одной строкой. Так // document.getElementById("im1").addEventListener("click", func); // document.getElementById("im2").addEventListener("click", func); // document.getElementById("im3").addEventListener("click", func); 139 и так /* document.getElementById("im1").addEventListener("click", func); /* document.getElementById("im2").addEventListener("click", func); /* document.getElementById("im3").addEventListener("click", /* func); */ В этом случае более рационален второй вариант комментирования строк в сценарии, так как символы /* и */ добавляются и удаляются быстрее, чем в первом варианте. 5.5. Кстати Раз уж мы в предыдущем разделе коснулись примера со сценарием управления непрозрачностью фотографии, давайте посмотрим не только на то, как четко отработана его логика. Проверим, какие результаты покажет код программы и разметка страницы при тестировании в соответствующих валидаторах. Приступим. Откройте в своем браузере страницу https://testjs.ru/p11.html. Щелкните на пустой части документа правой кнопкой мыши и выберите пункт «Посмотреть исходный код» (Microsoft Edge), или «Просмотр кода страницы» (Google Chrome), или «Исходный код страницы» (Opera и Mozilla Firefox), или «Посмотреть код страницы» (Яндекс.Браузер). В открывшейся вкладке с кодом страницы скопируйте содержимое сценария между тегами <script> </script> (но не копируя сами теги). Запустите JavaScript-валидатор: https://beautifytools.com/javascriptvalidator.php. В текстовое поле вставьте скопированный код. Кнопку «Validate Code» можно не нажимать — программа начнет анализ кода сразу после его вставки. Справа в поле для вывода результатов анализа вы увидите надпись «No syntax errors!» (рис. 5.5.1), что означает отсутствие ошибок. Как видите, в предыдущем разделе мы изучали вполне чистый и качественный сценарий. Теперь запустите HTML-валидатор: https://validator.w3.org/. Скопируйте адрес проверяемой страницы https://testjs.ru/p11.html и вставьте его в текстовое поле «Address:». Нажмите кнопку «Check» и программа начнет анализ разметки. Как только он завершится, откроется окно с извещением о результате (рис. 5.5.2). Как видите, в разметке тоже нет ни единого нарушения — об этом нам сообщает строка «Document checking completed. No errors or warnings to show». Проделаем аналогичные манипуляции для проверки качества заполнения таблицы стилей в странице с примером. Запустите CSS-валидатор: http:// jigsaw.w3.org/ css-validator/. Скопируйте адрес проверяемой страницы https:// testjs.ru/p11.html и вставьте его в текстовое поле «Адрес». Нажмите кнопку «Проверить». По завершении процесса откроется новое окно с результатом анализа (рис. 5.5.3). Как видите, и в таблице стилей нет нарушений — об этом нам сообщает строка «Поздравляем! Ошибок не обнаружено». 140 Рис. 5.5.1. Результат проверки сценария, «проявляющего» фотографию Рис. 5.5.2. Результат проверки разметки страницы 141 Рис. 5.5.3. Результат проверки таблицы стилей Что следует из результатов наших испытаний? А то, что я как автор не только призываю вас писать «чистый» код, но и сам строго придерживаюсь всех изложенных в этой книге правил. Кроме того, анализируя данную страницу, мы научились пользоваться валидаторами. Это тоже полезно. 142 6. Примеры сценариев Теория, конечно, хорошо, но практика лучше. Именно в процессе написания реальных сценариев программист по-настоящему осваивает JavaScript. Будем придерживаться такого подхода и мы. Рассмотрим изложенные в предыдущих главах принципы на примерах создания нескольких сценариев, предназначенных для работы с изображениями. Но перед тем, как приступить к делу, несколько слов о программах, с которыми вам предстоит познакомиться. Во-первых, они максимально оптимизированы согласно рекомендациям раздела 5.1. главы 5. Я постарался сделать код как можно «легче». Во-вторых, они проверены во всех валидаторах, упомянутых в разделе 5.2. Никаких ошибок в коде нет. В-третьих, программы испытаны во всех браузерах, упоминавшихся в разделе 5.3. Везде сценарии работают совершенно одинаково. Вы можете не только видеть примеры в действии, но также изучить их «начинку». Как посмотреть код страницы вы уже знаете. Более того, вы можете скачать zip-архив со всеми примерами и графическими файлами. Архив находится по адресу https://testjs.ru/kpp/kpp.zip. Наконец, последний комментарий — в сценариях использованы одни и те же изображения автомобилей, но в двух форматах: png и jpg. Дело в том, что для второго примера важно, чтобы рисунки «просвечивали» в тех частях, где отсутствует изображение. А для четвертого, наоборот, необходимы картинки на белом фоне. 6.1. Выбор картинки Итак, приступим. Самым первым рассмотрим в действии сценарий, который при выделении одного рисунка меняет вид остальных. Запустите в своем браузере страницу https://testjs.ru/kpp/k1.html. Вы увидите 6 изображений различных автомобилей — современных и ретро (рис. 6.1.1). Наведите указатель мыши на одну из картинок. Ее вид не изменится. А вот остальные автомобили плавно «растворятся», да так, что будут еле видны (рис. 6.1.2). Тем самым мы получили желаемый результат, выделив одно изображение на фоне остальных. 143 Рис. 6.1.1. Так выглядит страница после загрузки Рис. 6.1.2. При наведении указателя мыши на один из рисунков остальные «бледнеют» Приступим к написанию такой программы. В первую очередь создадим шаблон документа, как мы это уже делали в разделе 3.1 главы 3: <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Выбор картинки</title> </head> <body> 144 </body> </html> Разместим на странице контейнер для остальных элементов: <div class="bas"> </div> Зададим его ширину — 1000 пикселей — и расположим посередине. Для этого вставим в головную часть документа теги стилей <style> </style> и в них пропишем настройки контейнера: .bas {position: relative; width: 1000px; margin: auto;} Добавим на страницу необходимые изображения: <img id="i1" class="im" src="img/p1.png" alt="Фото"> <img id="i2" class="im" src="img/p2.png" alt="Фото"> <img id="i3" class="im" src="img/p3.png" alt="Фото"> <img id="i4" class="im" src="img/p4.png" alt="Фото"> <img id="i5" class="im" src="img/p5.png" alt="Фото"> <img id="i6" class="im" src="img/p6.png" alt="Фото"> Обратите внимание: каждый рисунок имеет собственный id, по которому программа будет находить выделенную картинку. Изображения тоже нуждаются в настройках: .im {width: 300px; border: 1px solid #000000; margin: 10px;} При ширине рабочей области 1000px автомобили расположатся двумя рядами. Начнем писать сценарий. Добавим в заголовочный блок теги для скриптов: <script> </script> Назначим обработчик события уровня окна браузера — перемещению указателя мыши по странице. Нам важно событие mouseover: addEventListener("mouseover", ...); 145 Для сокращения программы используем в качестве обработчика стрелочную функцию: addEventListener("mouseover", (ev)=> { ... }); В качестве аргумента ev функция будет получать параметры из интерфейса event (объекта события). Как вы понимаете, нам важно не любое событие mouseover, а только наведение указателя мыши на изображение. Поэтому первым делом обработчик проверяет, где событие произошло: if(e.tagName=="IMG") { ... } Данные об этом мы получаем из интерфейса event: let e=ev.target; if(e.tagName=="IMG") Если выясняется, что указатель мыши был наведен на одну из картинок, то начинается выполнение программных блоков стрелочной функции: if(e.tagName=="IMG") { ... } else { ... } Определим id рисунка: e.id Таким образом мы узнаем, какой из автомобилей останется в неизменном виде. Остальные авто надо сделать менее заметными. Для этого в первой части условного выражения if(e.tagName=="IMG") { ... } 146 напишем цикл, в котором все картинки, кроме выделенной, будут «мутнеть»: for(let i=1; i<7; i++) { let b="i"+i; if(e.id!=b) { let t=document.getElementById(b).style; t.transition="opacity 1s"; t.opacity=0.1; } } Поскольку рисунков 6, то условие выполнения цикла — i<7. В каждом проходе мы вычисляем id очередного рисунка: let b="i"+i; Если текущий id не совпадает с id выделенного изображения e.id!=b то происходит плавное «растворение» очередного автомобиля в течение одной секунды: if(e.id!=b) { let t=document.getElementById(b).style; t.transition="opacity 1s"; t.opacity=0.1; } Как видите, здесь мы меняем значение свойства opacity, которое отвечает за уровень непрозрачности элемента. Обратите внимание: автомобили «растворяются» одновременно, а не последовательно друг за другом, как могут предположить некоторые читатели. Теперь представим, что мы переместили указатель мыши с картинки на свободное пространство страницы. Тогда станет выполняться вторая часть условного выражения: else { for(let i=1; i<7; i++) { let b="i"+i; let t=document.getElementById(b).style; t.transition="opacity 2s"; t.opacity=1; } } 147 Так как выделенный рисунок уже имеет уровень непрозрачности, равный единице, то его можно не исключать из цикла for и не вводить ограничение if(a!=b). В этой ситуации на каждом проходе мы плавно возвращаем каждое изображение к исходному уровню максимальной непрозрачности. При наведении указателя мыши на другое изображение все будет происходить точно таким же образом. Думаю, что принцип действия сценария понятен всем читателям. Соберем все фрагменты в единое целое (файл k1.html zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Выбор картинки</title> <style> .bas {position: relative; width: 1000px; margin: auto;} .im {width: 300px; border: 1px solid #000000; margin: 10px;} </style> <script> addEventListener("mouseover", (ev)=> { let e=ev.target; if(e.tagName=="IMG") { for(let i=1; i<7; i++) { let b="i"+i; if(e.id!=b) { let t=document.getElementById(b).style; t.transition="opacity 1s"; t.opacity=0.1; } } } else { for(let i=1; i<7; i++) { let b="i"+i; let t=document.getElementById(b).style; t.transition="opacity 2s"; t.opacity=1; } } }); </script> </head> <body> <div class="bas"> <img id="i1" class="im" src="img/p1.png" alt="Фото"> <img id="i2" class="im" src="img/p2.png" alt="Фото"> <img id="i3" class="im" src="img/p3.png" alt="Фото"> 148 <img id="i4" class="im" src="img/p4.png" alt="Фото"> <img id="i5" class="im" src="img/p5.png" alt="Фото"> <img id="i6" class="im" src="img/p6.png" alt="Фото"> </div> </body> </html> Осталось добавить, что разное время «растворения» и «проявления» изображений выбрано экспериментальным путем. При данных значениях визуальный эффект наиболее качественный. Надеюсь, на этом примере вы освоили порядок создания документа со сценарием. Поэтому в дальнейшем я не стану подробно рассуждать на темы, которые были разобраны в главе 3: о создании шаблонов страниц, добавлении тегов стилей и скриптов, внедрении программного кода. 6.2. Увеличиваем рисунки Возьмемся за следующий пример. Его можно посмотреть по адресу https://testjs.ru/kpp/k2.html. Внешний вид страницы такой же, как и в предыдущем случае: снова 6 автомобилей, расставленных в 2 ряда (рис. 6.2.1). Щелкните указателем мыши на любом изображении и вы увидите, как оно плавно увеличится в размерах и одновременно сместится в центр (рис. 6.2.2). При этом оставшиеся авто не менее плавно исчезнут (рис. 6.2.3). В таком состоянии страница может находиться сколь угодно долго. Чтобы вернуть ей исходный вид, вторично щелкните по данной картинке. Она уменьшится и займет свое место, а остальные рисунки вновь станут видны. И опять все изменения произойдут плавно. Так можно увеличивать и просматривать любые картинки, находящиеся на странице. С чем мы имеем дело? Основой служит все тот же слой <div class="bas"> </div> с теми же самыми настройками: .bas {position: relative; width: 1000px; margin: auto;} Тот же набор рисунков: <img id="i1" class="im" src="img/p1.png" alt="Фото"> <img id="i2" class="im" src="img/p2.png" alt="Фото"> <img id="i3" class="im" src="img/p3.png" alt="Фото"> <img id="i4" class="im" src="img/p4.png" alt="Фото"> <img id="i5" class="im" src="img/p5.png" alt="Фото"> <img id="i6" class="im" src="img/p6.png" alt="Фото"> 149 Рис. 6.2.1. Страница в исходном состоянии Рис. 6.2.2. Выбранное изображение начинает увеличиваться и смещаться к центру, а остальные рисунки «растворяются» 150 Рис. 6.2.3. Изображение заняло центральное положение, остальные картинки в это время не видны Но дальше начинаются принципиальные различия с предыдущим случаем. В первую очередь у картинок иные настройки. Теперь изображения размещены не по блочному принципу, а методом позиционирования на слоеконтейнере: .im {position: absolute; width: 300px; border: 1px solid #000000; z-index: 1;} Кроме того, в иерархии элементов всем рисункам присвоено значение z-index, равное единице. Координаты для картинок: #i1 {left: 10px; top: 10px;} #i2 {left: 322px; top: 10px;} #i3 {left: 634px; top: 10px;} #i4 {left: 10px; top: 200px;} #i5 {left: 322px; top: 200px;} #i6 {left: 634px; top: 200px;} Перейдем к сценарию. Объявим 3 глобальные переменные: let y=1; let w=0; let h=0; Переменная y выполняет роль идентификатора кликов. Она позволяет сценарию определить — первый или второй щелчок был на рисунке. То есть 151 Powered by TCPDF (www.tcpdf.org) увеличивать изображение или, наоборот, уменьшать. Если y принимает значение 1, это означает первый клик, если 2 — второй. Переменные w и h нужны для временного хранения координат рисунка, на котором произошел клик, чтобы программа «знала», куда возвращать эту картинку. В новом сценарии мы регистрируем обработчик события click уровня окна: addEventListener("click", ...); И опять используем функцию-стрелку: addEventListener("click", (ev)=> { ... }); Так как обращений к интерфейсу event будет несколько, сократим код, объявив переменную e: let e=ev.target; Проверяем, где выполнен клик: if(e.tagName=="IMG") Если на одной из картинок, то выполняются программные блоки функции. В ее теле мы неоднократно станем обращаться к стилевым параметрам изображения, на котором был сделан щелчок. Поэтому для сокращения кода объявим переменную t: let t=document.getElementById(e.id).style; Если на данной картинке случился первый щелчок if(y==1) то последовательно выполняются следующие инструкции. Сначала выясняются координаты рисунка относительно границ слоя-контейнера: let d=document.getElementById(e.id); w=d.offsetLeft; h=d.offsetTop; Затем переменной-идентификатору присваивается новое значение: y=2; 152 Дальше стартует уже знакомый нам цикл, в котором выполняются операции «растворения» остальных изображений: for(let i=1; i<7; i++) { let b="i"+i; if(e.id!=b) { let p=document.getElementById(b).style; p.transition="opacity 1s"; p.opacity=0; } } Следом приходит очередь изменить состояние «главного действующего лица». Сначала увеличивается z-index картинки, чтобы она в процессе движения располагалась поверх остальных рисунков: t.zIndex=2; Потом одновременно плавно увеличиваем ее размер и смещаем к центру страницы: t.transition="width 1s, left 1s, top 1s"; t.width=600+"px"; t.left=200+"px"; t.top=100+"px"; Суммарно результат выполнения первой части условного выражения выглядит именно так, как описано в начале этого раздела: незадействованные автомобили растворяются, а помеченный увеличивается и смещается в центр, оказываясь в одиночестве на белом фоне. Снова щелкнем на изображение. Теперь выполняется вторая часть условного выражения: else { ... } Первым делом изменится значение, хранящееся в идентификаторе: y=1; Потом стартует цикл, в котором фоновые снимки появляются вновь, так как меняется значение их непрозрачности: for(let i=1; i<7; i++) { let b="i"+i; let p=document.getElementById(b).style; 153 p.transition="opacity 2s"; p.opacity=1; } После чего запускается процесс уменьшения и обратного перемещения просмотренного рисунка в точку, которая определена координатами, сохраненными в переменных w и h: t.transition="width 1s, left 1s, top 1s"; t.width=300+"px"; t.left=w+"px"; t.top=h+"px"; В последнюю очередь меняется свойство z-index. Оно возвращается к исходному значению: t.zIndex=1; Так как идентификатор в данный момент хранит единицу, сценарий готов обработать следующий щелчок и увеличить любую другую картинку (или ту же самую). На этом описание работы программы завершено. Полный код страницы (файл k2.html zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Увеличиваем рисунки</title> <style> .bas {position: relative; width: 1000px; margin: auto;} .im {position: absolute; width: 300px; border: 1px solid #000000; z-index: 1;} #i1 {left: 10px; top: 10px;} #i2 {left: 322px; top: 10px;} #i3 {left: 634px; top: 10px;} #i4 {left: 10px; top: 200px;} #i5 {left: 322px; top: 200px;} #i6 {left: 634px; top: 200px;} </style> <script> let y=1; let w=0; let h=0; addEventListener("click", (ev)=> { let e=ev.target; if(e.tagName=="IMG") { let t=document.getElementById(e.id).style; if(y==1) { 154 let d=document.getElementById(e.id); w=d.offsetLeft; h=d.offsetTop; y=2; for(let i=1; i<7; i++) { let b="i"+i; if(e.id!=b) { let p=document.getElementById(b).style; p.transition="opacity 1s"; p.opacity=0; } } t.zIndex=2; t.transition="width 1s, left 1s, top 1s"; t.width=600+"px"; t.left=200+"px"; t.top=100+"px"; } else { y=1; for(let i=1; i<7; i++) { let b="i"+i; let p=document.getElementById(b).style; p.transition="opacity 2s"; p.opacity=1; } t.transition="width 1s, left 1s, top 1s"; t.width=300+"px"; t.left=w+"px"; t.top=h+"px"; t.zIndex=1; } } }); </script> </head> <body> <div class="bas"> <img id="i1" class="im" src="img/p1.png" alt="Фото"> <img id="i2" class="im" src="img/p2.png" alt="Фото"> <img id="i3" class="im" src="img/p3.png" alt="Фото"> <img id="i4" class="im" src="img/p4.png" alt="Фото"> <img id="i5" class="im" src="img/p5.png" alt="Фото"> <img id="i6" class="im" src="img/p6.png" alt="Фото"> </div> </body> </html> 155 6.3. Галерея Сценарий, который мы сейчас разберем, пожалуй, самый простой. Зайдите на страницу https://testjs.ru/kpp/k3.html. Вы увидите, что начальное расположение элементов изменилось. Теперь документ содержит всего одно, но более крупное изображение автомобиля (рис. 6.3.1). Справа от него находится указатель-стрелка. Нажмем ее. Изображение поменяется и одновременно слева появится еще один указатель (рис. 6.3.2). Продолжаем нажимать правую стрелку до тех пор, пока не загрузится последний, шестой рисунок. В этот момент правая стрелка исчезнет (рис. 6.3.3). Как вы уже, наверное, поняли, стрелки-указатели выполняют роль кнопок перемотки вперед или назад. Когда загружается последний рисунок, выключается перемотка вперед. После загрузки самого первого рисунка выключается перемотка назад. Вот такая простая галерея картинок. Рис. 6.3.1. В исходном состоянии, а также, когда галерея промотана влево до конца, видна только правая кнопка Рис. 6.3.2. Если демонстрируется не первый и не последний рисунки, видны обе кнопки 156 Рис. 6.3.3. Когда галерея промотана вправо до конца, видна только левая кнопка Разберемся, как это все работает. Базовый слой <div class="bas"> </div> остался неизменным. Сохранились и его основные параметры: .bas {position: relative; width: 1000px; margin: auto; text-align: center;} Но появилось одно дополнение: text-align: center; То есть теперь элементы внутри контейнера выравниваются по середине. Собственно, элементов только три. 1. Рисунок: <img id="i1" class="im" src="img/p1.png" alt="Фото"> Как видите, в исходном состоянии загружена картинка p1.png. Параметры рисунка: .im {width: 600px; border: 1px solid #000000; margin-top: 10px;} 2. Кнопка перемотки назад: <img id="nz" class="bu" src="img/nz.png" alt="Назад"> 157 3. Кнопка перемотки вперед: <img id="vp" class="bu" src="img/vp.png" alt="Вперед"> Их настройки: .bu {vertical-align: 140px; margin: 20px;} Кроме того, сразу после загрузки документа кнопка перемотки назад скрыта: #nz {visibility: hidden;} Для управления процессом у нас зарегистрированы 2 обработчика событий click на кнопках: addEventListener("load", function() { document.getElementById("nz").addEventListener("click", nz); document.getElementById("vp").addEventListener("click", vp); }); Кроме того, мы предварительно объявили глобальную переменную i — счетчик изображений. Поскольку на начальном этапе загружен рисунок p1.png, переменной присвоено значение 1: let i=1; Рассмотрим сначала, что произойдет, если нажать кнопку перемотки вперед. Будет запущена функция vp: function vp() { if(i<6) { i++; document.getElementById("nz").style.visibility="visible"; document.getElementById("i1").src="img/p"+i+".png"; if(i==6) document.getElementById("vp").style.visibility="hidden"; } } Как видите, она очень простая. Ее инструкции выполняются до тех пор, пока значение счетчика не превышает шести — то есть общего количества изображений: if(i<6) 158 Поскольку начальное значение счетчика равно единице, функция готова к работе сразу после загрузки страницы. Щелкнем мышью на кнопке перемотки вперед. Первым делом произойдет увеличение переменной-счетчика: i++; Затем станет видимой кнопка перемотки назад: document.getElementById("nz").style.visibility="visible"; После этого рисунку с авто будет присвоен новый адрес: document.getElementById("i1").src="img/p"+i+".png"; Так как пока мы щелкнули только однажды, загрузится изображение p2.png — следующее по очереди. Кликнем на кнопке перемотки вперед еще раз — и увидим уже третью картинку. Так будет происходить до тех пор, пока выполняется главное условие функции. Когда галерея промотана до конца if(i==6) выполняется еще одна инструкция — скрывающая кнопку перемотки вперед: document.getElementById("vp").style.visibility="hidden"; Теперь посмотрим на функцию перемотки галереи назад. Она очень похожа: function nz() { if(i>1) { i--; document.getElementById("vp").style.visibility="visible"; document.getElementById("i1").src="img/p"+i+".png"; if(i==1) document.getElementById("nz").style.visibility="hidden"; } } Только здесь главное условие другое: функция работает, пока перемотка не достигла начала галереи: if(i>1) Поскольку рисунки следуют в обратном порядке, после каждого щелчка на кнопке перемотки назад переменная-счетчик уменьшается: i--; 159 Теперь вспомним, что при достижении конца галереи кнопка перемотки вперед будет скрыта. Поскольку сценарий «не знает», произошло данное событие или нет, лучше на каждом проходе функции выполнять вот такую инструкцию: document.getElementById("vp").style.visibility="visible"; Это гарантирует, что при обратном движении изображений от последнего кадра к началу скрытая кнопка перемотки вперед «объявится» вновь. Пойдем дальше. После щелчка на кнопке перемотки назад будет показано предыдущее изображение: document.getElementById("i1").src="img/p"+i+".png"; Когда на странице появится первый рисунок if(i==1) кнопка перемотки назад станет невидимой: document.getElementById("nz").style.visibility="hidden"; Просмотр не обязательно выполнять строго от начала до конца или наоборот. Вы можете изучить 2–3 первых рисунка, а потом вернуться назад. Прокрутить галерею до конца, потом отмотать ее на пару снимков назад и снова прокрутить вперед. Последовательность роли не играет. Во всех случаях сценарий будет работать корректно. Подведем черту — мы написали такую программу (файл k3.html zipархива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Галерея</title> <style> .bas {position: relative; width: 1000px; margin: auto; text-align: center;} .bu {vertical-align: 140px; margin: 20px;} .im {width: 600px; border: 1px solid #000000; margin-top: 10px;} #nz {visibility: hidden;} </style> <script> addEventListener("load", function() { document.getElementById("nz").addEventListener("click", nz); document.getElementById("vp").addEventListener("click", vp); }); 160 let i=1; function nz() { if(i>1) { i--; document.getElementById("vp").style.visibility="visible"; document.getElementById("i1").src="img/p"+i+".png"; if(i==1) document.getElementById("nz").style.visibility="hidden"; } } function vp() { if(i<6) { i++; document.getElementById("nz").style.visibility="visible"; document.getElementById("i1").src="img/p"+i+".png"; if(i==6) document.getElementById("vp").style.visibility="hidden"; } } </script> </head> <body> <div class="bas"> <img id="nz" class="bu" src="img/nz.png" alt="Назад"> <img id="i1" class="im" src="img/p1.png" alt="Фото"> <img id="vp" class="bu" src="img/vp.png" alt="Вперед"> </div> </body> </html> 6.4. Слайдер Рассмотрим следующий сценарий. Он управляет слайдером, за работой которого можно наблюдать на странице https://testjs.ru/kpp/k4.html. Посередине экрана расположен рисунок автомобиля (рис. 6.4.1), а под ним кнопка перемотки с надписью «Следующий» (т. е. следующий кадр). Нажмем ее. Первое изображение плавно уедет вверх, уступив место второму (рис. 6.4.2). Снова нажмем кнопку. Второе изображение будет заменено третьим. И так далее — до последней, шестой картинки. Когда она на экране, нажмем кнопку еще раз. Последнее изображение исчезнет за верхней границей окна браузера, и останется только белый «лист» с кнопкой. Но буквально через секунду на странице плавно возникнет первый рисунок (рис. 6.4.3). Слайдер готов демонстрировать автомобили по новой. 161 И опять базой для элементов страницы служит контейнер <div class="bas"> </div> с настройками стилей .bas {position: relative; width: 1000px; margin: auto;} В контейнере 6 рисунков. Рис. 6.4.1. Начальная картинка слайдера Рис. 6.4.2. Первый кадр «уплывает» вверх, освобождая место следующему рисунку 162 <img id="i1" class="im" src="img/p1.jpg" alt="Фото"> <img id="i2" class="im" src="img/p2.jpg" alt="Фото"> <img id="i3" class="im" src="img/p3.jpg" alt="Фото"> <img id="i4" class="im" src="img/p4.jpg" alt="Фото"> <img id="i5" class="im" src="img/p5.jpg" alt="Фото"> <img id="i6" class="im" src="img/p6.jpg" alt="Фото"> Рис. 6.4.3. Когда все изображения просмотрены, первое вновь «проявляется» на экране. Теперь можно крутить слайды по новой Их положение и оформление: .im {position: absolute; top: 10px; left: 200px; width: 600px; border: 1px solid #000000;} Благодаря присвоению каждому изображению z-index, на единицу меньше лежащего выше #i1 {z-index: 7;} #i2 {z-index: 6;} #i3 {z-index: 5;} #i4 {z-index: 4;} #i5 {z-index: 3;} #i6 {z-index: 2;} получается, что все рисунки расположились «стопкой». Наконец, добавляем кнопку <input type="button" value="Следующий" id="bu"> позиционируем ее и оформляем внешний вид: #bu {position: absolute; top: 380px; left: 200px; width: 250px; height: 40px; border: 1px solid #000000; box-shadow: #333333 5px 5px 8px; font-size: 20px;} 163 Зарегистрируем обработчик нажатия кнопки: addEventListener("load", function() { document.getElementById("bu").addEventListener("click", but); }); Объявим две глобальные переменные: let k=10; let s=0; Переменная k необходима для временного хранения текущей координаты рисунка, движущегося вверх. Переменная s — счетчик кадров. Первое, что делает функция but после щелчка на кнопке, увеличивает на единицу счетчика кадров s++; и присваивает переменной k начальное значение верхней координаты рисунка: k=10; На этом моменте остановимся подробнее. Зачем переменной k вновь присваивать значение, которое присвоено ей при объявлении? Как мы уже узнали, эта переменная хранит текущую координату движущейся картинки. Допустим, первый рисунок уехал вверх за границы браузера. В момент, когда он остановится, k будет хранить значение точки остановки, равное 700 пикселей. Значит, при втором щелчке на кнопке переменной k нужно вернуть начальное значение координаты по оси Y. Ведь именно эта координата взята за основу положения второго рисунка в исходном состоянии (как, впрочем, и всех остальных). Обработчик события имеет 2 стадии выполнения: if(s<6) ... и if(s==6) { ... } Пока мы не промотали все кадры, выполняется первое условие. Здесь все просто: if(s<6) mov(); 164 Как вы видите, при каждом щелчке запускается рекурсивная функция mov: function mov() { if(k>=-700) { document.getElementById("i"+s).style.top=k+"px"; k-=10; setTimeout(mov, 10); } } Она вызывает сама себя до тех пор, пока движущийся рисунок не достигнет верхней границы: if(k>=-700) Когда это условие верно, свойству top рисунка последовательно с интервалом 10 миллисекунд присваивается новое значение координаты, на 10 пикселей меньше предыдущего: document.getElementById("i"+s).style.top=k+"px"; k-=10; setTimeout(mov, 10); Достигнув верхнего предела, картинка останавливается. Так происходит со всеми изображениями вплоть до предпоследнего, пятого. Щелчок на кнопке перемотки — и очередной кадр уезжает вверх. Что меняется, когда вы в шестой раз нажимаете кнопку? Выполняется условие if(s==6) В действие вступает второй блок инструкций if(s==6) { ... } Опять запускается функция mov, которая отправляет последнее изображение за границы экрана: mov(); Через одну секунду переменным k и s присваиваются исходные значения: setTimeout(()=>{k=10; s=0;}, 1000); 165 Тем самым сценарий подготавливается к новому запуску слайд-шоу с самого начала. Далее объявляем переменную h let h=document.getElementById("i1").style; посредством которой мы станем обращаться к свойствам первого рисунка. Делаем его полностью невидимым: h.opacity=0; Теперь запускаем два таймера, один вложенный в другой: setTimeout(()=> { h.top="10px"; h.transition="opacity 1s"; h.opacity=1; setTimeout(()=> { if(h.opacity==1) { for(let a=2; a<7; a++) document.getElementById("i"+a).style.top="10px"; } }, 1000); }, 1000); Первый таймер сработает через одну секунду. Второй будет запущен через одну секунду после первого. Инструкции первого таймера возвращают первый рисунок в исходное положение h.top="10px"; а затем в течение одной секунды плавно делают картинку видимой: h.transition="opacity 1s"; h.opacity=1; Теперь про вложенный таймер. Он стартует после того, как непрозрачность первого изображения достигла максимума: if(h.opacity==1) После этого запускается цикл for(let a=2; a<7; a++) document.getElementById("i"+a).style.top="10px"; который проходит по всем кадрам, начиная со второго, и возвращает их в исходное положение. С этого момента слайдер готов вновь прокручивать рисунки. 166 Вот что в итоге у нас получилось (файл k4.html zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Слайдер</title> <style> .bas {position: relative; width: 1000px; margin: auto;} .im {position: absolute; top: 10px; left: 200px; width: 600px; border: 1px solid #000000;} #bu {position: absolute; top: 380px; left: 200px; width: 250px; height: 40px; border: 1px solid #000000; box-shadow: #333333 5px 5px 8px; font-size: 20px;} #i1 {z-index: 7;} #i2 {z-index: 6;} #i3 {z-index: 5;} #i4 {z-index: 4;} #i5 {z-index: 3;} #i6 {z-index: 2;} </style> <script> addEventListener("load", function() { document.getElementById("bu").addEventListener("click", but); }); let k=10; let s=0; function but() { s++; k=10; if(s<6) mov(); if(s==6) { mov(); setTimeout(()=>{k=10; s=0;}, 1000); let h=document.getElementById("i1").style; h.opacity=0; setTimeout(()=> { h.top="10px"; h.transition="opacity 1s"; h.opacity=1; setTimeout(()=> { if(h.opacity==1) { for(let a=2; a<7; a++) document.getElementById("i"+a).style.top="10px"; } }, 1000); }, 1000); 167 } } function mov() { if(k>=-700) { document.getElementById("i"+s).style.top=k+"px"; k-=10; setTimeout(mov, 10); } } </script> </head> <body> <div class="bas"> <img id="i1" class="im" src="img/p1.jpg" alt="Фото"> <img id="i2" class="im" src="img/p2.jpg" alt="Фото"> <img id="i3" class="im" src="img/p3.jpg" alt="Фото"> <img id="i4" class="im" src="img/p4.jpg" alt="Фото"> <img id="i5" class="im" src="img/p5.jpg" alt="Фото"> <img id="i6" class="im" src="img/p6.jpg" alt="Фото"> <input type="button" value="Следующий" id="bu"> </div> </body> </html> 6.5. Круговорот изображений Пятый сценарий довольно простой. Зайдем на страницу https://testjs.ru/kpp/k5.html. Она напоминает предыдущую. Разница только в отсутствии кнопки. Перед нами новый вариант слайдера, где изображения не уезжают вверх, а меняются несколько иным образом. Смотрим, что делается после загрузки страницы. Проходит несколько секунд и первый рисунок растворяется, а сквозь него проявляется второй (рис. 6.5.1). Снова пауза в несколько секунд — и второй рисунок сменяется третьим. Так повторяется до появления завершающего, шестого рисунка. Он, в свою очередь, «постояв» некоторое время, будет заменен на первое изображение, и демонстрация автомобилей начнется вновь (рис. 6.5.2). То есть цикл просмотра картинок представляет собой замкнутый круг. В теле документа у нас вновь привычный набор элементов. 1. Слой-контейнер <div class="bas"> </div> 168 Powered by TCPDF (www.tcpdf.org) с теми же параметрами: .bas {position: relative; width: 1000px; margin: auto;} Рис. 6.5.1. Смена первого рисунка на второй Рис. 6.5.2. Последняя картинка растворяется и на ее месте вновь появляется первая 2. Уже хорошо знакомые нам рисунки автомобилей: <img id="i1" class="im" src="img/p1.jpg" alt="Фото"> <img id="i2" class="im" src="img/p2.jpg" alt="Фото"> <img id="i3" class="im" src="img/p3.jpg" alt="Фото"> <img id="i4" class="im" src="img/p4.jpg" alt="Фото"> <img id="i5" class="im" src="img/p5.jpg" alt="Фото"> <img id="i6" class="im" src="img/p6.jpg" alt="Фото"> 169 В этом сценарии на начальном этапе все изображения сделаны невидимыми, так как их свойствам непрозрачности присвоено значение 0: .im {position: absolute; top: 10px; left: 200px; width: 600px; border: 1px solid #000000; opacity: 0;} Для правильной работы сценария сразу перезапишем значение свойства непрозрачности первого рисунка: #i1 {opacity: 1;} После загрузки страницы через 2 секунды дается старт рекурсивной функции diss: addEventListener("load", function() { setTimeout(diss, 2000); }); Назначение функции — выполнять показ картинок по кругу. В ней есть два блока условий: function diss() { if(s<6) { ... } else { ... } } Рассмотрим их по порядку. Перед запуском функции у нас объявляется глобальная переменная s, которая выполняет роль счетчика изображений: let s=1; Раз исходное значение переменной s равно единице, то выполняется первый блок инструкций. Сначала формируется id для текущего рисунка (в начальный момент работы сценария — для первого). let a="i"+s; а затем этот рисунок плавно исчезает с экрана за счет медленного изменения уровня непрозрачности opacity с 1 до 0 (напоминаю, что в отличие от всех остальных картинок, первую мы уже сделали видимой): 170 let t=document.getElementById(a).style; t.transition="opacity 2s"; t.opacity=0; Теперь значение s увеличивается на единицу: s++; Дальше формируется id следующего рисунка a="i"+s; после чего он плавно делается видимым: t=document.getElementById(a).style; t.transition="opacity 2s"; t.opacity=1; Через 3 с функция diss запускается повторно: setTimeout(diss, 3000); Так происходит, пока выполняется условие if(s<6) Когда переменная s достигает значения 6 (т. е. на экране демонстрируется шестой автомобиль), срабатывает вторая часть блока условий. Здесь все еще проще. Переменной s присваивается исходное значение: s=1; Последнее авто становится невидимым let t=document.getElementById("i6").style; t.transition="opacity 2s"; t.opacity=0; уступая место первому рисунку: t=document.getElementById("i1").style; t.transition="opacity 2s"; t.opacity=1; Через 3 с функция diss запускается по новой setTimeout(diss, 3000); и начинается второй круг. 171 Демонстрация автомобилей будет происходить непрерывно до тех пор, пока документ находится в окне браузера. Прервать цикл можно, только покинув страницу. Как видите, все действительно оказалось просто. Ранее мы говорили, что бесконечная рекурсия опасна зависанием страницы. В данном случае процесс организован так, что никакого зависания не будет. Это редкое исключение из правила. Ниже приведен листинг данной программы (файл k5.html zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Круговорот изображений</title> <style> .bas {position: relative; width: 1000px; margin: auto;} .im {position: absolute; top: 10px; left: 200px; width: 600px; border: 1px solid #000000; opacity: 0;} #i1 {opacity: 1;} </style> <script> addEventListener("load", function() { setTimeout(diss, 2000); }); let s=1; function diss() { if(s<6) { let a="i"+s; let t=document.getElementById(a).style; t.transition="opacity 2s"; t.opacity=0; s++; a="i"+s; t=document.getElementById(a).style; t.transition="opacity 2s"; t.opacity=1; setTimeout(diss, 3000); } else { s=1; let t=document.getElementById("i6").style; t.transition="opacity 2s"; t.opacity=0; t=document.getElementById("i1").style; t.transition="opacity 2s"; t.opacity=1; 172 setTimeout(diss, 3000); } } </script> </head> <body> <div class="bas"> <img id="i1" class="im" src="img/p1.jpg" alt="Фото"> <img id="i2" class="im" src="img/p2.jpg" alt="Фото"> <img id="i3" class="im" src="img/p3.jpg" alt="Фото"> <img id="i4" class="im" src="img/p4.jpg" alt="Фото"> <img id="i5" class="im" src="img/p5.jpg" alt="Фото"> <img id="i6" class="im" src="img/p6.jpg" alt="Фото"> </div> </body> </html> Интересно, что практически во всех сценариях, за исключением примера галереи, мы имели дело со свойством opacity, которое определяет непрозрачность элемента. Как видите, оно очень ценно для манипулирования состоянием картинок, фотографий, рисунков. 6.6. Пасьянс из картинок Очередной сценарий получил свое название от сходства с раскладыванием карточного пасьянса. Зайдем на страницу https://testjs.ru/kpp/k6.html. На ней мы увидим две кнопки, расположенные в нижней части: «Показать» и «Скрыть». И все. В исходном состоянии на странице ничего не происходит. Нажмем кнопку «Показать». В центре окна браузера по очереди, начиная с верхнего левого угла, с интервалом в полсекунды станут появляться уже хорошо знакомые нам изображения автомобилей (рис. 6.6.1). Нажмем кнопку «Скрыть». Изображения станут исчезать с таким же интервалом, но в обратном порядке. Приступим к разбору сценария. Традиционно основой для галереи картинок выбран слой-контейнер <div class="bas"> </div> с привычными настройками: .bas {position: relative; width: 1000px; margin: auto; text-align: center;} 173 Контейнер содержит 6 рисунков: <img id="i1" class="im" src="img/p1.png" alt="Фото"> <img id="i2" class="im" src="img/p2.png" alt="Фото"> <img id="i3" class="im" src="img/p3.png" alt="Фото"> <img id="i4" class="im" src="img/p4.png" alt="Фото"> <img id="i5" class="im" src="img/p5.png" alt="Фото"> <img id="i6" class="im" src="img/p6.png" alt="Фото"> Рис. 6.6.1. Раскладываем автомобильный пасьянс В начальном состоянии все рисунки невидимы, так как их свойствам непрозрачности opacity (вновь это полезное свойство!) присвоено значение 0: .im {width: 300px; border: 1px solid #000000; margin: 10px; opacity: 0;} Кроме того, в контейнере есть 2 кнопки: <input type="button" value="Показать" id="bup"> <input type="button" value="Скрыть" id="bum"> При нажатии любой из них запускается одна и та же функция but: addEventListener("load", function() { document.getElementById("bup").addEventListener("click", but); document.getElementById("bum").addEventListener("click", but); }); 174 Еще до регистрации обработчика мы объявляем две переменные: let a=1; let b; Переменная a является счетчиком изображений, а b получает данные о том, на какой из кнопок произошло событие click. Итак, как уже было сказано выше, после клика на одной из кнопок запускается функция but: function but(ev) { b=ev.target.id; opas(); } Ее задача определить источник события click b=ev.target.id; а после этого запустить функцию opas, которая и выполняет основные манипуляции с картинками: opas(); Функция opas содержит два блока инструкций, которые выполняются в зависимости от того, на какой кнопке был выполнен клик: if(b=="bup") { ... } if(b=="bum") { ... } Предположим, что мы щелкнули на кнопке «Показать». Тогда будет задействован первый блок инструкций: document.getElementById("i"+a).style.opacity=1; a++; if(a<7) setTimeout(opas, 500); else a=6; Поскольку начальное значение переменной a равно единице, инструкция document.getElementById("i"+a).style.opacity=1; 175 сделает видимой первую картинку. После этого значение счетчика увеличится на 1: a++; Поскольку на начальном этапе выполняется условие if(a<7) то через 500 миллисекунд функция opas запустится снова: setTimeout(opas, 500); Так происходит, пока не появятся все автомобили. Когда условие if(a<7) станет ложным, рекурсия будет остановлена. Все рисунки окажутся на странице. В этот момент значение переменной a достигнет 7. Чтобы функция не потеряла «работоспособность», вернем счетчику индекс последнего изображения: else a=6; Теперь нажмем кнопку «Скрыть». В дело вступит следующий блок инструкций: if(b=="bum") { document.getElementById("i"+a).style.opacity=0; a--; if(a>0) setTimeout(opas, 500); else a=1; } Здесь все будет происходить в обратном порядке: сначала исчезнет последний рисунок, потом предпоследний и так до самого первого. На последнем шаге переменная a получит значение 0. Теперь возвращаем ей индекс первой картинки: else a=1; Все. Программа вернулась в исходное состояние и готова к новому запуску. 176 Обращаю ваше внимание, что нажатие кнопок «Показать» и «Скрыть» в хаотичном порядке (например, два раза «Показать» и три раза «Скрыть») не приведет к нарушению работы сценария. Подведем черту, продемонстрировав код готовой программы (файл k6.html zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Пример двунаправленной рекурсии</title> <style> .bas {position: relative; width: 1000px; margin: auto; text-align: center;} .im {width: 300px; border: 1px solid #000000; margin: 10px; opacity: 0;} </style> <script> addEventListener("load", function() { document.getElementById("bup").addEventListener("click", but); document.getElementById("bum").addEventListener("click", but); }); let a=1; let b; function but(ev) { b=ev.target.id; opas(); } function opas() { if(b=="bup") { document.getElementById("i"+a).style.opacity=1; a++; if(a<7) setTimeout(opas, 500); else a=6; } if(b=="bum") { document.getElementById("i"+a).style.opacity=0; a--; if(a>0) setTimeout(opas, 500); else a=1; } } 177 </script> </head> <body> <div class="bas"> <img id="i1" class="im" src="img/p1.png" alt="Фото"> <img id="i2" class="im" src="img/p2.png" alt="Фото"> <img id="i3" class="im" src="img/p3.png" alt="Фото"> <img id="i4" class="im" src="img/p4.png" alt="Фото"> <img id="i5" class="im" src="img/p5.png" alt="Фото"> <img id="i6" class="im" src="img/p6.png" alt="Фото"> <input type="button" value="Показать" id="bup"> <input type="button" value="Скрыть" id="bum"> </div> </body> </html> 6.7. Проявление и «растворение» Помните, в разделе 4.10 мы говорили о том, что один и тот же цикл можно «заставить» работать в любом направлении: как увеличивать значение счетчика, так и уменьшать? Сейчас мы займемся сценарием, в котором проделаем такие «фокусы» с циклом while. Страница находится по адресу https://testjs.ru/kpp/k7.html. Если зайти на нее, то вы обнаружите то же самое, что и в предыдущем примере: две кнопки — «Показать» и «Скрыть». А все потому, что новая программа — это творческое развитие предыдущей. В чем недостаток примера из раздела 6.6? В резком появлении изображений. Давайте избавимся от этого эффекта. Пусть картинки появляются и скрываются более плавно. И, как уже было неоднократно, в этом нам поможет свойство opacity. Нажмем кнопку «Показать». На странице поочередно и, главное, плавно возникнут все шесть картинок (рис. 6.7.1). Щелкнем на кнопке «Скрыть». Автомобили также поочередно и плавно «растворятся». Начинка документа точно такая же, как и предыдущем случае. Слой-контейнер <div class="bas"> </div> с настройками: .bas {position: relative; width: 1000px; margin: auto; text-align: center;} 178 6 рисунков: <img id="i1" class="im" src="img/p1.png" alt="Фото"> <img id="i2" class="im" src="img/p2.png" alt="Фото"> <img id="i3" class="im" src="img/p3.png" alt="Фото"> <img id="i4" class="im" src="img/p4.png" alt="Фото"> <img id="i5" class="im" src="img/p5.png" alt="Фото"> <img id="i6" class="im" src="img/p6.png" alt="Фото"> Рис. 6.7.1. Проявление и «растворение» изображений В начальном состоянии все рисунки невидимы: .im {width: 300px; border: 1px solid #000000; margin: 10px; opacity: 0;} Две кнопки: <input type="button" value="Показать" id="bup"> <input type="button" value="Скрыть" id="bum"> запускающие одну и ту же функцию but: addEventListener("load", function() { document.getElementById("bup").addEventListener("click", but); document.getElementById("bum").addEventListener("click", but); }); Две переменные: let a=1; let b=1; 179 С этого момента начинаются различия. Переменной b мы сразу присвоили значение 1. Также изменилась роль переменных: a теперь не только счетчик изображений, но и показатель временного интервала «проявления» картинки, а b хранит значения временного интервала «растворения» картинки. Дальше. По сравнению с предыдущим случаем здесь у нас только одна функция — but. Она и выполняет все необходимые манипуляции с изображениями. Главное действующее «лицо» функции — цикл while: while(true) { ... } Так как в условии его выполнения указано значение «истина», такой цикл не имеет изначально заданных ограничений в количестве проходов. Остановка проходов выполняется в определенные моменты инструкцией break. Внутри цикла есть 2 условных блока: if(ev.target.id=="bup") { ... } if(ev.target.id=="bum") { ... } Инструкции первого выполняются, когда нажата кнопка «Показать», а инструкции второго — при нажатии кнопки «Скрыть». Пойдем по порядку. Представим, что щелчок выполнен на кнопке «Показать». Начинается выполнение первого блока: document.getElementById("i"+a).style.transition="opacity "+a+"s"; document.getElementById("i"+a).style.opacity=1; a++; if(a==7) { a=6; break; } Инструкции document.getElementById("i"+a).style.transition="opacity "+a+"s"; document.getElementById("i"+a).style.opacity=1; плавно добавляют первый рисунок на страницу в течение 1 с (так как начальное значение переменной a — единица). 180 Затем показатель временного интервала увеличивается a++; и после второго прохода время появления второго изображения будет уже 2 с. После этого временной интервал снова увеличивается. Так происходит шесть раз. В результате последняя картинка «проявляется» в течение 6 с. Мы видим, что на шестом проходе переменная a снова увеличивается и ее значение достигает 7. В этот момент и происходит остановка цикла: if(a==7) { a=6; break; } Одновременно переменной a возвращаются показатели верхней границы временного интервала и количества рисунков: a=6; Итак, все автомобили на странице. Теперь нажмем кнопку «Скрыть». Начинают выполняться инструкции второго блока: document.getElementById("i"+a).style.transition="opacity "+b+"s"; document.getElementById("i"+a).style.opacity=0; a--; b++; if(a==0) { a=1; b=1; break; } Шестой рисунок плавно исчезает в течение 1 с (так как начальное значение переменной b — единица, а значение переменной a в этот момент равно 6): document.getElementById("i"+a).style.transition="opacity "+b+"s"; document.getElementById("i"+a).style.opacity=0; Теперь счетчик изображений уменьшается на единицу a--; а показатель временного интервала, наоборот, возрастает: b++; 181 Так происходит шесть раз. Номера картинок уменьшаются от последнего к первому, а временной интервал «растворения» увеличивается от 1 до 6 с. На шестом проходе переменная a снова уменьшается и ее значение достигает 0. Переменная b тоже выходит «за рамки» — ее значение достигает 7. А это значит, что наступил момент остановки цикла: if(a==0) { a=1; b=1; break; } Одновременно переменным a и b возвращаются исходные значения: a=1; b=1; С этого момента программа готова к повторному использованию. Как и в предыдущем случае, нажатие кнопок «Показать» и «Скрыть» в хаотичном порядке не приведет к нарушению работы сценария. Полный код новой версии (файл k7.html zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Пример двунаправленного цикла</title> <style> .bas {position: relative; width: 1000px; margin: auto; text-align: center;} .im {width: 300px; border: 1px solid #000000; margin: 10px; opacity: 0;} </style> <script> addEventListener("load", function() { document.getElementById("bup").addEventListener("click", but); document.getElementById("bum").addEventListener("click", but); }); let a=1; let b=1; function but(ev) { while(true) { if(ev.target.id=="bup") { document.getElementById("i"+a).style.transition= "opacity "+a+"s"; document.getElementById("i"+a).style.opacity=1; a++; 182 if(a==7) { a=6; break; } } if(ev.target.id=="bum") { document.getElementById("i"+a).style.transition= "opacity "+b+"s"; document.getElementById("i"+a).style.opacity=0; a--; b++; if(a==0) { a=1; b=1; break; } } } } </script> </head> <body> <div class="bas"> <img id="i1" class="im" src="img/p1.png" alt="Фото"> <img id="i2" class="im" src="img/p2.png" alt="Фото"> <img id="i3" class="im" src="img/p3.png" alt="Фото"> <img id="i4" class="im" src="img/p4.png" alt="Фото"> <img id="i5" class="im" src="img/p5.png" alt="Фото"> <img id="i6" class="im" src="img/p6.png" alt="Фото"> <input type="button" value="Показать" id="bup"> <input type="button" value="Скрыть" id="bum"> </div> </body> </html> 6.8. Рисунки по номерам Мы уже рассматривали вариант галереи, где пролистывание изображений выполнялось кнопками «Вперед» и «Назад». Теперь напишем сценарий для галереи, где роль кнопок выполняют ссылки на номера картинок. Такая галерея представлена здесь: https://testjs.ru/kpp/k8.html (рис. 6.8.1). Как видите, все очень просто. В начальный момент в окне браузера показан первый автомобиль. Ссылка на него подсвечена красным цветом и не активна. Нажмите на ссылку с любым другим номером — и соответствующее изображение появится на странице. Одновременно ссылка с номером 1 активируется, а нажатая, наоборот, станет неактивной (рис. 6.8.2). Плюс такой галереи в том, что просматривать автомобили можно в любом порядке. 183 Рис. 6.8.1. Начальное состояние галереи картинок Рис. 6.8.2. Пролистываем изображения Приступим к изучению данной программы. Как и в предыдущих случаях, вся «начинка» расположена в контейнере <div class="bas"> </div> с привычными свойствами: .bas {position: relative; width: 1000px; margin: auto; text-align: center;} 184 В центре контейнера — рисунок номер один: <img id="im" src="img/p1.png" alt="Фото"> Под ним — расположенные в ряд ссылки <p id="bu"> <a href="img/p1.png" class="curs">1</a> <a href="img/p2.png">2</a> <a href="img/p3.png">3</a> <a href="img/p4.png">4</a> <a href="img/p5.png">5</a> <a href="img/p6.png">6</a> </p> с крупными цифрами: p {font-size: 30px;} Каждая ссылка имеет 20 пикселей свободного пространства вокруг: a {margin: 20px; color: #0000CC;} При этом первая из них <a href="img/p1.png" class="curs">1</a> не активна: .curs {cursor: auto; color: #CC0000;} В начале сценария объявляем переменную a: let a; Она понадобится для сохранения адреса контейнера <p id="bu"> </p>, в который помещены ссылки. После загрузки страницы присваиваем переменной a адрес контейнера и регистрируем в качестве обработчика события click функцию but: addEventListener("load", function() { a=document.getElementById("bu"); a.addEventListener("click", but); }); Функция but очень простая: function but(ev) { let h=ev.target.href; 185 if(h.indexOf("img")) { document.getElementById("im").src=h; for(let n=0; n<a.children.length; n++) { let b=a.children[n]; b.className=(b!=ev.target)?"":"curs"; } } ev.preventDefault(); } Из интерфейса event получаем значение атрибута href той ссылки, на которой был сделан щелчок let h=ev.target.href; и выясняем, действительно ли клик был именно на ссылке: if(h.indexOf("img")) { ... } Если условие истинно, присваиваем значение атрибута href атрибуту src рисунка: document.getElementById("im").src=h; Таким образом, мы загружаем выбранную картинку вместо исходной. Затем вычисляем ссылку, которая должна стать неактивной: for(let n=0; n<a.children.length; n++) { let b=a.children[n]; b.className=(b!=ev.target)?"":"curs"; } В этом цикле количество проходов ограничивается количеством дочерних элементов контейнера <p id="bu"> </p>: n<a.children.length В теле цикла выполняем две операции. 1. Присваиваем переменной b адрес очередной ссылки (для сокращения кода): let b=a.children[n]; 186 2. Посредством тернарного оператора выясняем, какие ссылки должны стать активными, а какая ссылка, наоборот, неактивной: b.className=(b!=ev.target)?"":"curs"; Для этого мы используем условие b!=ev.target Ссылке, на которой произошло событие click, присваивается значение curs атрибута class. У остальных ссылок атрибут class получает пустое значение. Благодаря этому ссылка, на которой был щелчок, становится неактивной, а остальные — активными. Последняя инструкция ev.preventDefault(); отменяет поведение браузера по умолчанию. Если бы ее не было, то при первом же щелчке на ссылке загружалась бы другая страница с фотографией на темном фоне. Как видите, ничего сложного. Ниже представлен код данной галереи (файл k8.html zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Галерея</title> <style> .bas {position: relative; width: 1000px; margin: auto; text-align: center;} p {font-size: 30px;} a {margin: 20px; color: #0000CC;} .curs {cursor: auto; color: #CC0000;} </style> <script> let a; addEventListener("load", function() { a=document.getElementById("bu"); a.addEventListener("click", but); }); function but(ev) { let h=ev.target.href; if(h.indexOf("img")) { document.getElementById("im").src=h; 187 for(let n=0; n<a.children.length; n++) { let b=a.children[n]; b.className=(b!=ev.target)?"":"curs"; } } ev.preventDefault(); } </script> </head> <body> <div class="bas"> <img id="im" src="img/p1.png" alt="Фото"> <p id="bu"> <a href="img/p1.png" class="curs">1</a> <a href="img/p2.png">2</a> <a href="img/p3.png">3</a> <a href="img/p4.png">4</a> <a href="img/p5.png">5</a> <a href="img/p6.png">6</a> </p> </div> </body> </html> 6.9. Три галереи «На закуску» у нас довольно простая, но в то же время универсальная программа. Во всех предыдущих сценариях мы рассматривали ситуацию, когда документ содержал всего одну галерею изображений. Настало время усложнить задачу. Попробуем написать страницу, где будет сразу три галереи. А для этого воспользуемся методом, который мы использовали в примере из предыдущего раздела. С одной оговоркой: метод необходимо подкорректировать. Сценарий из раздела 6.8 составлен так, что функция but может работать только с конкретным набором фотографий, размещенных в узле с id="bu". Наша задача — изменить ситуацию и написать такую функцию, которая могла бы «обслуживать» произвольное, не заданное изначально количество галерей. Что мы успешно и реализуем в последнем примере. Итак, есть страница https://testjs.ru/kpp/k9.html. Здесь три набора изображений: автомобилей, самолетов, яхт (рис. 6.9.1). Под каждым рисунком ссылки на остальные картинки из соответствующего набора. Ссылки можно щелкать в любом порядке (рис. 6.9.2). Убедившись, что все работает корректно, приступим к разбору сценария, который управляет галереями. 188 Powered by TCPDF (www.tcpdf.org) Рис. 6.9.1. Исходное состояние страницы Рис. 6.9.2. Пролистываем фотографии сразу во всех галереях Все группы фотографий, как и прежде, расположены в контейнере <div class="bas"> </div> с настройками: .bas {position: relative; width: 1000px; margin: auto; text-align: center;} Поскольку у нас теперь три галереи, для их размещения мы использовали таблицу <table> <tr> <td><img src="img/p1.png" alt="Фото"> <p id="bu1"> <a href="img/p1.png" class="curs">1</a> <a href="img/p2.png">2</a> <a href="img/p3.png">3</a> <a href="img/p4.png">4</a> 189 <a href="img/p5.png">5</a> <a href="img/p6.png">6</a> </p></td> <td><img src="pict/p1.jpg" alt="Фото"> <p id="bu2"> <a href="pict/p1.jpg" class="curs">1</a> <a href="pict/p2.jpg">2</a> <a href="pict/p3.jpg">3</a> <a href="pict/p4.jpg">4</a> </p></td> <td><img src="canv/p1.jpg" alt="Фото"> <p id="bu3"> <a href="canv/p1.jpg" class="curs">1</a> <a href="canv/p2.jpg">2</a> <a href="canv/p3.jpg">3</a> <a href="canv/p4.jpg">4</a> <a href="canv/p5.jpg">5</a> </p></td> </tr> </table> в каждой ячейке которой находятся начальные изображения и соответствующее количество ссылок. Как и прежде, рисунки располагаются в контейнерах <p> </p>. Только теперь в их id кроме символов bu добавлены порядковые номера. Размеры фото мы уменьшили и сделали им рамки: img {width: 300px; border: 1px solid #000000;} Во всех галереях первые ссылки не активны: .curs {cursor: auto; color: #CC0000;} Компоновка данной страницы более плотная, поэтому расстояние между ссылками уменьшено: a {margin: 10px; color: #0000CC;} Поскольку мы считаем количество галерей произвольным, для регистрации обработчиков надо применить следующий подход: addEventListener("load", function() { let t=document.getElementsByTagName("P"); for(let k=0; k<t.length; k++) t[k].addEventListener("click", but); }); На первом этапе получаем массив узлов p: let t=document.getElementsByTagName("P"); 190 А затем для каждого узла регистрируем обработчик, который вызывает одну и ту же функцию but: for(let k=0; k<t.length; k++) t[k].addEventListener("click", but); Сама функция содержит набор простых инструкций: function but(ev) { let a=parseFloat(ev.target.parentElement.id.substr(2))-1; document.getElementsByTagName("IMG")[a].src=ev.target.href; for(let n=0; n<this.children.length; n++) { let b=this.children[n]; b.className=(b!=ev.target)?"":"curs"; } ev.preventDefault(); } Вычисляем номер галереи, где произошло событие click, удаляя из id латинские буквы: let a=parseFloat(ev.target.parentElement.id.substr(2))-1; Так как полученное значение относится к строковому типу, приводим его к числовому: parseFloat(ev.target.parentElement.id.substr(2))-1; Операция -1 добавлена, поскольку нумерация id узлов начинается с 1, а нумерация массива этих узлов — с 0. Обратите внимание: щелчок мышью произошел на дочернем элементе контейнера p. Поэтому мы обратились к свойству parentElement — оно возвращает родительский узел для данной ссылки. Из интерфейса event получаем значение атрибута href той ссылки, на которой был сделан щелчок, и присваиваем его значение атрибуту src рисунка соответствующей галереи: document.getElementsByTagName("IMG")[a].src=ev.target.href; Здесь document.getElementsByTagName("IMG") массив начальных изображений на странице, а document.getElementsByTagName("IMG")[a] изображение из галереи, где произошло событие click. 191 Как и в предыдущем примере, вычисляем ссылку, которая должна стать неактивной: for(let n=0; n<this.children.length; n++) { let b=this.children[n]; b.className=(b!=ev.target)?"":"curs"; } Опять у нас количество проходов ограничивается количеством дочерних элементов контейнера p: n<this.children.length Снова в цикле две операции. 1. Присваиваем переменной b адрес очередной ссылки: let b=this.children[n]; 2. При помощи тернарного оператора выясняем, какие ссылки должны стать активными, а какая ссылка — неактивной: b.className=(b!=ev.target)?"":"curs"; Ссылке из текущей галереи, на которой произошло событие click, присваивается значение curs атрибута class. У остальных ссылок атрибут class получает пустое значение. Ссылка, на которой был щелчок, становится неактивной, остальные — активными. Последняя инструкция ev.preventDefault(); отменяет поведение браузера по умолчанию. Вся программа выглядит так (файл k9.html zip-архива): <!DOCTYPE html> <html lang="ru"> <head> <meta charset="utf-8"> <title>Галереи</title> <style> .bas {position: relative; width: 1000px; margin: auto; text-align: center;} p {font-size: 30px;} a {margin: 10px; color: #0000CC;} img {width: 300px; border: 1px solid #000000;} .curs {cursor: auto; color: #CC0000;} </style> <script> addEventListener("load", function() 192 { let t=document.getElementsByTagName("P"); for(let k=0; k<t.length; k++) t[k].addEventListener("click", but); }); function but(ev) { let a=parseFloat(ev.target.parentElement.id.substr(2))-1; document.getElementsByTagName("IMG")[a].src=ev.target.href; for(let n=0; n<this.children.length; n++) { let b=this.children[n]; b.className=(b!=ev.target)?"":"curs"; } ev.preventDefault(); } </script> </head> <body> <div class="bas"> <table> <tr> <td><img src="img/p1.png" alt="Фото"> <p id="bu1"> <a href="img/p1.png" class="curs">1</a> <a href="img/p2.png">2</a> <a href="img/p3.png">3</a> <a href="img/p4.png">4</a> <a href="img/p5.png">5</a> <a href="img/p6.png">6</a> </p></td> <td><img src="pict/p1.jpg" alt="Фото"> <p id="bu2"> <a href="pict/p1.jpg" class="curs">1</a> <a href="pict/p2.jpg">2</a> <a href="pict/p3.jpg">3</a> <a href="pict/p4.jpg">4</a> </p></td> <td><img src="canv/p1.jpg" alt="Фото"> <p id="bu3"> <a href="canv/p1.jpg" class="curs">1</a> <a href="canv/p2.jpg">2</a> <a href="canv/p3.jpg">3</a> <a href="canv/p4.jpg">4</a> <a href="canv/p5.jpg">5</a> </p></td> </tr> </table> </div> </body> </html> 193 Обратите внимание: мы написали программу, которая совершенно не зависит от количества галерей на странице. У нас их три. А могло быть две, или пять, или любое другое количество. Сколько бы фотогалерей мы ни добавили, сценарий останется неизменным. Данную программу легко применить в своих разработках. Главное, чтобы контейнеры p для фото имели в id латинские буквы, bu и порядковый номер, а таблица — необходимое количество ячеек. 194 7. Подведем итоги Любопытные тенденции наблюдаются среди нынешних специалистов: им очень хочется, чтобы уровень входа в профессию web-разработчика был как можно выше. Что это значит? Чем труднее начинающему освоить тот или иной язык программирования, тем выше уровень входа в среду программистов данного языка. Что очень выгодно «действующим лицам»: чем сложнее начинающим, тем меньшее число из них станет профессионалами. А это означает низкий уровень конкуренции, гарантии сохранения рабочего места и высокую зарплату тем, кто уже «при деле». Однако среда web-разработки тем и привлекательна, что у нее низкий уровень входа. Действительно, чтобы создать web-страницу, достаточно примитивного редактора «Блокнот». В нем вы можете написать HTML-код, таблицу стилей и сценарий на JavaScript. Не нужно устанавливать компилятор или интерпретатор. Все необходимое для запуска такой страницы содержится в «недрах» любого современного браузера. Да и язык JavaScript прост в изучении и доступен любому, кто хочет его освоить. В основных поисковых системах типа Яндекс или Google очень популярен запрос «скачать JavaScript». Многие из интересующихся программированием даже не подозревают, что ничего скачивать не надо — JavaScript уже есть на вашем компьютере. Обработчик кода этого языка изначально встроен в ваш браузер, какой бы «марки» он у вас ни был. Поэтому разработка страниц сайта — один из самых интересных и легких процессов в программировании. Конечно, в идеале было бы хорошо создать на своем ПК полноценную среду разработки, как у профессионалов, а не пользоваться «Блокнотом». Сделать это совсем не трудно, в чем вы могли убедиться, прочитав главу 1. Надеюсь, что отныне такая среда разработки есть и на вашем ПК. Верстку шаблонов страниц, внедрение в документ таблиц стилей и сценариев на JavaScript мы освоили в главе 3. С особенностями, нюансами и тонкостями процесса объявления переменных, регистрации обработчиков, написания функций вы познакомились в главе 4. Думаю, это была информация, которую редко можно найти в других книгах по программированию на JavaScript. Глава 5 поведала вам о том, как оптимизировать, проверять и настраивать программы. Следуя рекомендациям, приведенным в этой главе, вы научились создавать сценарии, полностью соответствующие стандартам языка и не содержащие ни единой ошибки. 195 Наконец, в главе 6 мы рассмотрели несколько действующих сценариев, что полезно с практической точки зрения. Теперь вы вооружены ценными знаниями и опытом. А это залог ваших будущих успехов в web-программировании. 196 8. Об авторе Валерий Викторович Янцев Окончил Московский институт радиотехники, электроники и автоматики по специальности «Инженер электронной техники». Автор одного изобретения. Опубликовал около 80 научно-популярных и технических статей по электронике. Программированием занимается с 2003 г. Владеет HTML, CSS, JavaScript, PHP, Perl, C++. Работал в нескольких студиях web-дизайна. В качестве фрилансера создал около 50 сайтов для различных компаний, в одиночку выполнил несколько крупных коммерческих проектов. В последние годы приоритетным для него является написание сценариев на JavaScript. 197 Валерий Викторович ЯНЦЕВ JAVASCRIPT КАК ПИСАТЬ ПРОГРАММЫ Учебное пособие Зав. редакцией литературы по информационным технологиям и системам связи О. Е. Гайнутдинова Ответственный редактор Н. А. Кривилёва Корректор Л. Ю. Киреева Выпускающий В. А. Плотникова ЛР № 065466 от 21.10.97 Гигиенический сертификат 78.01.10.953.П.1028 от 14.04.2016 г., выдан ЦГСЭН в СПб Издательство «ЛАНЬ» [email protected]; www.lanbook.com 196105, СанктПетербург, пр. Юрия Гагарина, д.1, лит. А. Тел.: (812) 3362509, 4129272. Бесплатный звонок по России: 88007004071 Подписано в печать 29.10.21. Бумага офсетная. Гарнитура Школьная. Формат 70×100 1/16. Печать офсетная/цифровая. Усл. п. л. 16,25. Тираж 30 экз. Заказ № 128821. Отпечатано в полном соответствии с качеством предоставленного оригиналмакета в АО «Т8 Издательские Технологии» 109316, г. Москва, Волгоградский пр., д. 42, к. 5. Powered by TCPDF (www.tcpdf.org)