• ГЛАВА 20. Web-формы и элементы управления
  • Web-формы и элементы управления HTML
  • Назначение Web-форм и элементов управления. Серверные приложения
  • Создание Web-форм и элементов управления 
  • Создание Web-форм
  • Создание элементов управления 
  • Поле ввода
  • Поле ввода пароля
  • Поле ввода значения для поиска
  • Область редактирования
  • Кнопка
  • Флажок
  • Переключатель
  • Список, обычный или раскрывающийся
  • Надпись
  • Группа
  • Прочие элементы управления
  • Специальные селекторы CSS, предназначенные для работы с элементами управления
  • Работа с элементами управления
  • Свойства и методы объекта HTMLElement, применяемые для работы с элементами управления
  • Свойства и методы объекта Element, применяемые для работы с элементами управления
  • События элементов управления
  • Реализация поиска на Web-сайте 
  • Подготовка базы данных
  • Создание Web-формы
  • Написание Web-сценария, выполняющего поиск
  • ГЛАВА 21. Свободно позиционируемые элементы Web-страницы 
  • Свободно позиционируемые контейнеры 
  • Понятие свободно позиционируемого элемента Web-страницы
  • Создание свободно позиционируемых элементов
  • Средства библиотеки Ext Core для управления свободно позиционируемыми элементами
  • Реализация усовершенствованного поиска 
  • Создание контейнера с Web-формой поиска
  • Написание Web-сценария, выполняющего поиск
  • ГЛАВА 22. Программируемая графика 
  • Канва
  • Контекст рисования
  • Рисование простейших фигур
  • Задание цвета, уровня прозрачности и толщины линий
  • Рисование сложных фигур 
  • Как рисуются сложные контуры
  • Перо. Перемещение пера
  • Прямые линии
  • Дуги
  • Кривые Безье
  • Прямоугольники
  • Задание стиля линий
  • Вывод текста
  • Использование сложных цветов 
  • Линейный градиентный цвет
  • Радиальный градиентный цвет
  • Графический цвет
  • Вывод внешних изображений
  • Создание тени у рисуемой графики
  • Преобразования системы координат 
  • Сохранение и загрузка состояния
  • Перемещение начала координат канвы
  • Поворот системы координат
  • Изменение масштаба системы координат
  • Управление наложением графики
  • Создание маски
  • Создание графического логотипа Web-сайта
  • ЧАСТЬ 5. Последние штрихи

    ГЛАВА 20. Web-формы и элементы управления

    В предыдущих главах мы проделали большую работу. Во-первых, реализовали подгрузку фрагментов содержимого Web-страницы вместо загрузки целых Web-страниц. Во-вторых, сделали часть содержимого Web-страниц, а именно полосу навигации, генерируемой Web-сценарием. В-третьих, создали у всех Web-страниц, описывающих теги HTML, атрибуты стиля CSS и примеры, раздел "См. также", содержащий гиперссылки на Web-страницы со связанными материалами. Попутно мы узнали о базах данных и семантической разметке, без которых создать все это было бы крайне проблематично.

    Но многое еще предстоит выполнить. В частности, реализовать поиск по Web-сайту. Идея была такой: посетитель вводит искомое слово — название тега или атрибута стиля или часть его названия, особый Web-сценарий ищет это слово в базе данных и выводит на Web-страницу гиперссылки на найденные Web-страницы.

    Чтобы реализовать поиск, нам понадобятся:

    — какие-либо средства, которые примут у посетителя искомое слово;

    — Web-сценарий, который будет, собственно, выполнять поиск и формировать его результаты;

    — элемент Web-страницы, куда будут выводиться результаты поиска.

    С последним пунктом все просто. Мы создадим либо абзац, либо список, либо контейнер, где будет формироваться набор гиперссылок на искомые Web-страницы. С Web-сценарием, который будет выполнять поиск, тоже не должно возникнуть сложностей — достаточно просмотреть массивы, формирующие базу данных, и отобрать из них те элементы, что описывают подходящие Web-страницы.

    Но как нам принять от посетителя искомое слово? Ясно, что для этого понадобится создать на Web-странице набор элементов управления как в Windows- приложениях: полей ввода, списков, флажков, переключателей и кнопок. Но как это сделать?

    Web-формы и элементы управления HTML

    Очень просто. Язык HTML предоставляет набор тегов для создания разнообразных элементов управления. Эти элементы управления уже "умеют" откликаться на действия посетителя: поля ввода — принимать введенные символы, флажки — устанавливаться и сбрасываться, переключатели — переключаться, списки — прокручиваться, выделять пункты, разворачиваться и сворачиваться, а кнопки — нажиматься. Всем этим будет заниматься Web-обозреватель; нам самим ничего делать не придется.

    Набор элементов управления, поддерживаемый HTML, невелик. Он включает поля ввода различного назначения, область редактирования, обычный и раскрывающийся список, флажок, переключатель, обычную и графическую кнопку. Более сложные элементы управления (таблицы, "блокноты" с вкладками, панели инструментов и пр.) так просто создать не получится. Хотя, как правило, для создания простых Web-приложений перечисленного ограниченного набора вполне достаточно.

    НА ЗАМЕТКУ

    Существуют JavaScript-библиотеки для создания сложных элементов управления: индикаторов прогресса, регуляторов, "блокнотов", таблиц, панелей инструментов, меню и даже "лент" в стиле Microsoft Office 2007 и "окон". К таким библиотекам можно отнести, в частности, Ext, основанную на знакомой нам Ext Core.

    Стандарт HTML требует, чтобы все элементы управления находились внутри Web-формы. Web-форма — это особый элемент Web-страницы, служащий "вместилищем" для элементов управления. На Web-странице она никак не отображается (если, конечно, мы не зададим для нее какого-либо представления); в этом Web-форма схожа с блочным контейнером. Для создания Web-формы HTML предусматривает особый тег.

    Стандарт HTML требует, чтобы каждый элемент управления имел уникальное в пределах Web-формы имя. Это нужно для успешного формирования пар данных перед отправкой их серверному приложению.

    Назначение Web-форм и элементов управления. Серверные приложения

    Стандарт HTML поддерживал Web-формы и элементы управления еще до появления Web-сценариев и языка JavaScript. Но зачем?

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

    Функциональность таких Web-сайтов реализуется с помощью особых программ, которые работают на серверном компьютере совместно с Web-сервером, — серверных приложений. Именно они обрабатывают полученные от посетителя Web-сайта данные и выдают результат в виде обычной Web-страницы. Именно для них в HTML предусмотрена возможность создания Web-форм и элементов управления — чтобы посетитель мог ввести данные, которые потом обработает серверное приложение.

    Вот основная схема работы серверного приложения.

    — Посетитель вводит в элементы управления, расположенные в Web-форме на Web-странице, нужные данные.

    — Введя данные, посетитель нажимает расположенную в той же Web-форме особую кнопку — кнопку отправки данных.

    — Web-форма кодирует введенные в нее данные и отправляет их серверному приложению, расположенному по указанному интернет-адресу.

    — Web-сервер перехватывает отправленные данные, запускает серверное приложение и передает данные ему.

    — Серверное приложение обрабатывает полученные данные.

    — Серверное приложение формирует Web-страницу с результатами обработки данных посетителя и передает ее Web-серверу.

    — Web-сервер получает сформированную серверным приложением Web-страницу и отправляет ее посетителю.

    Для того чтобы успешно подготовить введенные посетителем данные и отправить их серверному приложению, Web-форма должна "знать" значения трех параметров.

    — Интернет-адрес серверного приложения. Это обычный интернет-адрес, указывающий на файл серверного приложения, вида http://www.somesite.ru/apps/app.exe.

    — Метод отправки данных, указывающий вид, в котором данные будут отправлены. Таких методов HTML поддерживает два.

    Метод GET формирует из введенных посетителем данных набор пар вида

    <имя элемента управления>=<введенные в него данные>. (Ранее уже говорилось, что каждый элемент управления обязательно должен иметь уникальное в пределах Web-формы имя.) Эти пары добавляются справа к интернет- адресу серверного приложения, отделяясь от него символом? (вопросительный знак); сами пары разделяются символами & (амперсанд). Полученный таким образом интернет-адрес отправляется Web-серверу, который извлекает из него интернет-адрес серверного приложения и сами данные.

    Метод POST также формирует из введенных данных пары вида <имя элемента управления>=<введенные в него данные>. Но отправляет он их не в составе интернет-адреса, а вслед за ним, в качестве дополнительных данных.

    — Метод кодирования данных. Он актуален только при отправке данных методом POST; для метода GET его можно не указывать.

    Все это имеет смысл только в том случае, если мы создаем Web-форму для отправки данных серверному приложению. Поскольку книга посвящена исключительно клиентским интернет-технологиям, мы не будем подробно рассматривать кодирование и пересылку данных. Эти сведения можно найти в любой книге по HTML, благо от версии к версии этого языка они практически не меняются.

    ВНИМАНИЕ!

    Далее мы будем рассматривать только те возможности Web-форм и элементов управления, которые полезны исключительно при клиентском Web-программировании. Возможности, необходимые для взаимодействия с серверными приложениями, мы опустим.

    Создание Web-форм и элементов управления 

    Настала пора рассмотреть средства языков HTML и CSS, предназначенные для создания Web-форм и элементов управления, и возможности объектов Web-обозревателя и библиотеки Ext Core для работы с ними. Их довольно много.

    Создание Web-форм

    Для создания Web-формы применяется парный тег <FORM>, внутри которого помещают теги, формирующие элементы управления, входящие в эту Web-форму:

    <FORM>

    <теги, формирующие элементы управления>

    </FORM>

    Web-форма ведет себя как блочный элемент Web-страницы. (О блочных элементах см. главу 2.)

    Тег <FORM> поддерживает обязательный атрибут ACTION, который указывает интернет-адрес серверного приложения. Если Web-форма служит для ввода данных, предназначенных для обработки Web-сценарием, в качестве значения этого атрибута тега указывают "пустой" интернет-адрес #:

    <FORM ACTION="#">

    .

    </FORM>

    Создание элементов управления 

    Большинство элементов управления HTML создают посредством одинарного тега <INPUT>. Какой именно элемент управления следует создать, указывают с помощью необязательного атрибута TYPE этого тега. Некоторые элементы управления, такие как область редактирования и списки, создают с помощью других тегов. Мы обязательно их рассмотрим.

    Все эти теги поддерживают уже знакомые нам атрибуты ID, CLASS и STYLE. Следовательно, мы можем дать элементу управления имя, по которому сможем получить к нему доступ из Web-сценария, привязать к нему именованный стиль или стилевой класс и задать для него встроенный стиль.

    Ранее было сказано, что на основе данных, введенных в элементы управления, Web-форма, в которой эти элементы управления находятся, сформирует пары вида

    <имя элемента управления>=<введенные в него данные>, которые отправит серверному приложению. Так вот, имя элемента управления, которое будет фигурировать в этих парах, задается атрибутом тега NAME — не ID! Это обязательный атрибут тега — если его не указать, при работе с элементом управления возможны проблемы.

    Что касается атрибута тега ID, то он задает имя, под которым элемент управления будет доступен в Web-сценариях, а также имя именованного стиля. Собственно, об этом мы уже знаем.

    Обычно для каждого элемента управления атрибутами тега ID и NAME указывают одно и то же имя — просто чтобы не ломать голову и устранить разнобой в именах. Хотя это и не обязательно.

    Все элементы управления HTML представляют собой встроенные элементы Web-страницы (см. главу 3).

    Теперь рассмотрим все элементы управления HTML и особенности их создания.

    Поле ввода

    Поле ввода — наиболее распространенный элемент управления в Web-формах — создается с помощью одинарного тега <INPUT>:

    <INPUT [TYPE="text"] [VALUE="<изначальное значение>"] [SIZE="<размер>"] [MAXLENGTH="<максимальное количество символов>"] [DISABLED] [TABINDEX="<номер в порядке обхода>"] [ACCESSKEY="<быстрая клавиша>"] [READONLY] [AUTOFOCUS]>

    Атрибут тега TYPE, как уже говорилось, задает тип элемента управления. Значение "text" указывает Web-обозревателю создать именно поле ввода. Поле ввода также создается, если атрибут тега TYPE не указан (как уже говорилось, он необязательный).

    Необязательный атрибут тега VALUE задает значение, которое должно присутствовать в поле ввода изначально. Если этот атрибут не указан, поле ввода не будет содержать ничего.

    Необязательный атрибут тега SIZE задает длину поля ввода в символах. Если он не указан, длина поля ввода будет зависеть от Web-обозревателя.

    Необязательный атрибут тега MAXLENGTH задает максимальный размер строки, которую можно ввести в это поле ввода, в символах. Если этот атрибут тега не указан, в поле ввода можно будет ввести строку неограниченного размера.

    Необязательные атрибуты тега TABINDEX и ACCESSKEY задают, соответственно, номер в порядке обхода и "горячую" клавишу для доступа к элементу управления. Они знакомы нам по гиперссылкам (см. главу 6).

    Атрибут тега без значения DISABLED позволяет сделать поле ввода недоступным для посетителя; оно будет отображаться серым цветом, и посетитель не сможет даже его активизировать. Если этот атрибут присутствует в теге, поле ввода недоступно, если отсутствует — доступно.

    Атрибут тега без значения READONLY позволяет сделать поле ввода доступным только для чтения; при этом посетитель все-таки сможет активизировать это поле, выделить содержащийся в нем текст и скопировать его в Буфер обмена. Если этот атрибут тега присутствует, поле ввода будет доступно только для чтения, если отсутствует — доступно и для чтения, и для ввода.

    Если атрибут тега без значения AUTOFOCUS присутствует, данное поле ввода будет автоматически активизировано при открытии Web-страницы. Если же он отсутствует, поле ввода активизировано не будет и посетителю придется его активизировать щелчком мышью или клавишами <Tab> или <Shift>+<Tab>.

    ВНИМАНИЕ!

    Атрибут тега AUTOFOCUS можно указывать только для одного элемента управления на всей Web-странице.

    Листинг 20.1

    <FORM ACTION="#">

    <P>Имя: <INPUT TYPE="text" ID="name1" NAME="name1" SIZE="20"

    AUTOFOCUS></P>

    <P>Фамилия: <INPUT TYPE="text" ID="name2" NAME="name2" SIZE="30"></P>

    </FORM>

    В листинге 20.1 мы создаем Web-форму с двумя полями ввода: name1 длиной 20 символов, автоматически активизирующееся при открытии Web-страницы, и name2 длиной 30 символов. Оба поля ввода имеют надписи, представляющие собой обычный текст и расположенные перед ними.

    Обратим внимание, что для размещения элементов управления в Web-форме мы использовали абзацы. Вообще, для этого можно применять любые элементы Web- страниц из уже знакомых нам: списки, таблицы, контейнеры и пр.

    Поле ввода пароля

    Поле ввода пароля ничем не отличается от обычного поля ввода за тем исключением, что вместо вводимых символов в нем отображаются точки. Такие поля ввода широко применяют для запроса паролей и других конфиденциальных данных.

    Поле ввода пароля также создается с помощью одинарного тега <INPUT>:

    <INPUT TYPE="password" [VALUE="<изначальное значение>"] [SIZE="<размер>"] [MAXLENGTH="<максимальное количество символов>"] [TABINDEX="<номер в порядке обхода>"] [ACCESSKEY="<быстрая клавиша>"] [DISABLED] [READONLY] [AUTOFOCUS]>

    Значение "password" атрибута тега TYPE указывает Web-обозревателю создать поле ввода пароля. Остальные атрибуты нам уже знакомы по обычному полю ввода.

    Листинг 20.2

    <FORM ACTION="#">

    <P>Имя: <INPUT TYPE="text" ID="login" NAME="login" SIZE="20" AUTOFOCUS></P>

    <P>Пароль: <INPUT TYPE="password" ID="password" NAME="password" SIZE="20"></P>

    </FORM>

    В листинге 20.2 мы создаем Web-форму с обычным полем ввода и полем ввода пароля. Первое — login, длиной 20 символов, будет автоматически активизироваться при открытии Web-страницы. Второе — password, длиной также 20 символов.

    Поле ввода значения для поиска

    Поле ввода значения для поиска появилось в HTML 5. Оно ничем не отличается от обычного поля ввода за тем исключением, что из введенного в него значения автоматически удаляются переводы строк.

    Поле ввода значения для поиска также создается с помощью одинарного тега

    <INPUT>:

    <INPUT TYPE="search" [VALUE="<изначальное значение>"] [SIZE="<размер>"] [MAXLENGTH="<максимальное количество символов>"] [TABINDEX="<номер в порядке обхода>"] [ACCESSKEY="<быстрая клавиша>"] [DISABLED] [READONLY] [AUTOFOCUS]>

    Значение "search" атрибута тега TYPE указывает Web-обозревателю создать поле ввода значения для поиска. Остальные атрибуты нам уже знакомы по обычному полю ввода (листинг 20.3).

    Листинг 20.3

    <FORM ACTION="#">

    <P>Найти: <INPUT TYPE="search" ID="keyword" NAME="keyword"

    SIZE="40"></P>

    </FORM>

    Область редактирования

    Область редактирования создается парным тегом <TEXTAREA>:

    <TEXTAREA [ROWS="<высота>"] [COLS="<ширина>"] [WRAP="off|soft|hard"] [TABINDEX="<номер в порядке обхода>"] [ACCESSKEY="<быстрая клавиша>"] [DISABLED] [READONLY] [AUTOFOCUS]><изначальное значение>

    </TEXTAREA>

    Значение, которое должно изначально присутствовать в области редактирования, помещается внутрь тега <TEXTAREA>. Это должен быть текст без всяких HTML-тегов.

    Необязательный атрибут тега ROWS задает высоту области редактирования в строках. Если он не указан, высота области редактирования будет зависеть от Web- обозревателя.

    Необязательный атрибут тега COLS задает ширину области редактирования в символах. Если он не указан, высота области редактирования будет зависеть от Web-обозревателя.

    Необязательный атрибут тега WRAP позволяет управлять переносом строк в области редактирования. Атрибут WRAP может принимать два значения:

    — "soft" — область редактирования будет автоматически выполнять перенос слишком длинных строк. При этом в само значение, введенное в область редактирования, символы перевода строк вставляться не будут.

    — "hard" — область редактирования будет автоматически выполнять перенос слишком длинных строк. При этом в соответствующие места значения, введенного в область редактирования, будут вставлены символы перевода строк.

    Если атрибут тега WRAP не указан, область редактирования будет вести себя так, словно задано значение "soft".

    Остальные атрибуты, поддерживаемые тегом <TEXTAREA>, нам уже знакомы (листинг 20.4).

    Листинг 20.4

    <FORM ACTION="#">

    <P>

    Введите сюда ваш отзыв о Web-сайте:<BR>

    <TEXTAREA ID="opinion" NAME="opinion" COLS="60" ROWS="10">

    Отличный Web-сайт!

    </TEXTAREA>

    </P>

    </FORM>

    Кнопка

    Кнопка при нажатии запускает на выполнение какое-либо действие. Она создается с помощью тега <INPUT>:

    <INPUT TYPE="button" VALUE="<надпись>"

    [TABINDEX="<номер в порядке обхода>"] [ACCESSKEY="<быстрая клавиша>"] [DISABLED] [AUTOFOCUS]>

    Значение "button" атрибута тега TYPE указывает Web-обозревателю создать обычную кнопку. Атрибут тега VALUE, задающий надпись для кнопки, в этом случае является обязательным. Остальные атрибуты тега нам уже знакомы (листинг 20.5).

    Листинг 20.5

    <FORM ACTION="#">

    <P>

    Найти:

    <INPUT TYPE="search" ID="keyword" NAME="keyword" SIZE="40">

    <INPUT TYPE="button" ID="find" NAME="find" VALUE="Искать!">

    </P>

    </FORM>

    Флажок

    Флажки встречаются в Web-формах нечасто, в случаях, когда нужно дать посетителю возможность выбрать или не выбрать какую-то опцию. Для создания флажков применяется тег <INPUT>:

    <INPUT TYPE="checkbox" [CHECKED]

    [TABINDEX="<номер в порядке обхода>"] [ACCESSKEY="<быстрая клавиша>"] [DISABLED] [AUTOFOCUS]>

    Значение "checkbox" атрибута тега TYPE указывает Web-обозревателю создать именно флажок.

    Атрибут тега без значения CHECKED позволяет сделать флажок изначально установленным. Если он присутствует, флажок будет установлен изначально, если отсутствует — сброшен.

    Остальные атрибуты тега нам уже знакомы (листинг 20.6).

    Листинг 20.6

    <FORM ACTION="#">

    <P>

    <INPUT TYPE="checkbox" ID="updates" NAME="updates" CHECKED>

    Я хочу получать письма со списком обновлений Web-сайта

    </P>

    </FORM>

    Переключатель

    Переключатели в Web-формах, как и в окнах Windows-приложений, применяются только группами. Группа переключателей предоставляет посетителю возможность выбрать одну из нескольких доступных альтернатив. В одиночку же переключатели абсолютно бесполезны — флажки в таких случаях гораздо удобнее.

    А теперь — очень важная вещь! Ранее мы говорили, что каждый элемент управления должен иметь уникальное в пределах Web-формы имя, задаваемое атрибутом тега NAME. Это имя необходимо для формирования данных, отсылаемых серверному приложению.

    Но из этого правила есть исключение — переключатели. В их случае атрибут тега NAME задает имя группы переключателей. Иными словами, переключатели, входящие в одну группу, должны иметь одинаковое имя, заданное атрибутом тега NAME, и данное имя должно быть уникально в пределах формы.

    Тем не менее, имена переключателей, задаваемые атрибутом тега ID, могут быть разными. Это позволит нам получать доступ из Web-сценария к отдельным переключателям группы и проверять, установлены они или сброшены.

    Создается переключатель с помощью все того же тега <INPUT>:

    <INPUT TYPE="radio" [CHECKED]

    [TABINDEX="<номер в порядке обхода>"] [ACCESSKEY="<быстрая клавиша>"] [DISABLED] [AUTOFOCUS]>

    Значение "radio" атрибута тега TYPE указывает Web-обозревателю создать именно переключатель. Остальные атрибуты тега нам уже знакомы.

    В группе только один переключатель может быть установлен. Это значит, что атрибут тега без значения CHECKED можно указывать только для одного переключателя в группе. Листинг 20.7 содержит пример переключателя.

    Листинг 20.7

    <FORM ACTION="#">

    <P>

    <INPUT TYPE="radio" ID="updates_yes" NAME="updates" CHECKED>

    Я хочу получать письма со списком обновлений Web-сайта

    </P>

    <P>

    <INPUT TYPE="radio" ID="updates_no" NAME="updates">

    Я не хочу получать письма со списком обновлений Web-сайта

    </P>

    </FORM>

    Список, обычный или раскрывающийся

    Списки, как обычные, так и раскрывающиеся, реализуют с помощью парного тега <SELECT>, внутри которого помещают парные теги <OPTION>, создающие пункты списка.

    Начнем с парного тега <SELECT>, который создает сам список:

    <SELECT [SIZE="<высота в пунктах (позициях)>"] [MULTIPLE] [TABINDEX="<номер в порядке обхода>"] [ACCESSKEY="<быстрая клавиша>"] [DISABLED] [AUTOFOCUS]><теги <OPTION>, создающие пункты списка>

    </SELECT>

    Необязательный атрибут тега SIZE задает высоту списка в пунктах (имеются в виду пункты списка). Если этот атрибут тега имеет значение, отличное от единицы, создается обычный список, имеющий высоту, равную указанному значению пунктов. Если же значение этого атрибута тега равно единице или вообще отсутствует, создается раскрывающийся список (имеющий в высоту один пункт).

    Атрибут тега без значения MULTIPLE позволяет создать список, в котором можно выбрать сразу несколько пунктов. Если этот атрибут тега присутствует, в списке можно будет выбрать сразу несколько пунктов, если отсутствует — можно будет выбрать только один пункт. Понятно, что данный атрибут тега имеет смысл указывать только для обычных списков (если атрибут тега SIZE имеет значение, отличное от 1); в раскрывающемся списке в любом случае можно выбрать только один пункт.

    Остальные атрибуты этого тега нам уже знакомы.

    Парный тег <OPTION> создает отдельный пункт списка. Он может присутствовать только в теге <SELECT>:

    <OPTION [LABEL="<текст пункта>"] [SELECTED] [DISABLED]> [<текст пункта>]<OPTION>

    Текст пункта списка либо помещают внутрь тега <OPTION>, либо указывают с помощью атрибута тега LABEL.

    Атрибут тега без значения SELECTED позволяет сделать данный пункт списка изначально выбранным. Если этот атрибут тега указан, пункт списка будет изначально выбранным, если не указан — не будет выбранным.

    Уже знакомый нам атрибут тега без значения DISABLED позволяет сделать данный пункт недоступным для выбора. Листинг 20.8 иллюстрирует пример.

    Листинг 20.8

    <FORM ACTION="#">

    <P>

    Выполнять поиск по

    <SELECT ID="search_in" NAME="search_in">

    <OPTION>названиям</OPTION>

    <OPTION>ключевым словам</OPTION>

    <OPTION SELECTED>названиям и ключевым словам</OPTION>

    </SELECT>

    </P>

    </FORM>

    HTML также позволяет объединять пункты списка в группы по какому-либо родственному признаку. Такую группу создают с помощью парного тега <OPTGROUP>; в него помещают теги, создающие пункты списка, которые входят в эту группу. Тег <OPTGROUP> может присутствовать только внутри тега <SELECT>:

    <OPTGROUP LABEL="<заголовок группы>" [DISABLED]>

    <теги <OPTION>, создающие пункты, которые входят в группу>

    <OPTGROUP>

    Обязательный в этом случае атрибут тега LABEL задает заголовок группы. А атрибут тега без значения DISABLED позволяет сделать все пункты данной группы недоступными для выбора (листинг 20.9).

    Листинг 20.9

    <FORM ACTION="#">

    <P>

    Выполнять поиск по

    <SELECT ID="search_in" NAME="search_in">

    <OPTGROUP LABEL="Быстрый поиск">

    <OPTION>названиям</OPTION>

    <OPTION>ключевым словам</OPTION>

    </OPTGROUP>

    <OPTION SELECTED>названиям и ключевым словам</OPTION>

    </SELECT>

    </P>

    </FORM>

    Надпись

    Строго говоря, надпись — это не элемент управления. Она просто задает для элемента управления текстовую надпись, которая описывает его назначение. Если посетитель щелкнет мышью на надписи, элемент управления будет активизирован.

    Надпись создают с помощью парного тега <LABEL>:

    <LABEL [FOR="<имя элемента управления, к которому относится надпись>"] [TABINDEX="<номер в порядке обхода>"] [ACCESSKEY="<быстрая клавиша>"]><текст надписи>[<элемент управления>]

    </LABEL>

    Есть два способа привязать надпись к элементу управления, который она должна описывать. Сейчас мы их рассмотрим.

    При первом способе (листинг 20.10) элементу управления, к которому привязывается надпись, дают имя с помощью атрибута тега ID. (Впрочем, любой элемент управления должен иметь имя.) Это имя указывают в качестве значения обязательного в таком случае атрибута FOR тега <LABEL>, создающего надпись.

    Листинг 20.10

    <FORM ACTION="#">

    <P><LABEL FOR="keyword">Найти:</LABEL>

    <INPUT TYPE="search" ID="keyword" NAME="keyword" SIZE="40"></P>

    </FORM>

    При втором способе (листинг 20.11) элемент управления, к которому привязывается надпись, помещают в сам тег <LABEL>, создающий ее, сразу после текста надписи.

    Листинг 20.11

    <FORM ACTION="#">

    <P><LABEL>Найти: <INPUT TYPE="search" ID="keyword" NAME="keyword"

    SIZE="40"></LABEL></P>

    </FORM>

    Надписи в Web-формах встречаются довольно редко. Обычно Web-дизайнеры ограничиваются простым текстом, который ставят до или после элемента управления.

    Группа

    Группу также нельзя отнести к "настоящим" элементам управления. Она объединяет несколько элементов управления, имеющих сходное назначение. Визуально группа представляет собой рамку, окружающую элементы управления и, возможно, имеющую заголовок, расположенный прямо на ее верхней или нижней границе.

    Группу создают с помощью парного тега <FIELDSET>:

    <FIELDSET>

    <элементы управления, объединяемые в группу>

    </FIELDSET>

    Видно, что теги, создающие элементы управления, которые должны быть объединены в группу, помещают прямо в тег <FIELDSET>.

    Кроме того, в теге <FIELDSET> может присутствовать парный тег <LEGEND>, создающий заголовок группы:

    <LEGEND [ACCESSKEY="<быстрая клавиша>"]><текст заголовка></LEGEND>

    Текст заголовка помещают прямо внутри этого тега.

    Тег <LEGEND> должен помещаться либо сразу же после открывающего тега <FIELDSET>, либо перед закрывающим тегом </FIELDSET>. В первом случае заголовок будет присутствовать на верхней границе группы, во втором случае — на нижней границе. В листинге 20.12 приведен пример группы.

    Листинг 20.12

    <FORM ACTION="#">

    <FIELDSET>

    <LEGEND>Найти:</LEGEND>

    <P>

    <INPUT TYPE="search" ID="keyword" NAME="keyword" SIZE="40">

    <INPUT TYPE="button" ID="find" NAME="find" VALUE="Искать!">

    </P>

    </FIELDSET>

    </FORM>

    Прочие элементы управления

    HTML позволяет создать еще несколько элементов управления, которые необходимы только для взаимодействия с серверными приложениями. Если же Web-форма служит для ввода данных, предназначенных для обработки Web-сценарием, эти элементы управления не имеют смысла.

    Прежде всего, это кнопка отправки данных, о которой мы уже говорили в начале главы. Она отличается от обычной кнопки только значением атрибута TYPE тега <INPUT> — "submit".

    Далее, в Web-форме может присутствовать кнопка очистки. При нажатии на такую кнопку все элементы управления в Web-форме получают изначальные значения, заданные в HTML-коде. Значение атрибута TYPE тега <INPUT>, создающего подобную кнопку, должно быть "reset".

    Поле ввода имени файла служит для указания имени файла, который будет отправлен серверному приложению (сам файл, а не его имя). Оно состоит из собственно поля ввода и расположенной правее его кнопки Обзор, при нажатии которой на экране появится стандартное диалоговое окно открытия файла Windows, в котором можно выбрать отправляемый файл.

    Поле ввода имени файла отличается от обычного поля ввода значением атрибута TYPE тега <INPUT> — "file". В теге <INPUT> в этом случае поддерживаются атрибуты ACCESSKEY, AUTOFOCUS, DISABLED, SIZE и TABINDEX.

    Графическая кнопка отправки данных — это графическое изображение, при щелчке на котором Web-форма запускает процесс отправки введенных данных серверному приложению. Фактически это кнопка отправки данных, в качестве которой выступает изображение.

    Графическую кнопку отправки данных создают с помощью тега <INPUT>. Значение атрибута TYPE этого тега должно быть "image". Атрибут тега SRC задает интернет- адрес файла с графическим изображением, а атрибут тега ALT — текст замены (подробнее см. в главе 4). Также поддерживаются атрибуты ACCESSKEY, AUTOFOCUS, DISABLED и TABINDEX тега <INPUT>.

    Скрытое поле — это фактически вообще не элемент управления, поскольку никак не отображается на Web-странице. Оно служит для хранения каких-либо служебных данных, необходимых для серверного приложения, показ которых посетителю нежелателен.

    Скрытое поле создают с помощью тега <INPUT>. Значение атрибута TYPE этого тега должно быть "hidden". Атрибут VALUE тега <INPUT> задает хранимое в скрытом поле значение.

    Специальные селекторы CSS, предназначенные для работы с элементами управления

    Язык CSS предоставляет несколько специальных селекторов, с помощью которых можно неявно привязать стиль к элементам управления на основе их состояния. Все они относятся к структурным псевдоклассам.

    -:enabled — привязывает стиль к элементам управления, доступным для посетителя.

    -:disabled — привязывает стиль к элементам управления, недоступным для посетителя.

    -:checked — привязывает стиль к установленным флажкам и переключателям. Листинг 20.13 иллюстрирует пример.

    Листинг 20.13

    :disabled { color: #B1BEC6 }

    :checked { font-weight: bold }

    .

    <FORM ACTION="#">

    <P>

    <INPUT TYPE="radio" ID="updates_yes" NAME="updates" CHECKED>

    Я хочу получать письма со списком обновлений Web-сайта

    </P>

    <P>

    <INPUT TYPE="radio" ID="updates_no" NAME="updates" CHECKED >

    Я не хочу получать письма со списком обновлений Web-сайта

    </P>

    <P>Почтовый адрес: <INPUT TYPE="text" ID="email" NAME="email" DISABLED></P>

    </FORM>

    Работа с элементами управления

    Толку от Web-формы немного, если вводимые в ней данные никак не обрабатываются. Поскольку мы занимаемся исключительно клиентскими интернет-технологиями, обрабатывать данные мы будем в Web-сценариях.

    А чтобы обработать в Web-сценариях данные, введенные в элементы управления, мы должны их как-то получить оттуда. Кроме того, нам пригодится возможность манипулировать элементами управления из Web-сценариев: делать их доступными и недоступными, устанавливать и сбрасывать флажки, включать переключатели, выбирать пункты списков и пр. И, поскольку львиная доля Web-сценариев — это обработчики событий, мы должны знать, какие события поддерживают элементы управления и когда они возникают.

    Вот об этом и пойдет сейчас разговор.

    Свойства и методы объекта HTMLElement, применяемые для работы с элементами управления

    Сначала мы рассмотрим самые полезные для нас свойства и методы объектов Web-обозревателя, представляющих различные элементы управления. Запомним: это именно объекты Web-обозревателя, производные от объекта HTMLElement.

    Свойство disabled позволяет сделать элемент управления доступным или недоступным для посетителя. Значение true этого свойства делает элемент управления доступным, значение false — недоступным. Листинг 20.14 иллюстрирует пример.

    Листинг 20.14

    <FORM ACTION="#">

    <P>

    <INPUT TYPE="checkbox" ID="updates" NAME="updates">

    Я хочу получать письма со списком обновлений Web-сайта

    </P>

    <P>Почтовый адрес: <INPUT TYPE="text" ID="email" NAME="email"></P>

    </FORM>

    .

    Ext.getDom("email"). disabled = false;

    Здесь мы с помощью метода getDom получаем экземпляр объекта HTMLElement, представляющий поле ввода почтового адреса email, и делаем его недоступным для ввода, присвоив свойству disabled значение false.

    Свойство readOnly позволяет сделать элемент управления доступным или недоступным для ввода. Значение true этого свойства делает элемент управления недоступным для ввода, значение false — доступным:

    Ext.getDom("email"). readOnly = false;

    Свойство value задает или возвращает значение, введенное в поле ввода или область редактирования, в виде строки:

    var sEmail = Ext.getDom("email"). value;

    Свойство checked позволяет получить или задать состояние флажка или переключателя — установлен он или нет. Значение true обозначает, что флажок или переключатель установлен, значение false — сброшен:

    Ext.get("updates"). on("click", function() { var htelEmail = Ext.getDom("email"); htelEmail.disabled = this.checked;

    });

    Здесь мы привязываем к флажку updates функцию — обработчик события click, которую тут же и объявляем. Эта функция делает доступным для посетителя поле ввода email, если флажок установлен, и недоступным — если он сброшен. Наша задача упрощается тем, что переменная this, доступная в теле функции- обработчика события и хранящая элемент Web-страницы, в котором обрабатывается событие, хранит этот элемент в виде экземпляра объекта HTMLElement. Спасибо разработчикам Ext Core!

    Еще один пример приведен в листинге 20.15.

    Листинг 20.15

    <FORM ACTION="#">

    <P>

    <INPUT TYPE="radio" ID="updates_yes" NAME="updates" CHECKED>

    Я хочу получать письма со списком обновлений Web-сайта

    </P>

    <P>

    <INPUT TYPE="radio" ID="updates_no" NAME="updates">

    Я не хочу получать письма со списком обновлений Web-сайта

    </P>

    <P>Почтовый адрес: <INPUT TYPE="text" ID="email" NAME="email"></P>

    </FORM>

    .

    Ext.get("updates_yes"). on("click", function() { var htelEmail = Ext.getDom("email"); htelEmail.disabled = this.checked;

    });

    В листинге 20.15 мы выполняем аналогичные действия, но уже с группой из двух переключателей updates2. Обратим внимание, что мы проверяем состояние только первого переключателя этой группы — updates_yes. В группе может быть включен только один переключатель, и если посетитель включит второй переключатель этой группы, первый переключатель отключится. Фактически группа из двух переключателей ведет себя как флажок.

    Свойство selectedIndex задает или возвращает номер выбранного в списке пункта в виде числа. При этом:

    — если список позволяет выбирать одновременно только один пункт, возвращается номер именно этого пункта;

    — если список позволяет выбирать сразу несколько пунктов, возвращается номер первого выбранного пункта;

    — если ни один пункт в списке не выбран, возвращается значение –1.

    Понятно, что пользы от свойства selectedIndex будет больше в том случае, если список позволяет выбирать только один пункт одновременно. Хотя в любом случае его можно применять для проверки, выбран ли в списке хоть один пункт. Листинг 20.16 иллюстрирует пример.

    Листинг 20.16

    <FORM ACTION="#">

    <P>

    Выполнять поиск по

    <SELECT ID="search_in" NAME="search_in">

    <OPTION>названиям</OPTION>

    <OPTION>ключевым словам</OPTION>

    <OPTION SELECTED>названиям и ключевым словам</OPTION>

    </SELECT>

    </P>

    </FORM>

    .

    var iIndex = Ext.getDom("search_in"). selectedIndex;

    if (iIndex == -1) {

    //если в списке не выбран ни один пункт, делаем одно

    } else {

    //если в списке выбран какой-либо пункт, делаем другое

    }

    Свойство options возвращает коллекцию пунктов списка. Эта коллекция является

    экземпляром объекта HTMLOptionsCollection:

    var clItems = Ext.getDom("search_in"). options;

    Свойство length объекта HTMLOptionsCollection возвращает число элементов в коллекции, т. е. количество пунктов в списке:

    var iItemsCount = clItems.length;

    Для доступа к отдельным пунктам в этой коллекции мы можем использовать числовые индексы, как и в случае массива:

    var htelSecondItem = clItems[1];

    Здесь мы получаем второй пункт списка.

    Отдельный пункт списка представляется экземпляром объекта HTMLOptionElement. Он поддерживает уже знакомое нам свойство disabled, позволяющее разрешить или запретить доступ к данному пункту списка.

    А еще он поддерживает свойство selected, указывающее, выбран ли данный пункт списка. Значение true обозначает, что пункт списка выбран, а значение false — не выбран. Это свойство удобно применять, чтобы выяснить, какие пункты выбраны в списке, позволяющем выбирать сразу несколько пунктов (листинг 20.17).

    Листинг 20.17

    <FORM ACTION="#">

    <P>

    С помощью каких тегов HTML формируется таблица?

    <SELECT ID="answer" NAME="answer" SIZE="5" MULTIPLE>

    <OPTION>TR</OPTION>

    <OPTION>DIV</OPTION>

    <OPTION>TABLE</OPTION>

    <OPTION>TH</OPTION>

    <OPTION>TT</OPTION>

    <OPTION>HEAD</OPTION>

    <OPTION>TD</OPTION>

    </SELECT>

    </P>

    </FORM>

    .

    var clItems = Ext.getDom("answer"). options;

    if ((clItems[0].selected) && (clItems[2].selected)

    && (clItems[3].selected) && (clItems[6].selected)) {

    var s = "Вы ответили правильно!";

    } else {

    var s = "Неправильно! Будьте внимательнее.";

    }

    В листинге 20.17 мы создали что-то наподобие онлайнового экзамена. Посетителю требуется выбрать в списке answer пункты, представляющие теги HTML, с помощью которых создаются таблицы. Если все эти пункты выбраны, ответ считается правильным.

    Свойство form возвращает экземпляр объекта HTMLElement, представляющий Web-форму, в которой находится данный элемент управления:

    var htelForm = Ext.getDom("answer"). form;

    Метод focus делает данный элемент управления активным. Он не принимает параметров и не возвращает результата:

    Ext.getDom("email"). focus();

    Метод blur делает данный элемент управления, наоборот, неактивным; при этом фокус ввода переносится на следующий в порядке обхода элемент управления. Данный метод также не принимает параметров и не возвращает результата:

    Ext.getDom("email"). blur();

    Метод select выделяет все содержимое поля ввода или области редактирования. Он не принимает параметров и не возвращает результата:

    Ext.getDom("email"). select();

    Метод click позволяет имитировать щелчок на кнопке. Он не принимает параметров и не возвращает результата (листинг 20.18).

    Листинг 20.18

    <FORM ACTION="#">

    <P>

    Найти:

    <INPUT TYPE="search" ID="keyword" NAME="keyword" SIZE="40">

    <INPUT TYPE="button" ID="find" NAME="find" VALUE="Искать!">

    </P>

    </FORM>

    .

    Ext.getDom("find"). click();

    Свойства и методы объекта Element, применяемые для работы с элементами управления

    А теперь обратимся к объекту Element библиотеки Ext Core и посмотрим, что он может предложить нам для работы с элементами управления.

    Метод getValue возвращает значение, введенное в поле ввода или область редактирования, в виде строки или числа:


    <экземпляр объекта Element>.getValue(<преобразовать в число>)

    Если этому методу передать в качестве параметра значение false, он вернет значение поля ввода или области редактирования в виде строки. Если же ему передать значение true, он попытается преобразовать это значение в число и в случае успеха вернет его; в противном случае он вернет это значение в виде строки:

    var sEmail = Ext.get("email"). getValue(false);

    Метод focus делает данный элемент управления активным. Он не принимает параметров и не возвращает результата. Если вызвать этот метод у элемента Web- страницы, не являющимся элементом управления, ничего не произойдет:

    Ext.get("email"). focus();

    Метод blur делает данный элемент управления неактивным; при этом фокус ввода переносится на следующий в порядке обхода элемент управления:

    Ext.get("email"). blur();

    Данный метод также не принимает параметров и не возвращает результата. Если вызвать его у элемента Web-страницы, не являющимся элементом управления, ничего не произойдет.

    Метод select поддерживает еще один селектор —:checked. Он соответствует всем установленным флажкам и переключателям:

    var clChecked = Ext.get("cmain"). select(":checked");

    События элементов управления

    Специфические события, поддерживаемые элементами управления, перечислены в табл. 20.1. Их немного.

    Элементы управления также поддерживают события dblclick, keydown, keypress, keyup, mousedown, mousemove, mouseout, mouseover и mouseup, описанные в табл. 15.1.

    Реализация поиска на Web-сайте 

    Теоретическая часть, посвященная Web-формам и элементам управления, закончена. Давайте попрактикуемся.

    Для практики мы реализуем давно задуманное — поиск на нашем Web-сайте. Поиск будет осуществляться на основе информации, хранящейся в базе данных, которую мы создали еще в главе 18. База данных — вещь универсальная и может пригодиться для многих дел. Мы уже убедились в этом, когда в главе 19 создавали раздел "См. также" у Web-страниц, куда поместили связанные с ними материалы.

    Чтобы усложнить себе задачу и упростить жизнь посетителям, мы реализуем поиск, во-первых, по названиям Web-страниц, во-вторых, по ключевым словам, связанным с каждой Web-страницей. Ключевым словом в данном случае называется специальным образом подобранное кодовое слово, характеризующее конкретный материал. Скажем, для материала, рассказывающего о теге <AUDIO>, ключевыми словами будут "мультимедиа" и "аудио", поскольку он описывает способ размещения на Web-страницах аудиороликов, относящихся к мультимедийным материалам.

    Далее, мы предоставим посетителю возможность выбирать критерии поиска: только по названиям, только по ключевым словам или и по названиям, и по ключевым словам. Для этого мы используем раскрывающийся список, т. к. он занимает немного места на Web-странице и вполне информативен.

    Что касается самого поиска, то реализовать его несложно. Достаточно просмотреть все три массива, составляющие нашу базу данных, найти элементы, содержащие название или ключевое слово, совпадающее с введенным посетителем значением, и скопировать их в другой массив, который будет хранить результаты поиска. Потом на основе полученного массива мы сформируем, скажем, список, пункты которого будут представлять собой гиперссылки на соответствующие Web-страницы, и вставим его в контейнер cmain. Почти как в случае раздела "См. также".

    Еще мы предусмотрим ситуацию, когда посетитель введет не все искомое название или ключевое слово, а только его начало. Соответственно, Web-сценарий, который мы напишем, будет искать элементы базы данных, начало названия или одного из ключевых слов которого совпадает с тем, что ввел посетитель.

    Что ж, основной план работ мы набросали, а детали будем прояснять по ходу дела. Начнем с базы данных.

    Подготовка базы данных

    Нам потребуется указать для каждого элемента массива, составляющего базу данных и представляющего одну из Web-страниц, список ключевых слов. Для этого мы создадим у конфигураторов — элементов этих массивов новое свойство keyword, которому и присвоим список соответствующих ключевых слов. Он будет представлять собой обычную строку с ключевыми словами, разделенными запятыми, — так его проще обрабатывать.

    Откроем файл Web-сценариев data.js и поместим после кода, создающего свойство related со связанными данными, но перед кодом, выполняющим сортировку базы, такое выражение:

    aHTML[0].keyword = "тип, версия";

    Мы взяли первый элемент массива aHTML (с индексом 0), добавили к хранящемуся в нем конфигуратору свойство keyword и присвоили этому свойству строку с ключевыми словами "тип" и "версия". Следовательно, мы указали, что Web-страницу с описанием тега <!DOCTYPE> будут характеризовать эти два ключевых слова.

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

    Создание Web-формы

    На очереди — Web-форма, в которую посетитель будет вводить искомое слово или его часть. Вот только куда ее поместить? Давайте пока что вставим ее в контейнер cnavbar, ниже полосы навигации, непосредственно перед закрывающим тегом </DIV>, формирующим этот контейнер. В главе 21 мы найдем Web-форме поиска местоположение получше.

    Наша первая "рабочая" Web-форма будет содержать следующие элементы:

    — надпись "Поиск", чтобы посетитель сразу понял, зачем нужна эта Web-форма;

    — поле ввода значения для поиска, где указывается искомое слово или начало слова;

    — кнопку, запускающую поиск;

    — раскрывающийся список для выбора режима поиска (только по названиям, только по ключевым словам или одновременно по названиям и по ключевым словам).

    Содержимое Web-формы мы поместим в один абзац, который разобьем на три строки с помощью тегов разрыва строк (см. главу 3). Первая строка будет содержать надпись, вторая — поле ввода и кнопку, третья — раскрывающийся список. Можно, конечно, разместить эти элементы в трех отдельных абзацах, но так Web-форма займет на Web-странице слишком много места.

    Поле ввода искомого слова мы назовем keyword, кнопку — find, а раскрывающийся список — search_in.

    Листинг 20.19 содержит HTML-код, создающий Web-форму.

    Листинг 20.19

    <FORM ACTION="#">

    <P>

    Поиск:<BR>

    <INPUT TYPE="search" ID="keyword" NAME="keyword" SIZE="20">

    <INPUT TYPE="button" ID="find" NAME="find" VALUE="Искать!"><BR>

    <SELECT ID="search_in" NAME="search_in">

    <OPTION>В названиях</OPTION>

    <OPTION>В ключевых словах</OPTION>

    <OPTION SELECTED>В названиях и ключевых словах</OPTION>

    </SELECT>

    </P>

    </FORM>

    Вставим его в соответствующее место файла фрагмента html.htm.

    Обязательно проверим, правильно ли наша Web-форма отображается на Web-странице. Если мы допустили в HTML-коде ошибку, лучше исправить ее прямо сейчас.

    Написание Web-сценария, выполняющего поиск

    Осталось написать Web-сценарий, который будет искать Web-страницы, удовлетворяющие заданным посетителем условиям.

    Откроем файл Web-сценария main.js и поместим где-либо в теле функции, передаваемой в качестве параметра методу onReady объекта Ext, такое выражение:

    Ext.get("find"). on("click", searchData);

    Оно привязывает к событию click кнопки find функцию-обработчик searchData, которая будет выполнять поиск и выводить его результаты и которую мы объявим чуть позже. Кнопка find созданной нами Web-формы запускает процесс поиска, а событие click, как мы уже знаем, возникает при щелчке на кнопке.

    Теперь объявим функцию searchData. Она не будет ни принимать параметры, ни возвращать результат. Объявляющий ее код (листинг 20.20) поместим где-либо перед вызовом методу onReady объекта Ext.

    Листинг 20.20

    function searchData() {

    var sKeyword = Ext.get("keyword"). getValue(false);

    if (sKeyword!= "") {

    var iSearchMode = Ext.getDom("search_in"). selectedIndex;

    var aResult = [];

    searchInArray(sKeyword, aHTML, aResult, iSearchMode); searchInArray(sKeyword, aCSS, aResult, iSearchMode); searchInArray(sKeyword, aSamples, aResult, iSearchMode); if (aResult.length > 0) {

    var s = "";

    for (var i = 0; i < aResult.length; i++) {

    s += "<LI><A HREF=\"" + aResult[i].url + "\">" +

    aResult[i].name + "</A></LI>";

    }

    var htelResult = Ext.get("cmain"). insertHtml("beforeEnd", "<P>Результаты поиска:</P><UL>" + s + "</UL>"); Ext.fly(htelResult). select("A"). on("click", function(e, t) {

    var href = Ext.fly(this). getAttribute("href");

    var elA = Ext.get("navbar"). child("A[href=" + href + "]");

    var elItem = elA.parent("LI");

    loadFragment(elItem, e);

    });

    }

    }

    }

    Рассмотрим его построчно.

    Получаем искомое слово, введенное посетителем в поле ввода keyword:

    var sKeyword = Ext.get("keyword"). getValue(false);

    Проверяем, ввел ли посетитель вообще что-либо в это поле ввода:

    if (sKeyword!= "") {

    Если ввел, получаем номер пункта, выбранного в раскрывающемся

    search_in:

    var iSearchMode = Ext.getDom("search_in"). selectedIndex;

    Объявляем массив, который будет хранить набор элементов массивов aHTML, aCSS и aSamples, имеющих название или одно из ключевых слов, начало которого совпадает с введенным посетителем словом:

    var aResult = [];

    Фактически этот массив будет хранить результаты поиска.

    Для каждого из массивов aHTML, aCSS и aSamples вызываем функцию searchInArray, которую объявим потом:

    searchInArray(sKeyword, aHTML, aResult, iSearchMode); searchInArray(sKeyword, aCSS, aResult, iSearchMode); searchInArray(sKeyword, aSamples, aResult, iSearchMode);

    Эта функция будет искать элементы в массиве, переданном ей вторым параметром, имеющие название или одно из ключевых слов, начало которого совпадает со словом, переданным первым параметром, и помещать их в массив с результатами поиска, переданный третьим параметром. Четвертым параметром этой функции передается номер пункта, выбранного в раскрывающемся списке search_in, — режим поиска.

    Проверяем, есть ли в массиве с результатами поиска хоть один элемент, т. е. увенчался ли поиск успехом:

    if (aResult.length > 0) {

    Если так, объявляем переменную, которая будет хранить строку с HTML-кодом, формирующим пункты списка с результатами поиска:

    var s = "";

    (Ранее мы договорились, что будем выводить результаты поиска на Web-страницу в виде списка HTML; каждый пункт этого списка будет содержать гиперссылку на соответствующую Web-страницу.)

    Запускаем цикл со счетчиком, который будет просматривать все элементы массива с результатами поиска:

    for (var i = 0; i < aResult.length; i++) {

    Тело этого цикла на основе каждого элемента массива с результатами поиска сформирует HTML-код, создающий пункт списка с гиперссылкой:

    s += "<LI><A HREF=\"" + aResult[i].url + "\">" +

    aResult[i].name + "</A></LI>";

    }

    На основе полученного таким образом HTML-кода создаем список с результатами поиска и помещаем его в самом конце контейнера cmain:

    var htelResult = Ext.get("cmain"). insertHtml("beforeEnd",

    "<P>Результаты поиска:</P><UL>" + s + "</UL>");

    Выбираем из этого списка все гиперссылки и привязываем к ним обработчик события click, который будет обрабатывать щелчки на этих гиперссылках и выполнять загрузку соответствующей Web-страницы:

    Ext.fly(htelResult). select("A"). on("click", function(e, t) {

    var href = Ext.fly(this). getAttribute("href");

    var elA = Ext.get("navbar"). child("A[href=" + href + "]");

    var elItem = elA.parent("LI");

    loadFragment(elItem, e);

    });

    }

    }

    Этот фрагмент кода без изменений перекочевал сюда из функции generateRelated, объявленной в главе 19. (В принципе, будет лучше оформить его в виде отдельной функции, но это вы можете сделать сами, в качестве домашнего задания.)

    На этом выполнение функции searchData заканчивается.

    Осталось объявить функцию searchInArray, которая, собственно, будет выполнять поиск в массивах, составляющих базу данных. Объявляющий код (листинг 20.21) мы поместим где-либо перед объявлением функции searchData.

    Листинг 20.21

    function searchInArray(sKeyword, aDataArray, aResultArray, iSearchMode) {

    var sN, sK;

    var sKw = "," + sKeyword.toLowerCase();

    for(var i = 0; i < aDataArray.length; i++) {

    sN = "," + aDataArray[i].name.toLowerCase();

    if (aDataArray[i].keyword)

    sK = "," + aDataArray[i].keyword.toLowerCase()

    else

    sK = "";

    if (((iSearchMode == 0) || (iSearchMode == 2)) && (sN.indexOf(sKw)!= -1))

    aResultArray[aResultArray.length] = aDataArray[i]

    else

    if (((iSearchMode == 1) || (iSearchMode == 2)) && (sK.indexOf(sKw)!= -1))

    aResultArray[aResultArray.length] = aDataArray[i];

    }

    }

    Как уже говорилось, эта функция принимает четыре параметра:

    — искомое слово в виде строки;

    — массив, составляющий базу данных, в котором будет выполняться поиск;

    — массив, в который будут помещаться результаты поиска;

    — число, обозначающее режим поиска. Фактически это номер пункта, выбранного посетителем в раскрывающемся списке search_in.

    Результат эта функция возвращать не будет.

    Давайте рассмотрим объявляющий ее код построчно, т. к. он довольно сложен, хоть и невелик по размеру.

    Объявляем служебные переменные:

    var sN, sK;

    Преобразуем полученное первым параметром искомое слово к нижнему регистру, добавляем к ней спереди запятую и сохраняем в служебной переменной для дальнейшего использования:

    var sKw = "," + sKeyword.toLowerCase();

    Посетитель — человек непредсказуемый. Кто знает, в каком регистре он наберет искомое слово — в верхнем или нижнем, прописными буквами или строчными. А названия Web-страниц нашего Web-сайта указаны как в верхнем, так и в нижнем регистре. И строка, набранная в верхнем регистре, не равна строке, содержащей те же символы, но набранные в нижнем регистре; так, строки "title" и "TITLE", хоть и содержат одни и те же символы, не равны, поскольку эти символы набраны в разных регистрах.

    Выход — перед сравнением строк принудительно преобразовать их к какому-либо одному регистру, скажем, к нижнему. В этом нам поможет метод toLowerCase объекта JavaScript String. Он как раз возвращает строку, равную той, для которой он вызван, но набранную в нижнем регистре. Параметров он не принимает.

    Но зачем добавлять к искомой строке спереди запятую? Давайте разберемся. Предположим, посетитель захотел найти материалы по тегу <IMG>. Причем посетитель попался на редкость ленивый, и, вместо того чтобы набрать имя тега полностью, ввел только букву "I".

    Средства JavaScript позволяют узнать, присутствует ли в какой-либо строке указанная подстрока. (Как мы потом узнаем, за это "отвечает" особый метод объекта String.) Другими словами, приняв за подстроку введенное посетителем искомое слово, мы с помощью этих средств можем легко узнать, присутствует ли оно в названии или списке ключевых слов какого-либо элемента базы данных. Так, мы выясним, что в строке "IMG" присутствует подстрока "I", а в строке"!DOCTYPE" — нет.

    Но ведь подстрока "I" присутствует и в строках "AUDIO", "VIDEO" и "TITLE"! А мы решили, что будем выбирать только те материалы, начало названий или ключевых слов содержит указанное слово. Начало, а не середина или конец! К сожалению, средства JavaScript не позволяют указать, в какой именно части слова должна присутствовать искомая подстрока…

    Чтобы решить возникшую проблему, мы пойдем на небольшую хитрость. Мы добавим в начало искомого слова, названия и списка ключевых слов каждой Web- страницы какой-нибудь символ, например, запятую. А уже после этого будем выполнять сам поиск.

    Например, если мы добавим к строкам "I", "IMG" и "AUDIO" спереди запятую, то получим",I", ",IMG" и",AUDIO". Посмотрим, что получится: строка",IMG" содержит подстроку",I", а",AUDIO" — не содержит. Принятое нами правило поиска — указанное слово должно содержаться в начале названия — теперь выполняется. Как говорится, не мытьем, так катаньем. Ладно, поехали дальше…

    Запускаем цикл со счетчиком, который будет просматривать все элементы массива базы данных, переданного вторым параметром:

    for(var i = 0; i < aDataArray.length; i++) {

    В теле этого цикла мы получаем название очередного элемента этого массива, преобразуем его к нижнему регистру, добавляем к нему спереди запятую и присваиваем объявленной ранее служебной переменной:

    sN = "," + aDataArray[i].name.toLowerCase();

    Проверяем, есть ли у данного элемента свойство keyword:

    if (aDataArray[i].keyword)

    sK = "," + aDataArray[i].keyword.toLowerCase()

    else

    sK = "";

    (Ранее мы решили, что оно будет необязательным.) Если оно есть, преобразуем его значение — список ключевых слов — к нижнему регистру, добавляем к нему спереди запятую и присваиваем объявленной ранее служебной переменной. Если его нет, присваиваем той же служебной переменной пустую строку — "пустой" список ключевых слов.

    Если посетитель выбрал первый или третий пункты раскрывающегося списка search_in (т. е. если указан режим поиска по названиям или по названиям и ключевым словам; номер выбранного пункта списка search_in передается четвертым параметром) и если в названии элемента массива присутствует указанное посетителем слово:

    if (((iSearchMode == 0) || (iSearchMode == 2)) && (sN.indexOf(sKw)!= -1))

    мы присваиваем этот элемент новому элементу массива результатов, переданного третьим параметром:

    aResultArray[aResultArray.length] = aDataArray[i]

    Чтобы добавить к массиву новый элемент, нужно дать ему индекс, на единицу больший индекса его последнего уже существующего элемента. В качестве этого индекса удобно использовать размер массива — ведь он всегда на единицу больше индекса последнего элемента массива (конечно, при условии, что индексы всех элементов данного массива представляют собой непрерывающуюся последовательность чисел, причем каждое следующее число больше предыдущего на единицу).

    Если посетитель выбрал второй или третий пункты раскрывающегося списка search_in (т. е. если указан режим поиска по ключевым словам или по названиям и ключевым словам) и если в списке ключевых слов элемента массива присутствует указанное посетителем слово

    else

    if (((iSearchMode == 1) || (iSearchMode == 2)) && (sK.indexOf(sKw)!= -1))

    мы присваиваем этот элемент новому элементу массива результатов, переданного третьим параметром:

    aResultArray[aResultArray.length] = aDataArray[i];

    }

    На этом выполнение тела цикла и тела функции searchInArray заканчивается.

    Что ж, поиск готов. Откроем наш Web-сайт, наберем в поле ввода какое-либо слово и нажмем кнопку Искать!. Если поиск увенчается успехом, в самом конце контейнера cmain мы увидим список, пункты которого будут содержать гиперссылки на найденные Web-страницы.

    Поиск работает!

    Что дальше?

    В этой главе мы познакомились с Web-формами и элементами управления, тегами HTML для их создания и средствами объектов Web-обозревателя и библиотеки Ext Core для работы с ними. А еще мы наконец-то реализовали поиск на своем Web- сайте!

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

    ГЛАВА 21. Свободно позиционируемые элементы Web-страницы 

    В предыдущей главе мы познакомились с Web-формами и элементами управления, HTML-тегами для их создания и средствами объектов Web-обозревателя и библиотеки Ext Core для работы с ними. На основе этих элементов управления и базы данных мы создали систему поиска для своего Web-сайта. Наш небольшой Web-сайтик теперь выглядит просто шикарно!

    Только вот поиск у нас вышел какой-то корявый, больше похожий не на готовое решение, а на экспериментальный "прибамбас", который со временем переделают во что-либо более приемлемое (или уберут совсем). Давайте сами посмотрим на него критическим взглядом.

    — Контейнер с полосой навигации — не лучшее место для Web-формы поиска. То, что она нарушает дизайн Web-страницы, не страшно — задать для нее подходящее представление не составит для нас труда. Хуже другое — полоса навигации в какой-то момент станет слишком большой, не поместится в контейнер, Web- форма "уедет" вниз, и посетителю, чтобы до нее добраться, придется пользоваться полосами прокрутки. А ведь он должен догадаться, что Web-форма поиска еще присутствует на Web-странице, а не пропала бесследно и невесть куда!

    — Низ контейнера с основным содержимым тоже плохо подходит для размещения результатов поиска: основное содержимое может оказаться слишком большим, чтобы поместиться в контейнер полностью, и посетителю придется пользоваться полосами прокрутки, чтобы добраться до результатов поиска.

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

    В принципе, некоторые из перечисленных проблем можно решить известными нам средствами. Как уже говорилось, для Web-формы мы можем создать представление, "облагораживающее" ее. Мы способны выявлять, присутствует ли уже в основном содержимом список с результатами поиска, и удалять его; для этого мы можем привязать к списку стилевой класс или дать ему имя, по которому сможем его найти.

    Но куда выводить результаты поиска, чтобы посетитель сразу их увидел? Напрашивается вывод: создать на Web-странице еще один блочный контейнер, поместить в него Web-форму и туда же выводить результаты поиска. Но дополнительный контейнер займет определенное место на Web-странице, может быть, слишком много места. А значит, останется меньше пространства для остальных контейнеров.

    Мы можем сделать этот контейнер изначально небольшим, ровно таким, чтобы вместить только Web-форму. Если же поиск увенчается успехом, мы увеличим размер контейнера, чтобы он вместил список с результатами. Но тогда остальные контейнеры будут "ездить" по Web-странице туда-сюда, чем немало развеселят посетителей. Хорошее настроение — оно, конечно, замечательно, но все равно это не выход.

    Свободно позиционируемые контейнеры 

    Давайте вернемся назад, к языкам HTML и CSS, и посмотрим, не предложат ли они нам что-либо, радикально решающее эту проблему. Так и есть!

    Понятие свободно позиционируемого элемента Web-страницы

    Откроем любую из созданных нами ранее Web-страниц и посмотрим на нее. Что мы видим?

    Прежде всего, расположением элементов этих Web-страниц управляет сам Web-обозреватель. При этом он руководствуется следующими правилами.

    — Элемент выводится на экран в том месте, в котором находится определяющий его HTML-код. Так, контейнер cheader мы определили в самом начале HTML-кода Web-страницы index.htm, поэтому он будет выведен в самом ее начале, т. е. в верхней части.

    — Если для элементов задано значение none атрибута стиля float (см. главу 10) или этот атрибут стиля вообще отсутствует, то элементы выстроятся друг за другом по вертикали. Пример: контейнеры cheader и cnavbar, для которых мы не указали этот атрибут стиля.

    — При других значениях атрибута стиля float элементы выстроятся по горизонтали. Пример: контейнеры cnavbar и cmain, для которых мы задали значение left атрибута стиля float.

    Произвольно управлять местоположением элементов Web-страницы в этом случае мы не можем. Поэтому такие элементы называются непозиционируемыми.

    Web-дизайнерам и особенно Web-программистам такое положение дел не нравилось. Именно поэтому уже довольно давно в языке CSS появилась возможность создавать свободно позиционируемые, или свободные, элементы Web-страницы.

    Подобный элемент может располагаться где угодно на Web-странице, независимо от места в HTML-коде, где стоит определяющий его тег.

    Рассмотрим особенности свободно позиционируемых элементов Web-страницы.

    — Местоположение свободно позиционируемого элемента задается произвольно в виде горизонтальной и вертикальной координат его верхнего левого угла. Координаты задают относительно верхнего левого угла родителя данного элемента.

    — Под свободно позиционируемый элемент на Web-странице место не выделяется.

    — Свободно позиционируемые элементы находятся "выше" обычного содержимого Web-страницы, как бы "плавают" над ним и перекрывают его.

    — Свободно позиционируемые элементы могут перекрывать друг друга. Обычное содержимое Web-страницы свободные элементы перекрывают в любом случае.

    — Слово "перекрывают" в предыдущих двух пунктах обозначает, что содержимое Web-страницы, находящееся под свободным элементом, не будет видно — его скроет свободный элемент.

    — Свободно позиционируемые элементы могут иметь любое содержимое, в том числе и другие свободно позиционируемые элементы.

    Существующая реализация CSS позволяет сделать свободно позиционируемыми только блочные контейнеры. В этом случае говорят о свободно позиционируемых, или свободных, контейнерах.

    Создание свободно позиционируемых элементов

    Свободные элементы Web-страницы создают с помощью особых атрибутов стиля CSS, которые мы сейчас рассмотрим.

    Самый важный атрибут стиля — position. Он задает способ позиционирования элемента Web-страницы:

    position: static|absolute|relative|fixed|inherit

    Этот атрибут стиля может принимать четыре значения:

    — static — контейнер непозиционируемый (поведение по умолчанию);

    — absolute — элемент Web-страницы свободно позиционируемый. Его координаты задаются относительно верхнего левого угла родителя. Место на Web- странице под такой элемент не выделяется. Если содержимое родителя прокручивается, свободно позиционируемый элемент будет перемещаться вместе с ним;

    — relative — элемент Web-страницы относительно позиционируемый. Его координаты отсчитываются относительно точки, в которой он находился бы, если был непозиционируемым. На Web-странице выделяется место под такой элемент;

    — fixed — элемент Web-страницы фиксированно позиционируемый. Он ведет себя как свободный элемент, с двумя исключениями. Во-первых, его координаты задаются относительно верхнего левого угла Web-страницы. Во-вторых, если содержимое родителя прокручивается, фиксированно позиционируемый элемент не будет перемещаться вместе с ним.

    Пример:

    #search { position: absolute }

    Здесь мы превратили контейнер search в свободно позиционируемый.

    Атрибуты стиля left и top задают, соответственно, горизонтальную и вертикальную координаты верхнего левого угла свободно, относительно или фиксированно позиционируемого элемента Web-страницы:

    left|top: <значение>|auto|inherit

    Значения координат можно указывать в любых единицах измерения, поддерживаемых стандартом CSS (см. табл. 8.1). Значение auto возвращает управление соответствующей координатой Web-обозревателю.

    В примере из листинга 21.1 мы задали координаты и размеры контейнера search.

    Листинг 21.1

    #search { position: absolute; left: 200px; top: 100px; width: 300px; height: 200px }

    Мы уже знаем, что свободные элементы могут перекрывать друг друга. При этом элемент, определенный в HTML-коде позже, перекрывает элемент, определенный раньше. Однако мы можем сами задать порядок их перекрытия друг другом, указав так называемый z-индекс. Он представляет собой целое число, указывающее номер в порядке перекрытия; при этом элементы с бoльшим z-индексом перекрывают элементы с меньшим z-индексом. Z-индекс задается атрибутом стиля с "говорящим" именем z-index:

    z-index: <номер>|auto|inherit

    Как уже говорилось, z-индекс указывается в виде целого числа. Значение auto возвращает управление порядком перекрытия Web-обозревателю. Листинг 21.2 иллюстрирует пример.

    Листинг 21.2

    #search { position: absolute; left: 200px; top: 100px; width: 300px;

    height: 200px; z-index: 2 }

    #main { position: absolute; left: 100px; top: 0px; width: 600px; height: 500px;

    z-index: 0 }

    Контейнер search перекроет контейнер main, поскольку для него задан больший

    z-индекс.

    Еще один атрибут стиля, который иногда может быть полезен, — clip. Он определяет координаты прямоугольной области, задающей видимую область свободного элемента. Фрагмент содержимого элемента, попадающий в эту область (ее, кстати, называют маской), будет видим на Web-странице, остальная часть содержимого будет скрыта.

    Вот синтаксис записи атрибута clip:

    clip: rect(<верхняя граница>, <правая граница>, <нижняя граница>,<левая граница>)|auto|inherit

    Здесь:

    — верхняя граница — расстояние от верхней границы свободного элемента до верхней границы маски по вертикали;

    — правая граница — расстояние от левой границы свободного элемента до правой границы маски по горизонтали;

    — нижняя граница — расстояние от верхней границы свободного элемента до нижней границы маски по вертикали;

    — левая граница — расстояние от левой границы свободного элемента до левой границы маски по горизонтали.

    Значение auto атрибута стиля clip убирает маску и тем самым делает все содержимое свободного элемента видимым. Это поведение по умолчанию. Листинг 21.3 иллюстрирует пример.

    Листинг 21.3

    #search { position: absolute; left: 200px; top: 100px; width: 300px; height: 200px; z-index: 2;

    clip: rect(100px, 200px, 200px, 0px) }

    Средства библиотеки Ext Core для управления свободно позиционируемыми элементами

    Настала пора рассмотреть методы объекта Element библиотеки Ext Core, с помощью которых мы можем управлять свободно позиционируемыми элементами Web- страницы. Их немного.

    Метод position задает способ позиционирования, z-индекс и координаты данного элемента:

    <экземпляр объекта Element>.position(<способ позиционирования>[, <z-индекс>[, <горизонтальная координата>[, <вертикальная координата>]]])

    Первым — обязательным — параметром передается соответствующее значение атрибута стиля position в виде строки: "absolute", "relative" или "fixed". Остальные — необязательные — параметры определяют, соответственно, z-индекс, горизонтальную и вертикальную координаты в пикселах; все эти значения задаются в виде чисел.

    Пример:

    var elSearch = Ext.get("search");

    elSearch.position("absolute", 2);

    Методы setX и setY задают, соответственно, горизонтальную и вертикальную координаты данного элемента относительно верхнего левого угла Web-страницы:

    <экземпляр объекта Element>.setX|setY(<значение координаты>)

    Значение координат указывают в пикселах в виде числа:

    elSearch.setX(400);

    elSearch.setY(200);

    Метод setLocation задает сразу обе координаты данного элемента также относительно верхнего левого угла Web-страницы:

    <экземпляр объекта Element>.setLocation(<горизонтальная координата>,<вертикальная координата>)

    Оба значения координат задают в виде чисел в пикселах:

    elSearch.setLocation(400, 200);

    Метод clearPositioning делает данный элемент непозиционируемым и удаляет заданные для него координаты. Этот метод не принимает параметров и не возвращает результата:

    elSearch.clearPositioning();

    Реализация усовершенствованного поиска 

    Что ж, все, что нам нужно знать о свободно позиционируемых элементах, мы рассмотрели. Настала пора практических занятий.

    В главе 20 мы реализовали на нашем Web-сайте систему поиска. Получилось, мягко говоря, не очень профессионально, о чем уже говорилось в начале этой главы. Давайте улучшим ситуацию.

    Прежде всего, мы создадим новый контейнер, дадим ему имя csearch и поместим в него Web-форму поиска. В этот же контейнер, ниже Web-формы, мы вставим список, в котором будут выводиться результаты поиска. (Результаты мы будем формировать в виде пунктов списка, содержащих гиперссылки на найденные Web- страницы, — как и в главе 20.) Дадим этому списку имя search_result и сделаем его изначально скрытым.

    Когда посетитель выполнит поиск, мы проверим, присутствуют ли в списке search_result какие-либо пункты (т. е. выполнялся ли поиск ранее и был ли он удачным), и, если присутствуют, удалим их и скроем этот список. Если поиск увенчался успехом, мы сформируем в списке search_result пункты, содержащие гиперссылки на найденные Web-страницы, и откроем его. Таким образом, список search_result будет присутствовать на экране только в случае успешного поиска.

    Когда посетитель щелкнет на любом месте Web-страницы (неважно — на гиперссылке, в том числе и гиперссылке в списке результатов поиска, на абзаце, на изображении или вообще на пустом месте), мы должны скрыть список search_result. Это нужно для того, чтобы этот список не присутствовал на экране постоянно и не мешал посетителю.

    В остальном новый поиск будет работать так же, как и старый.

    Создание контейнера с Web-формой поиска

    Откроем Web-страницу index.htm в Блокноте, найдем созданный в главе 20 фрагмент кода, создающий Web-форму поиска, и удалим его. Вместо него мы вставим сразу после открывающего тега <BODY> код, приведенный в листинге 21.4.

    Листинг 21.4

    <DIV ID="csearch">

    <FORM ACTION="#">

    <P>

    <INPUT TYPE="search" ID="keyword" NAME="keyword" SIZE="20">

    <INPUT TYPE="button" ID="find" NAME="find" VALUE="Искать!"><BR>

    <SELECT ID="search_in" NAME="search_in">

    <OPTION>В названиях</OPTION>

    <OPTION>В ключевых словах</OPTION>

    <OPTION SELECTED>В названиях и ключевых словах</OPTION>

    </SELECT>

    </P>

    <UL ID="search_result">

    </UL>

    </FORM>

    </DIV>

    Он создает контейнер csearch, а в нем — Web-форму поиска. В Web-форме присутствуют те же элементы управления: поле ввода искомого слова, кнопка запуска поиска и раскрывающийся список для выбора режима поиска. Ниже Web-формы мы поместили список search_result, в котором будут выводиться результаты поиска.

    Далее нам нужно задать стиль для только что созданного контейнера csearch, который сделает его свободно позиционируемым. Откроем таблицу стилей main.css в Блокноте и добавим в нее CSS-код, приведенный в листинге 21.5.

    Листинг 21.5

    #csearch { background-color: #F8F8F8; position: absolute;

    left: 600px; top: 0px; padding: 2px; border: thin solid #B1BEC6 }

    Здесь мы, собственно, делаем контейнер csearch свободно позиционируемым, задаем для него начальные координаты, внутренние отступы и рамку. Внутренние отступы будут совсем небольшими, чтобы контейнер сохранял компактность, а рамка — сплошной — пусть Web-форма поиска будет сразу заметна.

    А еще мы указываем для контейнера csearch цвет фона — такой же, как у Web- страницы. Если мы этого не сделаем, фон контейнера будет прозрачным, и сквозь него станет просвечивать содержимое Web-страницы, расположенное "ниже" контейнера. А это будет выглядеть очень некрасиво.

    Раз уж мы правим представление Web-страницы, давайте сразу зададим стили для элементов управления и списка search_result, чтобы сделать их привлекательнее. Рассмотрим эти стили отдельно друг от друга.

    Мы задаем для абзаца, в котором поместили элементы управления, и списка search_result нулевые внешние отступы, чтобы сделать контейнер csearch компактнее:

    #csearch P,

    #search_result { margin: 0px }

    Для элементов управления назначаем размер шрифта 10 пунктов:

    INPUT, SELECT { font-size: 10pt }

    Дело в том, что размер шрифта по умолчанию, принятый для элементов управления, слишком мал, что понравится далеко не всем посетителям.

    Убираем у пунктов списка search_result маркеры и слишком большой отступ слева, где, собственно, выводятся эти маркеры:

    #search_result LI { list-style-type: none; margin-left: -40px; }

    Так мы сделаем контейнер csearch еще компактнее.

    На этом с Web-формой и элементами управления покончено.

    Написание Web-сценария, выполняющего поиск

    Осталось создать (точнее, переделать уже созданный в главе 20) Web-сценарий, который, собственно, будет выполнять поиск.

    Откроем файл Web-сценария main.js в Блокноте и добавим в его начало такое выражение:

    var cSearchHeight = 0;

    Оно объявляет служебную переменную, в которой будет сохранена изначальная высота контейнера csearch. Это значение мы будем использовать для позиционирования данного контейнера. Его высота в процессе работы будет постоянно меняться, поэтому для его позиционирования нам понадобится изначальное значение высоты.

    Далее найдем тело функции, передаваемой методу onReady объекта Ext. В самом его начале поместим два выражения:

    Ext.get("search_result"). setDisplayed(false);

    cSearchHeight = Ext.get("csearch"). getHeight();

    Первое выражение сразу скроет список search_result, а второе присвоит изначальную высоту контейнера csearch объявленной ранее служебной переменной.

    Функцию adjustContainers, задающую размеры контейнеров, мы объявили еще в главе 16 и с тех пор ни разу к ней не возвращались. Настала пора внести в объявление этой функции некоторые правки.

    Вот выражения, которые мы добавим в самый конец adjustContainers:

    var elCSearch = Ext.get("csearch"); elCSearch.setLocation(clientWidth — elCSearch.getWidth(), Ext.get("cmain"). getY() — cSearchHeight);

    Они позиционируют контейнер csearch так, чтобы он в любом случае находился над верхним левым углом контейнера cmain. Кстати, здесь используется значение изначальной высоты контейнера csearch, которое мы сохранили ранее.

    Теперь найдем объявление функции searchData, написанной нами в главе 20. Переделаем его так, как показано в листинге 21.6.

    Листинг 21.6

    function searchData() {

    var elSearchResult = Ext.get("search_result"); elSearchResult.select("A"). removeAllListeners(); elSearchResult.dom.innerHTML = ""; elSearchResult.setDisplayed(false);

    var sKeyword = Ext.get("keyword"). getValue(false);

    if (sKeyword!= "") {

    var iSearchMode = Ext.getDom("search_in"). selectedIndex;

    var aResult = [];

    searchInArray(sKeyword, aHTML, aResult, iSearchMode); searchInArray(sKeyword, aCSS, aResult, iSearchMode); searchInArray(sKeyword, aSamples, aResult, iSearchMode); if (aResult.length > 0) {

    var s = "";

    for (var i = 0; i < aResult.length; i++) {

    s += "<LI><A HREF=\"" + aResult[i].url + "\">" +

    aResult[i].name + "</A></LI>";

    }

    var htelResult = elSearchResult.insertHtml("beforeEnd", s); Ext.fly(htelResult). select("A"). on("click", function(e, t) {

    var href = Ext.fly(this). getAttribute("href");

    var elA = Ext.get("navbar"). child("A[href=" + href + "]");

    var elItem = elA.parent("LI");

    loadFragment(elItem, e);

    });

    elSearchResult.setDisplayed(true);

    }

    }

    Рассмотрим листинг 21.6 построчно.

    Перед поиском нам нужно удалить все пункты, уже присутствующие в списке search_result. Для этого мы сначала удаляем все обработчики событий, привязанные к гиперссылкам, находящимся в пунктах этого списка, а потом удаляем сами пункты:

    var elSearchResult = Ext.get("search_result"); elSearchResult.select("A"). removeAllListeners(); elSearchResult.dom.innerHTML = ""; elSearchResult.setDisplayed(false);

    Напоследок скрываем список search_result.

    Обратим внимание, как выполняется удаление пунктов списка search_result. Из главы 15 мы знаем, что объект Web-обозревателя HTMLElement поддерживает свойство innerHTML, хранящее HTML-код, создающий содержимое данного элемента Web-страницы, в виде строки. Значит, чтобы удалить все содержимое данного элемента, мы можем получить соответствующий ему экземпляр объекта HTMLElement (через свойство dom объекта Ext Core Element) и присвоить его свойству innerHTML пустую строку. Что мы и делаем.

    Листинг 21.7

    var sKeyword = Ext.get("keyword"). getValue(false);

    if (sKeyword!= "") {

    var iSearchMode = Ext.getDom("search_in"). selectedIndex;

    var aResult = [];

    searchInArray(sKeyword, aHTML, aResult, iSearchMode); searchInArray(sKeyword, aCSS, aResult, iSearchMode); searchInArray(sKeyword, aSamples, aResult, iSearchMode); if (aResult.length > 0) {

    var s = "";

    for (var i = 0; i < aResult.length; i++) {

    s += "<LI><A HREF=\"" + aResult[i].url + "\">" +

    aResult[i].name + "</A></LI>";

    }

    var htelResult = elSearchResult.insertHtml("beforeEnd", s); Ext.fly(htelResult). select("A"). on("click", function(e, t) {

    var href = Ext.fly(this). getAttribute("href");

    var elA = Ext.get("navbar"). child("A[href=" + href + "]");

    var elItem = elA.parent("LI");

    loadFragment(elItem, e);

    });

    Фрагмент кода, приведенный в листинге 21.7, перекочевал из предыдущей реализации функции searchData практически без изменений. Мы уже знаем, что он делает. Сформировав пункты списка search_result, открываем его:

    elSearchResult.setDisplayed(true);

    }

    }

    На этом выполнение функции searchData заканчивается.

    Функция cleanupSamples, которую мы объявили в главе 16, удаляет обработчики событий, привязанные к гиперссылкам раздела "См. также" и результатам поиска. Найдем объявляющий ее код и удалим выражения, которые убирают обработчики событий у гиперссылок результатов поиска, — ведь ранее мы поместили выполняющий это действие код в функцию searchData. После этого объявление функции cleanupSamples будет выглядеть так, как в листинге 21.8.

    Листинг 21.8

    function cleanupSamples() {

    var ceSamples = Ext.select(".sample"); ceSamples.each(function(el, cl, ind){ var elH6 = el.child(":first"); elH6.removeAllListeners();

    });

    }

    Так, бoльшую часть работы мы сделали. Осталось реализовать скрытие списка search_result при щелчке на содержимом Web-страницы.

    Вернемся к телу функции, передаваемой параметром методу onReady объекта Ext, и добавим в его конец такое выражение:

    Ext.getBody(). on("click",

    function(){ Ext.get("search_result"). setDisplayed(false); });

    Оно привязывает к событию click секции тела Web-страницы обработчик, который скрывает список search_result.

    В главе 15 мы узнали, что некоторые события, в том числе и click, имеют обыкновение всплывать из элементов, в которых они изначально возникли, в их родители, затем — в родители их родителей и, наконец, в секцию тела Web-страницы. Обработчик события click, который мы только что привязали к секции тела Web- страницы, сработает независимо от того, в каком элементе Web-страницы возникло это событие, и список search_result в любом случае будет скрыт.

    Но тут возникает очень неприятный момент: событие click кнопки запуска поиска также рано или поздно всплывет в секцию тела Web-страницы. Давайте посмотрим, что получится в результате. Посетитель нажмет кнопку запуска поиска, функция searchData сформирует пункты списка результатов и откроет этот список, после чего выполнится обработчик события click, привязанный нами к секции тела Web- страницы, который скроет список результатов. Непорядок!

    Найдем в теле функции, передаваемой параметром методу onReady объекта Ext, вот это выражение:

    Ext.get("find"). on("click", searchData);

    Оно привязывает обработчик к событию click кнопки, запускающей поиск. Изменим его следующим образом:

    Ext.get("find"). on("click", function(e){

    searchData();

    e. stopPropagation();

    });

    Новый обработчик события click сначала вызовет функцию searchData, собственно выполняющую поиск, а потом подавит всплытие возникшего события. Как видим, для этого используется метод stopPropagation объекта Ext Core EventObject (см. главу 15).

    И еще. В обработчике события click пунктов полосы навигации (функция loadFragment) у нас подавляется всплытие этого события. Следовательно, если посетитель щелкнет на пункте полосы навигации (или гиперссылке раздела "См. также", или гиперссылке пункта в списке результатов поиска), событие click не всплывет в секцию тела Web-страницы, привязанный к нему обработчик не выполнится, и список search_result скрыт не будет. Нам нужно это исправить.

    Найдем код, объявляющий функцию loadFragment, и добавим в самый его конец такое выражение:

    Ext.get("search_result"). setDisplayed(false);

    Что оно делает, мы уже знаем.

    Сохраним все исправленные файлы и проверим поиск в действии. Вот теперь он выглядит вполне профессионально!..

    Что дальше?

    В этой главе мы познакомились со свободно позиционируемыми элементами Web- страницы и даже применили их на практике в новой версии системы поиска. Мелочь, конечно, — иные Web-дизайнеры с помощью свободных элементов творят на Web-страницах чудеса, — но для начала получилось неплохо.

    В следующей, последней, главе мы познакомимся с еще одной возможностью HTML 5 — программируемой графикой. Мы научимся рисовать произвольные фигуры на Web-странице. А еще мы наделим наш Web-сайт графическим логотипом.

    ГЛАВА 22. Программируемая графика 

    В предыдущей главе мы познакомились со свободно позиционируемыми элементами Web-страницы и использовали их, чтобы улучшить систему поиска на нашем Web-сайте. Получилось неплохо, правда?

    На этом мы закончим с поиском и обратим внимание на заголовок нашего Web-сайта. Как-то неказисто он выглядит — обычный текст без всяких изысков. И это в то время, когда большинство Web-сайтов щеголяют шикарными графическими логотипами. Почему у нас такого нет?

    Потому что мы этим еще не занимались. Вообще, сделать графический логотип Web-сайта проще простого — достаточно нарисовать его в каком-либо графическом редакторе и сохранить в любом формате, применяемом в Интернете. А как поместить на Web-страницу графическое изображение, мы уже знаем — изучали еще в главе 4.

    Существует и другой путь — задействовать возможности программируемой графики, предлагаемые HTML 5. Они позволяют нарисовать любую, даже весьма сложную фигуру программно, из Web-сценария. Причем пользоваться этими возможностями не так и сложно — достаточно уяснить пару концепций и выучить несколько десятков методов.

    Давайте так и сделаем. Ведь если мы взялись изучать HTML 5, так уж будем идти до конца.

    Канва

    Начнем сразу с первой концепции, на основе которой работает программируемая графика HTML 5 и которую нам нужно уяснить. Все рисование выполняется в особом элементе Web-страницы, еще нам не встречавшемся, — в канве (ее еще называют холстом). В других элементах (абзацах, заголовках, таблицах, графических изображениях и пр.) программное рисование не работает.

    Канву создают с помощью парного тега <CANVAS>:

    <CANVAS ID="<имя>" [WIDTH="<ширина>"] [HEIGHT="<высота>"]></CANVAS>

    Мы уже знаем, что рисование в канве выполняется программно, в Web-сценарии. А перед тем как что-то нарисовать, нам придется получить доступ к канве. Сделать это проще всего через имя, заданное атрибутом тега ID. Именно поэтому данный атрибут тега помечен здесь как обязательный.

    Необязательные атрибуты тега WIDTH и HEIGHT задают, соответственно, ширину и высоту канвы в пикселах (по умолчанию 300 150 пикселов).

    ВНИМАНИЕ!

    Задавать размеры канвы с помощью стилей CSS не рекомендуется.

    Вот HTML-код, создающий на странице канву cnv размером 400 300 пикселов:

    <CANVAS ID="cnv" WIDTH="400" HEIGHT="300"></CANVAS>

    Канва представляется как экземпляр объекта Web-обозревателя HTMLCanvasElement, производный от объекта HTMLElement. Для нас будет полезен только единственный метод этого объекта, который мы скоро рассмотрим.

    Контекст рисования

    Рисование на канве выполняется с помощью особых свойств и методов объекта… нет, не HTMLCanvasElement, а CanvasRenderingContext2D. Этот объект представляет так называемый контекст рисования, который можно рассматривать как набор инструментов, используемый для рисования на данной канве.

    Значит, перед тем как начать рисование, нам придется как-то получить экземпляр объекта Web-обозревателя CanvasRenderingContext2D для данной канвы. Это выполняется вызовом единственного метода getContext объекта HTMLCanvasElement:

    <канва>.getContext("2d")

    Мы видим, что метод getContext принимает единственный параметр — строку "2d". Возвращает он то, что нам нужно, — экземпляр объекта CanvasRenderingContext2D, представляющий контекст рисования данной канвы.

    Напишем небольшой Web-сценарий, который помещает в переменную ctxCanvas контекст рисования для ранее созданной канвы cnv:

    var htelCanvas = Ext.getDom("cnv");

    var ctxCanvas = htelCanvas.getContext("2d");

    Впоследствии мы будем пользоваться этим контекстом рисования для наших примеров.

    Вот теперь, вооружившись контекстом рисования канвы, мы можем начать рисовать на ней с помощью весьма многочисленных свойств и методов объекта CanvasRenderingContext2D, которые мы обязательно рассмотрим.

    ВНИМАНИЕ!

    Все свойства и методы, рассматриваемые далее, принадлежат объекту CanvasRenderingContext2D, если об этом не сказано специально.

    При выполнении операций рисования нам потребуется задавать координаты точек, в которых будет начинаться и заканчиваться рисование фигур и пр. Координаты измеряются в пикселах и отсчитываются от верхнего левого угла канвы; другими словами — в верхнем левом углу канвы находится начало ее координат. Запомним это.

    Рисование простейших фигур

    Начнем мы с самых простых операций — рисования различных прямоугольников, с заливкой и без нее.

    Для рисования прямоугольника без заливки (т. е. одного лишь контура прямоугольника) предназначен метод strokeRect объекта CanvasRenderingContext2D:

    <контекст рисования>.strokeRect(<горизонтальная координата>,<вертикальная координата>, <ширина>,<высота>)

    Первые два параметра задают горизонтальную и вертикальную координаты верхнего левого угла рисуемого прямоугольника в пикселах в виде чисел. Третий и четвертый параметры задают, соответственно, ширину и высоту прямоугольника, также в пикселах и также в виде чисел. Метод strokeRect не возвращает результата.

    Пример:

    ctxCanvas.strokeRect(20, 20, 360, 260);

    Метод fillRect рисует прямоугольник с заливкой:

    <контекст рисования>.fillRect(<горизонтальная координата>,<вертикальная координата>, <ширина>,<высота>)

    Как видим, формат его вызова такой же, как у метода strokeRect:

    ctxCanvas.fillRect(40, 40, 320, 220);

    Весьма полезный для создания сложных фигур метод clearRect очищает заданную прямоугольную область от любой присутствовавшей там графики:

    <контекст рисования>.clearRect(<горизонтальная координата>,<вертикальная координата>, <ширина>,<высота>)

    И его формат вызова схож с форматом вызова метода strokeRect.

    Вот выражения, которые рисуют большой прямоугольник с заливкой, занимающий всю канву cnv, после чего создают в его середине прямоугольную "прореху":

    ctxCanvas.fillRect(0, 0, 400, 300);

    ctxCanvas.clearRect(100, 100, 200, 100);

    А это выражение очищает канву от всей присутствующей на ней графики:

    ctxCanvas.clearRect(0, 0, 400, 300);

    Задание цвета, уровня прозрачности и толщины линий

    Во время работы с канвой нам придется задавать цвета линий и заливок, уровень их прозрачности и толщину линий. Это выполняется с помощью особых свойств объекта CanvasRenderingContext2D.

    Свойство strokeStyle задает цвет линий контура. Все фигуры, которые мы впоследствии нарисуем, будут иметь контур данного цвета. Цвет задают в виде строки либо с именем цвета, либо в привычном нам формате #RRGGBB, либо в двух других форматах, которые мы сейчас рассмотрим.

    Вот первый формат:

    rgb(<красная составляющая>, <зеленая составляющая>, <синяя составляющая>)

    Здесь все три составляющие цвета имеют вид десятичных чисел от 0 до 255.

    Второй формат позволяет дополнительно задать уровень прозрачности рисуемых линий:

    rgba(<красная составляющая>, <зеленая составляющая>,<синяя составляющая>, <уровень прозрачности>)

    Три составляющие цвета также представляют собой десятичные числа от 0 до 255. Уровень прозрачности задают в виде числа от 0.0 (полностью прозрачный) до 1.0 (полностью непрозрачный).

    Все четыре выражения задают непрозрачный красный цвет линий контура:

    ctxCanvas.strokeStyle = "red"; ctxCanvas.strokeStyle = "#FF0000"; ctxCanvas.strokeStyle = "rgb(255, 0,0)"; ctxCanvas.strokeStyle = "rgb(255, 0, 0, 1)";

    А вот выражение, задающее для линий контура полупрозрачный черный цвет:

    ctxCanvas.strokeStyle = "rgb(0, 0, 0, 0.5)";

    Изначально, сразу после загрузки и вывода канвы на Web-страницу, линии контура будут иметь черный цвет.

    Свойство fillStyle определяет цвет заливки, также в строковом виде и с использованием тех же форматов, что описаны ранее. Для цвета заливок действуют те же правила, что и для цвета линий. По умолчанию цвет заливок также черный.

    Вот выражение, задающее тускло-зеленый непрозрачный цвет заливки:

    ctxCanvas.fillStyle = "rgb(0, 127, 0)";

    Еще один пример иллюстрирует листинг 22.1.

    Листинг 22.1

    ctxCanvas.strokeStyle = "rgba(255, 0, 0, 1)";

    ctxCanvas.fillStyle = "rgba(255, 0, 0, 1)";

    ctxCanvas.fillRect(0, 100, 400, 100);

    ctxCanvas.strokeStyle = "rgba(0, 255, 0, 0.5)";

    ctxCanvas.fillStyle = "rgba(0, 255, 0, 0.5)";

    ctxCanvas.fillRect(100, 0, 200, 300);

    Web-сценарий из листинга 22.1 рисует прямоугольник с заливкой, используя и для контура, и для заливки непрозрачный красный цвет, после чего поверх него рисует прямоугольник с заливкой, но уже полупрозрачным зеленым цветом. При этом сквозь полупрозрачный второй прямоугольник будет просвечивать непрозрачный первый. Интересный эффект, кстати!

    ВНИМАНИЕ!

    Нельзя присваивать значение свойства strokeStyle свойству fillStyle и наоборот. Это вызовет ошибку в Web-сценарии.

    Свойство lineWidth задает толщину линий в пикселах в виде числа.

    Пример:

    ctxCanvas.lineWidth = 20;

    ctxCanvas.strokeRect(20, 20, 360, 260);

    Этот Web-сценарий рисует прямоугольник без заливки линиями толщиной 20 пикселов.

    Свойство globalAlpha, возможно, также нам пригодится. Оно позволяет задать уровень прозрачности для любой графики, которую мы впоследствии нарисуем. Уровень прозрачности также задается в виде числа от 0.0 (полностью прозрачный) до 1.0 (полностью непрозрачный).

    Вот выражение, задающее для всей графики, которую мы потом нарисуем на канве, уровень прозрачности 10 %:

    ctxCanvas.globalAlpha = 0.1;

    Рисование сложных фигур 

    Канва также поддерживает рисование более сложных, чем прямоугольники, фигур с контурами из множества прямых и кривых линий. Сейчас мы выясним, как это делается, и рассмотрим соответствующие методы объекта CanvasRenderingContext2D.

    Как рисуются сложные контуры

    Контуры сложных фигур рисуются в три этапа.

    1. Web-обозреватель ставится в известность, что сейчас начнется рисование контура сложной фигуры.

    2. Рисуются отдельные линии, прямые и кривые, составляющие сложный контур.

    3. Web-обозреватель ставится в известность, что рисование контура закончено, и теперь фигура должна быть выведена на канву, возможно, с созданием заливки.

    Также можно указать Web-обозревателю, что следует замкнуть нарисованный контур.

    Рисование сложного контура начинается с вызова метода beginPath, который не принимает параметров и не возвращает результата.

    Собственно рисование линий, составляющих сложный контур, выполняют особые методы, которые мы далее рассмотрим.

    После окончания рисования сложного контура мы можем захотеть, чтобы Web-обозреватель его замкнул. Это реализует метод closePath, который не принимает параметров и не возвращает результата. После его вызова последняя точка контура будет соединена с самой первой, в которой началось его рисование.

    Завершает рисование контура вызов одного из двух методов: stroke или fill. Первый метод просто завершает рисование контура, второй, помимо этого, замыкает контур, если он не замкнут, и рисует заливку получившейся фигуры. Оба метода не принимают параметров и не возвращают результата.

    А теперь рассмотрим методы, которые используются для рисования разнообразных линий, составляющих сложный контур.

    Перо. Перемещение пера

    Для рисования сложного контура используется концепция пера — воображаемого инструмента рисования. Перо можно перемещать в любую точку на канве. Рисование каждой линии контура начинается в точке, где в данный момент находится перо. После рисования каждой линии перо перемещается в ее конечную точку, из которой тут же можно начать рисование следующей линии контура.

    Изначально, сразу после загрузки Web-страницы и вывода канвы, перо находится в точке с координатами [0,0], т. е. в верхнем левом углу канвы. Переместить перо в другую точку канвы, где мы собираемся начать рисование контура, позволяет метод moveTo:

    <контекст рисования>.moveTo(<горизонтальная координата>,<вертикальная координата>)

    Параметры этого метода задают горизонтальную и вертикальную координаты точки, в которую должно переместиться перо, в пикселах в виде чисел. Метод moveTo не возвращает результата.

    Пример:

    ctxCanvas.moveTo(200, 150);

    Это выражение перемещает перо в центр канвы cnv — в точку с координатами [200,150].

    Прямые линии

    Прямые линии рисовать проще всего. Для этого используется метод lineTo:

    <контекст рисования>.lineTo(<горизонтальная координата>,<вертикальная координата>)

    Начальная точка рисуемой прямой будет находиться в том месте, где в данный момент установлено перо (об этом уже говорилось ранее). Координаты конечной точки в пикселах задают параметры метода lineTo. Метод не возвращает результата.

    После рисования прямой линии перо будет установлено в ее конечной точке. Мы можем прямо из этой точки начать рисование следующей линии контура.

    Листинг 22.2

    ctxCanvas.beginPath();

    ctxCanvas.moveTo(20, 20);

    ctxCanvas.lineTo(380, 20);

    ctxCanvas.lineTo(200, 280);

    ctxCanvas.closePath();

    ctxCanvas.stroke();

    Web-сценарий из листинга 22.2 рисует треугольник без заливки. Давайте рассмотрим последовательность действий.

    1. Вызовом метода beginPath сообщаем Web-обозревателю, что собираемся рисовать контур сложной фигуры.

    2. Методом moveTo устанавливаем перо в точку, где начнется рисование.

    3. С помощью метода lineTo рисуем две линии, которые станут сторонами треугольника.

    4. Третью сторону мы рисовать не будем, а лучше вызовем метод closePath, чтобы Web-обозреватель сам нарисовал ее, замкнув нарисованный нами контур.

    5. Вызываем метод stroke, чтобы закончить рисование треугольника без заливки.

    Дуги

    Дуги рисуются тоже довольно просто. Для этого используется метод arc:

    <контекст рисования>.arc(<горизонтальная координата>,<вертикальная координата>, <радиус>, <начальный угол>, <конечный угол>,true|false)

    Первые два параметра задают горизонтальную и вертикальную координаты центра рисуемой дуги в виде числа в пикселах. Третий параметр определяет радиус дуги, также в пикселах и в виде числа. Четвертый и пятый параметры задают начальный и конечный углы дуги в радианах в виде чисел; эти углы отсчитываются от горизонтальной оси. Если шестой параметр имеет значение true, то дуга рисуется против часовой стрелки, а если false — по часовой стрелке. Метод arc не возвращает результата.

    Рисование дуги начинается в точке, где в данный момент установлено перо. После рисования дуги перо будет установлено в точке, где кончается эта дуга.

    Как уже говорилось, начальный и конечный углы рисуемой дуги задаются в радианах, а не в градусах. Пересчитает величину угла из градусов в радианы нам следующее выражение JavaScript:

    radians = (Math.PI / 180) * degrees;

    Здесь переменная degrees хранит значение угла в градусах, а переменная radians будет хранить то же значение, но в радианах. Свойство PI объекта JavaScript Math хранит значение числа ?.

    Вот Web-сценарий, который рисует окружность без заливки:

    ctxCanvas.beginPath();

    ctxCanvas.arc(200, 150, 100, 0, Math.PI * 2, false);

    ctxCanvas.stroke();

    Отметим, какие параметры метода arc, в частности, значения начального и конечного угла, мы задавали в этом случае.

    Кривые Безье

    Кривые Безье — это линии особой формы, описываемые тремя или четырьмя точками: начальной, конечной и одной или двумя контрольными. Начальная и конечная точки, как и в случае прямой линии, задают начало и конец кривой Безье, а контрольные точки формируют касательные, определяющие форму этой кривой.


    Рис. 22.1. Кривая Безье с двумя контрольными точками


    Рис. 22.2. Кривая Безье с одной контрольной точкой

    На рис. 22.1 кривая Безье выделена утолщенной линией, ее начальная и конечная точки обозначены кружками, квадратики соответствуют контрольным точкам. Через каждую контрольную точку, а также через начальную и конечную точки кривой Безье проведены касательные (тонкие прямые линии) — они определяют форму кривой. Если мы мысленно переместим какую-либо из контрольных точек, то направление проведенной через нее касательной изменится, и, следовательно, изменится и форма кривой Безье.

    На рис. 22.1 представлена кривая Безье с двумя контрольными точками. Такие кривые применяются чаще всего.

    Но зачастую предпочтительнее использовать другую, "вырожденную", форму кривых Безье — с одной контрольной точкой (рис. 22.2). На основе кривых Безье с одной контрольной точкой можно создавать дуги и рисовать секторы, в чем мы вскоре убедимся.

    Для рисования кривых Безье с двумя контрольными точками предусмотрен метод

    bezierCurveTo: <контекст рисования>.bezierCurveTo(<горизонтальная координата первой контрольной точки>,<вертикальная координата первой контрольной точки>,<горизонтальная координата второй контрольной точки>,<вертикальная координата второй контрольной точки>,<горизонтальная координата конечной точки>,<вертикальная координата конечной точки>)

    Назначение параметров этого метода понятно из их описания. Все они задаются в пикселах в виде чисел. Метод не возвращает результата.

    Рисование кривой Безье начинается в той точке, где в данный момент установлено перо. После рисования кривой перо устанавливается в ее конечную точку.

    Вот Web-сценарий, рисующий кривую Безье с двумя контрольными точками:

    ctxCanvas.beginPath();

    ctxCanvas.moveTo(100, 100);

    ctxCanvas.bezierCurveTo(120, 80, 160, 20, 100, 200);

    ctxCanvas.stroke();

    Рисование кривых Безье с одной контрольной точкой реализует метод quadraticCurveTo:<контекст рисования>.quadraticCurveTo(<горизонтальная координата контрольной точки>,<вертикальная координата контрольной точки>,<горизонтальная координата конечной точки>,<вертикальная координата конечной точки>)

    Описывать параметры этого метода также нет смысла — их назначение понятно. Все они задаются в пикселах в виде чисел. Метод не возвращает результата.

    Рисование такой кривой Безье также начинается в той точке, где в данный момент установлено перо. После рисования кривой перо устанавливается в ее конечную точку.

    Вот Web-сценарий, рисующий кривую Безье с одной контрольной точкой:

    ctxCanvas.beginPath();

    ctxCanvas.moveTo(100, 100);

    ctxCanvas.quadraticCurveTo(200, 100, 200, 200);

    ctxCanvas.stroke();

    Получившаяся кривая будет иметь вид дуги.

    Более сложный пример иллюстрирует листинг 22.3.

    Листинг 22.3

    ctxCanvas.beginPath();

    ctxCanvas.strokeStyle = "red";

    ctxCanvas.fillStyle = "red";

    ctxCanvas.moveTo(100, 100);

    ctxCanvas.quadraticCurveTo(200, 100, 200, 200);

    ctxCanvas.lineTo(100, 200);

    ctxCanvas.lineTo(100, 100);

    ctxCanvas.fill();

    Web-сценарий из листинга 22.3 рисует красный сектор окружности с красной же заливкой. Мы проводим кривую Безье с одной контрольной точкой, имеющую вид дуги, и соединяем ее начальную и конечную точки с центром воображаемой окружности.

    Еще один пример приведен в листинге 22.4.

    Листинг 22.4

    ctxCanvas.beginPath();

    ctxCanvas.moveTo(20, 0);

    ctxCanvas.lineTo(180, 0);

    ctxCanvas.quadraticCurveTo(200, 0, 200, 20);

    ctxCanvas.lineTo(200, 80);

    ctxCanvas.quadraticCurveTo(200, 100, 180, 100);

    ctxCanvas.lineTo(20, 100);

    ctxCanvas.quadraticCurveTo(0, 100, 0, 80);

    ctxCanvas.lineTo(0, 20);

    ctxCanvas.quadraticCurveTo(0, 0, 20, 0);

    ctxCanvas.stroke();

    Web-сценарий из листинга 22.4 рисует прямоугольник со скругленными углами.

    Попробуйте сами с ним разобраться.

    Прямоугольники

    Мы уже умеем рисовать прямоугольники, используя описанные ранее методы strokeRect и fillRect. Но прямоугольники, рисуемые этими методами, представляют собой независимые фигуры, не являющиеся частью какого-либо сложного контура. Если мы хотим нарисовать прямоугольник в составе сложного контура, нам придется прибегнуть к методу rect:

    <контекст рисования>.rect(<горизонтальная координата>,<вертикальная координата>, <ширина>,<высота>)

    Первые два параметра задают горизонтальную и вертикальную координаты верхнего левого угла рисуемого прямоугольника в пикселах в виде чисел. Третий и четвертый параметры задают, соответственно, ширину и высоту прямоугольника, также в пикселах и тоже в виде чисел. Метод rect не возвращает результата.

    После рисования прямоугольника методом rect перо устанавливается в точку с координатами [0,0], т. е. в верхний левый угол канвы.

    Web-сценарий из листинга 22.5 рисует сложную фигуру, состоящую их трех накладывающихся друг на друга квадратов, и создает для нее заливку.

    Листинг 22.5

    ctxCanvas.beginPath();

    ctxCanvas.rect(50, 50, 50, 50);

    ctxCanvas.rect(75, 75, 50, 50);

    ctxCanvas.rect(100, 100, 50, 50);

    ctxCanvas.fill();

    Задание стиля линий

    Канва позволяет задать стиль линий, включающий в себя некоторые параметры, которые управляют формой их начальных и конечных точек и точек соединения линий друг с другом. Давайте их рассмотрим.

    Свойство lineCap задает форму начальных и конечных точек линий. Его значение может быть одной из следующих строк:

    — "butt" — начальная и конечная точки как таковые отсутствуют (значение по умолчанию);

    — "round" — начальная и конечная точки имеют вид кружков;

    — "square" — начальная и конечная точки имеют вид квадратиков.

    Web-сценарий из листинга 22.6 рисует толстую прямую линию, начальная и конечная точки которой имеют вид кружков.

    Листинг 22.6

    ctxCanvas.beginPath();

    ctxCanvas.lineWidth = 10;

    ctxCanvas.lineCap = "round";

    ctxCanvas.moveTo(20, 20);

    ctxCanvas.lineTo(180, 20);

    ctxCanvas.stroke();

    Свойство lineJoin задает форму точек соединения линий друг с другом. Его значение может быть одной из следующих строк:

    — "miter" — точки соединения имеют вид острого или тупого угла (значение по умолчанию);

    — "round" — точки соединения, образующие острые углы, скругляются;

    — "bevel" — острые углы, образуемые соединяющимися линиями, как бы срезаются.

    Web-сценарий из листинга 22.7 рисует контур треугольника толстыми линиями, причем точки соединения этих линий, образующие острые углы, будут срезаться.

    Листинг 22.7

    ctxCanvas.beginPath();

    ctxCanvas.lineWidth = 10;

    ctxCanvas.lineJoin = "bevel";

    ctxCanvas.moveTo(20, 20);

    ctxCanvas.lineTo(380, 20);

    ctxCanvas.lineTo(200, 280);

    ctxCanvas.closePath();

    ctxCanvas.stroke();

    Свойство miterLimit задает дистанцию, на которую могут выступать острые углы, образованные соединением линий, от точки соединения, когда для свойства lineJoin задано значение "miter". Углы, выступающие на бoльшую дистанцию, будут срезаны.

    Значение данного свойства задается в пикселах в виде числа. Каково его значение по умолчанию, автору выяснить не удалось.

    Листинг 22.8 содержит исправленный вариант Web-сценария, приведенного ранее.

    Листинг 22.8

    ctxCanvas.beginPath();

    ctxCanvas.lineJoin = "miter";

    ctxCanvas.lineWidth = 10;

    ctxCanvas.miterLimit = 1;

    ctxCanvas.moveTo(20, 20);

    ctxCanvas.lineTo(380, 20);

    ctxCanvas.lineTo(200, 280);

    ctxCanvas.closePath();

    ctxCanvas.stroke();

    Дистанция, на которую могут выступать острые углы, образованные соединением линий контура, не должна превышать одного пиксела. Углы, выступающие на бoльшую дистанцию, будут срезаны.

    Вывод текста

    Было бы странно, если канва не позволяла выводить произвольный текст. Существуют два метода и несколько свойств для вывода текста.

    Метод strokeText выводит заданный текст в указанное место. Текст рисуется в виде контура, без заливки; цвет контура задается значением свойства strokeStyle: <контекст рисования>.strokeText(<выводимый текст>,<горизонтальная координата>, <вертикальная координата>[, <максимальная ширина>])

    Первый параметр этого метода задает выводимый текст в виде строки. Второй и третий параметры задают координаты точки, в которой будет выведен текст, в пикселах в виде чисел. По умолчанию выводимый текст будет выровнен по левому краю относительно этой точки.

    Четвертый, необязательный, параметр определяет максимальное значение ширины, которую может принять выводимый на канву текст. Если выводимый текст получается шире, канва выводит его либо шрифтом с уменьшенной шириной символов (если данный шрифт поддерживает такое начертание), либо шрифтом меньшего размера.

    Метод strokeText не возвращает результата. Пример:

    ctxCanvas.strokeStyle = "blue";

    ctxCanvas.strokeText("Всем привет!", 200, 50, 100);

    Метод fillText также выводит заданный текст в указанное место. Однако текст этот представляет собой одну заливку, без контура; цвет заливки задается значением свойства fillStyle: <контекст рисования>.fillText(<выводимый текст>,<горизонтальная координата>, <вертикальная координата>[, <максимальная ширина>])

    Формат вызова этого метода такой же, как и у метода strokeText:

    ctxCanvas.fillStyle = "yellow";

    ctxCanvas.fillText("Всем пока!", 50, 100);

    Свойство font позволяет задать параметры шрифта, которым будет выводиться текст. Параметры шрифта указывают в том же формате, что и у значения атрибута стиля font (см. главу 8), в виде строки:

    ctxCanvas.fillStyle = "yellow"; ctxCanvas.font = "italic 12pt Verdana"; ctxCanvas.fillText("Всем пока!", 50, 100);

    Свойство textAlign позволяет задать горизонтальное выравнивание выводимого текста относительно точки, в которой он будет выведен (координаты этой точки задаются вторым и третьим параметрами методов strokeText и fillText). Это свойство может принимать следующие значения:

    — "left" — выравнивание по левому краю;

    — "right" — выравнивание по правому краю;

    — "start" — выравнивание по левому краю, если текст выводится по направлению слева направо, и по правому краю в противном случае (значение по умолчанию);

    — "end" — выравнивание по правому краю, если текст выводится по направлению слева направо, и по левому краю в противном случае;

    — "center" — выравнивание по центру.

    Пример:

    ctxCanvas.fillStyle = "yellow";

    ctxCanvas.font = "italic 12pt Verdana";

    ctxCanvas.textAlign = "center";

    ctxCanvas.fillText("Всем пока!", 100, 100);

    Свойство textBaseline позволяет задать вертикальное выравнивание выводимого текста относительно точки, в которой он будет выведен. Это свойство может принимать следующие значения:

    — "top" — выравнивание по верху прописных (больших) букв;

    — "hanging" — выравнивание по верху строчных (маленьких) букв;

    — "middle" — выравнивание по середине строчных букв;

    — "alphabetic" — выравнивание по базовой линии букв европейских алфавитов

    (значение по умолчанию);

    — "ideographic" — выравнивание по базовой линии иероглифических символов

    (она находится чуть ниже базовой линии букв европейских алфавитов);

    — "bottom" — выравнивание по низу букв. Пример:

    ctxCanvas.fillStyle = "yellow";

    ctxCanvas.font = "italic 12pt Verdana";

    ctxCanvas.textAlign = "center";

    ctxCanvas.textBaseline = "top";

    ctxCanvas.fillText("Всем пока!", 100, 100);

    Использование сложных цветов 

    Ранее для линий и заливок у нас были только простые, однотонные, цвета. Настала пора познакомиться со средствами канвы для создания и использования сложных цветов: градиентных и графических.

    Линейный градиентный цвет

    В линейном градиентном цвете (или просто линейном градиенте) один простой цвет плавно переходит в другой при движении по прямой линии. Пример такого цвета — окраска заголовка окна в Windows 2000 и более поздних версиях Windows при выборе классической темы; там синий цвет плавно перетекает в белый.

    Линейный градиентный цвет создают в три этапа.

    Первый этап — вызов метода createLinearGradient — собственно создание линейного градиентного цвета:

    <контекст рисования>.createLinearGradient(<горизонтальная координата начальной точки>,<вертикальная координата начальной точки>,<горизонтальная координата конечной точки>,<вертикальная координата конечной точки>)

    Параметры этого метода определяют координаты начальной и конечной точки воображаемой прямой, по которой будет "распространяться" градиент. Они отсчитываются относительно канвы и задаются в пикселах в виде чисел.

    Метод createLinearGradient возвращает экземпляр объекта CanvasGradient, представляющий созданный нами линейный градиент. Мы используем его для указания цветов, формирующих градиент.

    Вот выражение, создающее линейный градиент, который будет "распространяться" по прямой с координатами начальной и конечной точек [0,0] и [100,50], и помещающее его в переменную lgSample:

    var lgSample = ctxCanvas.createLinearGradient(0, 0, 100, 50);

    Второй этап — расстановка так называемых ключевых точек градиента, определяющих позицию, в которой будет присутствовать "чистый" цвет. Между ключевыми точками будет наблюдаться переход между цветами. Таких ключевых точек может быть сколько угодно.

    Ключевую точку ставят, вызвав метод addColorStop объекта CanvasGradient:

    <градиент>.addColorStop(<положение ключевой точки>, <цвет>)

    Первый параметр определяет относительное положение создаваемой ключевой точки на воображаемой прямой, по которой "распространяется" градиент. Он задается в виде числа от 0.0 (начало прямой) до 1.0 (конец прямой). Второй параметр задает цвет, который должен присутствовать в данной ключевой точке, в виде строки; при этом допустимы все форматы описания цвета, упомянутые в начале этой главы.

    Метод addColorStop не возвращает значения. Пример:

    lgSample.addColorStop(0, "black");

    lgSample.addColorStop(0.4, "rgba(0, 0, 255, 0.5)");

    lgSample.addColorStop(1, "#FF0000");

    Этот Web-сценарий создает на полученном нами ранее линейном градиенте три ключевые точки:

    — расположенную в начальной точке воображаемой прямой и задающую черный цвет;

    — расположенную в точке, отстоящей на 40 % длины воображаемой прямой от ее начальной точки, и задающую полупрозрачный синий цвет;

    — расположенную в конечной точке воображаемой прямой и задающую красный цвет.

    Третий этап — собственно использование готового линейного градиента. Для этого представляющий его экземпляр объекта CanvasGradient следует присвоить свойству strokeStyle или fillStyle. Первое свойство, как мы помним, задает цвет линий

    контуров, а второе — цвет заливок:

    ctxCanvas.fillStyle = lgSample;

    А теперь нам нужно рассмотреть один очень важный вопрос. И рассмотрим мы его на примере созданного ранее градиента.

    Предположим, что мы нарисовали на канве три прямоугольника и применили к ним наш линейный градиент. Первый прямоугольник нарисован в точке [0,0] (в начале воображаемой прямой градиента, в смысле, в его первой ключевой точке), второй — в точке [30,20] (во второй ключевой точке), третий — в точке [80,40] (в конце градиента — его третьей ключевой точке). Иначе говоря, мы "расставили" наши прямоугольники во всех ключевых точках градиента.

    Как будут окрашены эти прямоугольники? Давайте посмотрим.

    — Первый прямоугольник будет окрашен, в основном, в черный цвет, заданный в первой ключевой точке градиента.

    — Второй прямоугольник будет окрашен, в основном, в полупрозрачный синий цвет, заданный во второй ключевой точке градиента.

    — Третий прямоугольник будет окрашен, в основном, в красный цвет, заданный в третьей ключевой точке градиента.

    Следовательно, созданный нами градиент не "втиснулся" в каждый из нарисованных прямоугольников целиком, а как бы зафиксировался на самой канве, а прямоугольники только "проявили" фрагменты этого градиента, соответствующие их размерам. Другими словами, градиент задается для целой канвы, а фигуры, к которым он применен, окрашиваются соответствующими его фрагментами.

    Если мы захотим, чтобы какая-то фигура была окрашена градиентом полностью, придется задать для этого градиента такие координаты воображаемой прямой, что- бы он "покрыл" всю фигуру. Иначе фигура будет содержать только часть градиента.

    Листинг 22.9

    var lgSample = ctxCanvas.createLinearGradient(0, 0, 100, 50);

    lgSample.addColorStop(0, "black"); lgSample.addColorStop(0.4, "rgba(0, 0, 255, 0.5)"); lgSample.addColorStop(1, "#FF0000"); ctxCanvas.fillStyle = lgSample;

    ctxCanvas.fillRect(0, 0, 200, 100);

    Web-сценарий из листинга 22.9 рисует прямоугольник и заполняет его линейным градиентом, аналогичным созданному ранее в этом разделе. Попробуйте изменить координаты и размеры рисуемого прямоугольника и посмотрите, какая часть градиента в нем появится.

    Радиальный градиентный цвет

    Радиальный градиентный цвет (радиальный градиент) описывается двумя вложенными друг в друга окружностями. "Распространяется" он из внутренней окружности по направлению к внешней во все стороны. Ключевые точки такого градиента расставлены между этими окружностями.

    Радиальный градиентный цвет также создают в три этапа.

    Первый этап — вызов метода createRadialGradient — создание радиального градиентного цвета:

    <контекст рисования>.createRadialGradient (<горизонтальная координата центра внутренней окружности>,<вертикальная координата центра внутренней окружности>,<радиус внутренней окружности>,<горизонтальная координата центра внешней окружности>,<вертикальная координата центра внешней окружности>,<радиус внешней окружности>)

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

    Метод createRadialGradient возвращает экземпляр объекта CanvasGradient, представляющий созданный нами радиальный градиент.

    Пример:

    var rgSample = ctxCanvas.createRadialGradient(100, 100, 10, 150, 100, 120);

    Это выражение создает радиальный градиент и помещает его в переменную rgSample. Созданный градиент будет "распространяться" от внутренней окружности, центр которой находится в точке с координатами [100,100], а радиус равен 10 пикселам, к внешней окружности с центром в точке [150,100] и радиусом 120 пикселов.

    Второй этап — расстановка ключевых точек — выполняется с помощью уже знакомого нам метода addColorStop объекта CanvasGradient:

    <градиент>.addColorStop(<положение ключевой точки>, <цвет>)

    Первый параметр определяет относительное положение создаваемой ключевой точки на промежутке между внутренней и внешней окружностями. Он задается в виде числа от 0.0 (начало промежутка, т. е. внутренняя окружность) до 1.0 (конец промежутка, т. е. внешняя окружность). Второй параметр, как мы уже знаем, задает цвет, который должен присутствовать в данной ключевой точке.

    Как и линейный градиент, радиальный может содержать сколько угодно ключевых точек.

    Пример:

    rgSample.addColorStop(0, "#CCCCCC");

    rgSample.addColorStop(0.8, "black");

    rgSample.addColorStop(1, "#00FF00");

    Этот Web-сценарий создает на полученном нами ранее радиальном градиенте три ключевые точки:

    — расположенную в начальной точке воображаемого промежутка между окружностями (т. е. на внутренней окружности) и задающую серый цвет;

    — расположенную в точке, отстоящей на 80 % длины воображаемого промежутка между окружностями от его начальной точки, и задающую черный цвет;

    — расположенную в конечной точке воображаемого промежутка между окружностями (т. е. на внешней окружности) и задающую зеленый цвет.

    Третий этап — использование готового радиального градиента — выполняется так же, как для линейного градиента, т. е. присваиванием его свойству strokeStyle или fillStyle:

    ctxCanvas.fillStyle = rgSample;

    Радиальный градиент ведет себя точно так же, как линейный — фиксируется на канве и частично "проявляется" на фигурах, к которым применен.

    Листинг 22.10 иллюстрирует пример.

    Листинг 22.10

    var rgSample = ctxCanvas.createRadialGradient(100, 100, 10, 150, 100, 120);

    rgSample.addColorStop(0, "#CCCCCC");

    rgSample.addColorStop(0.8, "black");

    rgSample.addColorStop(1, "#00FF00");

    ctxCanvas.fillStyle = rgSample;

    ctxCanvas.fillRect(0, 0, 200, 200);

    Web-сценарий из листинга 22.10 рисует прямоугольник и заполняет его радиальным градиентом, аналогичным созданному ранее в этом разделе. Отметим, что центры внутренней и внешней окружностей, описывающих этот градиент, различаются, за счет чего достигается весьма примечательный эффект, который лучше видеть своими глазами.

    Графический цвет

    Графический цвет — это обычное графическое изображение, которым закрашиваются линии или заливки. Таким графическим изображением может быть содержимое как обычного графического файла, так и другой канвы.

    Графический цвет создают в три этапа.

    Первый этап необходим только в том случае, если мы используем в качестве цвета содержимое графического файла. Файл нужно как-то загрузить, удобнее всего — с помощью объекта Web-обозревателя Image, который представляет графическое изображение, хранящееся в файле.

    Сначала с помощью знакомого нам по главе 14 оператора new нам потребуется создать экземпляр объекта Image:

    var imgSample = new Image();

    Объект Image поддерживает свойство src, задающее интернет-адрес загружаемого графического файла в виде строки. Если присвоить этому свойству интернет-адрес какого-либо файла, данный файл тотчас будет загружен:

    imgSample.src = "graphic_color.jpg";

    В дальнейшем мы можем использовать данный экземпляр объекта Image для создания графического цвета.

    Второй этап — собственно создание графического цвета с помощью метода createPattern:

    <контекст рисования>.createPattent(<графическое изображение или канва>,<режим повторения>)

    Первый параметр задает графическое изображение в виде экземпляра объекта Image или канву в виде экземпляра объекта HTMLCanvasElement.

    Часто бывает так, что размеры заданного графического изображения меньше, чем фигуры, к которой должен быть применен графический цвет. В этом случае изображение повторяется столько раз, чтобы полностью "вымостить" линию или заливку. Режим такого повторения задает второй параметр метода createPattern. Его значение должно быть одной из следующих строк:

    — "repeat" — изображение будет повторяться по горизонтали и вертикали;

    — "repeat-x" — изображение будет повторяться только по горизонтали;

    — "repeat-y" — изображение будет повторяться только по вертикали;

    — "no-repeat" — изображение не будет повторяться никогда; в этом случае часть фигуры останется не занятой им.

    Метод createPattern возвращает экземпляр объекта CanvasPattern, представляющий созданный нами графический цвет:

    var cpSample = ctxCanvas.createPattern(imgSample, "repeat");

    Третий этап — использование готового графического цвета — выполняется так же, как для градиентов, т. е. присваиванием его свойству strokeStyle или fillStyle.

    Пример:

    ctxCanvas.fillStyle = cpSample;

    ctxCanvas.fillRect(0, 0, 200, 100);

    Этот Web-сценарий рисует прямоугольник с заливкой на основе созданного нами ранее графического цвета.

    Графический цвет не фиксируется на канве, а полностью применяется к рисуемой фигуре. В этом его принципиальное отличие от градиентов.

    НА ЗАМЕТКУ

    В приведенном ранее примере мы предположили, что файл graphic_color.jpg имеет небольшие размеры или уже присутствует в кэше Web-обозревателя и поэтому загрузится очень быстро. Но если он, так сказать, "задержится в пути", Web-сценарий не выполнится. Поэтому изображения, хранящиеся в других файлах, выводятся по иной методике, которую мы скоро рассмотрим.

    Вывод внешних изображений

    Внешним по отношению к канве называется графическое изображение, хранящееся в отдельном файле, или содержимое другой канвы. Канва предоставляет довольно мощные средства для вывода таких изображений: мы можем изменить размеры изображения и даже вывести только его часть.

    Вывести внешнее изображение на канву проще всего методом drawImage, точнее, его сокращенным форматом:

    <контекст рисования>.drawImage(<графическое изображение или канва>,<горизонтальная координата>, <вертикальная координата>)

    Первый параметр задает графическое изображение в виде экземпляра объекта Image или канву в виде экземпляра объекта HTMLCanvasElement. Второй и третий параметры определяют координаты точки канвы, где должен находиться верхний левый угол выводимого изображения; они задаются в пикселах в виде чисел. Метод drawImage не возвращает результата.

    Вот Web-сценарий, который загружает изображение из файла someimage.jpg и выводит его на канву так, чтобы его верхний левый угол находился в точке [0,0], т. е. в верхнем левом углу канвы:

    var imgSample = new Image();

    imgSample.src = "someimage.jpg";

    ctxCanvas.drawImage(imgSample, 0, 0);

    Если нам нужно при выводе внешнего изображения изменить его размеры, к нашим услугам расширенный формат метода drawImage:

    <контекст рисования>.drawImage(<графическое изображение или канва>,<горизонтальная координата>, <вертикальная координата><ширина>, <высота>)

    Первые три параметра нам уже знакомы. Четвертый и пятый параметры задают, соответственно, ширину и высоту выводимого изображения в пикселах в виде чисел.

    Вот пример Web-сценария, который выводит загруженное ранее изображение, растягивая его так, чтобы занять канву целиком:

    ctxCanvas.drawImage(imgSample, 0, 0, 400, 300);

    Рассмотрим теперь самый сложный случай — вырезание из внешнего изображения фрагмента и вывод его на канву с изменением размеров. Для этого применяется третий по счету формат метода drawImage:

    <контекст рисования>.drawImage(<графическое изображение или канва>,<горизонтальная координата вырезаемого фрагмента>,<вертикальная координата вырезаемого фрагмента><ширина вырезаемого фрагмента>, <высота вырезаемого фрагмента>,<горизонтальная координата вывода>, <вертикальная координата вывода>,<ширина вывода>, <высота вывода>)

    Первый параметр нам уже знаком и задает внешнее изображение.

    Второй и третий параметры определяют координаты верхнего левого угла вырезаемого из внешнего изображения фрагмента. Они задаются относительно внешнего изображения в пикселах в виде чисел.

    Четвертый и пятый параметры определяют ширину и высоту вырезаемого из внешнего изображения фрагмента. Они также задаются относительно внешнего изображения в пикселах в виде чисел.

    Шестой и седьмой параметры определяют координаты точки канвы, где должен находиться верхний левый угол выводимого фрагмента внешнего изображения. Они задаются относительно канвы в пикселах в виде чисел.

    Восьмой и девятый параметры определяют ширину и высоту выводимого фрагмента внешнего изображения в пикселах в виде чисел.

    Вот Web-сценарий, который вырезает из загруженного ранее изображения фрагмент с верхним левым углом в точке [20,40], шириной 40 и высотой 20 пикселов и выводит этот фрагмент на канву, растягивая его так, чтобы занять канву целиком:

    ctxCanvas.drawImage(imgSample, 20, 40, 40, 20, 0, 0, 400, 300);

    Все приведенные ранее примеры подразумевают, что файл, хранящий внешнее изображение, загружается очень быстро (так может случиться, если файл имеет небольшие размеры или уже находится в кэше Web-обозревателя.) Но чаще всего случается так, что файл не успевает загрузиться к тому моменту, когда начнет выполняться выводящий его на канву код, и мы получим ошибку выполнения Web- сценария. Как избежать этого?

    Очень просто. Объект Image поддерживает событие onload, возникающее после окончания загрузки изображения. Данному событию соответствует одноименное свойство, которому следует присвоить функцию — обработчик этого события. Web-сценарий из листинга 22.11 иллюстрирует сказанное.

    Листинг 22.11

    var imgSample = new Image();

    function imgOnLoad() {

    ctxCanvas.drawImage(imgSample, 20, 40, 40, 20, 0, 0, 400, 300);

    }

    imgSample.src = "someimage.jpg";

    imgSample.onload = imgOnLoad;


    НА ЗАМЕТКУ

    Именно так выполняется привязка обработчиков к событиям некоторых объектов Web- обозревателя — присваиванием функции-обработчика свойству, которое соответствует нужному событию. Можно ли использовать для этого методы библиотеки Ext Core, автор не проверял.

    Создание тени у рисуемой графики

    Еще канва позволяет создавать тень у всех рисуемых фигур. Для задания ее параметров применяют четыре свойства, которые мы сейчас рассмотрим.

    Свойства shadowOffsetX и shadowOffsetY задают смещение тени, соответственно, по горизонтали и вертикали относительно фигуры в пикселах в виде чисел. Положительные значения смещают тень вправо и вниз, а отрицательные — влево и вверх. Значения этих свойств по умолчанию — 0, т. е. фактически отсутствие тени.

    Пример:

    ctxCanvas.shadowOffsetX = 2;

    ctxCanvas.shadowOffsetY = -2;

    Свойство shadowBlur задает степень размытия тени в виде числа. Чем больше это число, тем более размыта тень. Значение по умолчанию — 0, т. е. отсутствие размытия.

    Пример:

    ctxCanvas.shadowBlur = 4;

    Свойство shadowColor задает цвет тени. Цвет задается в виде строки в любом из форматов, описанных в начале этой главы. Значение по умолчанию — черный непрозрачный цвет.

    Пример:

    ctxCanvas.shadowColor = "rgba(128, 128, 128, 0.5)";

    После того как мы зададим параметры тени, они будут применяться ко всей графике, которую мы далее нарисуем. На параметры тени уже нарисованной графики они влияния не окажут.

    Пример:

    ctxCanvas.fillText("Двое: я и моя тень.", 150, 50);

    Преобразования системы координат 

    Преобразования — это различные действия (изменение масштаба, поворот и перемещение точки начала координат), которые мы можем выполнить над системой координат канвы.

    При выполнении преобразования изменяется только система координат канвы. Рисуемая после этого графика будет создаваться в измененной системе координат; а нарисованная графика остается неизменной.

    Сохранение и загрузка состояния

    Первое, что нам нужно рассмотреть применительно к преобразованиям, — сохранение и загрузка состояния канвы. Эти возможности нам очень пригодятся в дальнейшем.

    При сохранении состояния канвы сохраняются:

    — все заданные трансформации (будут описаны далее);

    — значения свойств globalAlpha, globalCompositeOperation (будет описано далее), fillStyle, lineCap, lineJoin, lineWidth, miterLimit и strokeStyle;

    — все заданные маски (будут описаны далее).

    Сохранение состояния канвы выполняет метод save. Он не принимает параметров и не возвращает результата.

    Состояние канвы сохраняется в памяти компьютера и впоследствии может быть восстановлено. Более того, сохранять состояние канвы можно несколько раз; при этом все предыдущие состояния остаются в памяти и их также можно восстановить.

    Восстановить сохраненное ранее состояние можно вызовом метода restore. Он не принимает параметров и не возвращает результата.

    При вызове этого метода будет восстановлено самое последнее из сохраненных состояний канвы. При последующем его вызове будет восстановлено предпоследнее сохраненное состояние и т. д. Этой особенностью часто пользуются для создания сложной графики.

    Перемещение начала координат канвы

    Мы можем переместить начало координат канвы в любую другую ее точку. После этого все координаты будут отсчитываться от нового начала координат.

    Для перемещения начала координат канвы в другую точку достаточно вызвать метод translate:

    <контекст рисования>.translate(<горизонтальная координата>,<вертикальная координата>)

    Параметры метода translate определяют координаты точки, в которой должно находиться новое начало координат канвы. Они отсчитываются от текущего начала координат, измеряются в пикселах и задаются в виде чисел. Метод не возвращает результата.

    При перемещении начала координат канвы будут учитываться все трансформации, примененные к канве ранее. Значит, если мы ранее переместили начало координат в точку [50,50] и теперь снова перемещаем его, уже в точку [100,50], в результате начало координат окажется в точке [150,100], если отсчитывать от верхнего левого угла канвы (начала системы координат по умолчанию).

    Листинг 22.12 иллюстрирует пример.

    Листинг 22.12

    ctxCanvas.save();

    ctxCanvas.translate(100, 100);

    ctxCanvas.fillRect(0, 0, 50, 50);

    ctxCanvas.translate(100, 100);

    ctxCanvas.fillRect(0, 0, 50, 50);

    ctxCanvas.restore();

    ctxCanvas.fillRect(0, 0, 50, 50);

    Web-сценарий из листинга 22.12 делает следующее:

    1. Сохраняет текущее состояние канвы.

    2. Перемещает начало координат в точку [100,100].

    3. Рисует квадрат размерами 50 50 пикселов, верхний левый угол которого находится в точке начала координат.

    4. Опять перемещает начало координат в точку [100,100]. Обратим внимание, что координаты этой точки теперь отсчитываются от нового начала системы координат, установленного предыдущим вызовом метода translate.

    5. Опять рисует квадрат размерами 50 50 пикселов, верхний левый угол которого находится в начале координат.

    6. Восстанавливает сохраненное состояние канвы, в том числе и положение начала системы координат (это положение по умолчанию, т. е. верхний левый угол канвы).

    7. Рисует третий по счету квадрат размерами 50 50 пикселов, верхний левый угол которого находится в начале координат.

    В результате мы увидим три квадрата, расположенные на воображаемой диагонали, тянущейся от верхнего левого угла канвы вниз и вправо.

    ВНИМАНИЕ!

    К сожалению, не существует простого способа вернуться к системе координат по умолчанию. Единственная возможность — сохранить состояние системы координат перед любыми трансформациями и потом восстановить его.

    Поворот системы координат

    Метод rotate позволяет повернуть оси системы координат на произвольный угол вокруг точки начала координат; при этом поворот будет выполняться по часовой стрелке:

    <контекст рисования>.rotate(<угол поворота>)

    Единственный параметр метода задает угол поворота системы координат в виде числа в радианах; этот угол отсчитывается от горизонтальной оси. Метод не возвращает результата.

    При повороте системы координат будут учитываться все трансформации, примененные к канве ранее. Так, если мы ранее переместили начало системы координат в другую точку и теперь поворачиваем систему координат на какой-то угол, она будет повернута вокруг нового начала координат. А если мы после этого снова повернем систему координат на какой-то угол, она будет повернута относительно текущего положения горизонтальной оси — уже повернутой ранее.

    Листинг 22.13 иллюстрирует пример.

    Листинг 22.13

    ctxCanvas.translate(200, 150);

    for (var i = 0; i < 3; i++)

    { ctxCanvas.rotate(Math.PI / 6); ctxCanvas.strokeRect(-50, -50, 100, 100);

    }

    Web-сценарий из листинга 22.13 сдвигает начало координат в центр канвы (точку [200,150]), после чего трижды поворачивает систему координат на ?/6 радиан (30°) и рисует в центре канвы квадрат без заливки. Обратим внимание, что каждый последующий поворот системы координат выполняется с учетом того, что она уже была повернута ранее.

    Изменение масштаба системы координат

    Метод scale дает возможность изменить масштаб системы координат канвы в бoльшую или меньшую сторону:

    <контекст рисования>.scale(<масштаб по горизонтали>,<масштаб по вертикали>)

    Параметры этого метода задают масштаб для горизонтальной и вертикальной оси системы координат в виде чисел. Числа меньше 1.0 задают уменьшение масштаба, а числа больше 1.0 — увеличение; если нужно оставить масштаб по какой-то из осей неизменным, достаточно указать значение 1.0 соответствующего параметра. Метод scale не возвращает результата.

    При изменении масштаба координат канвы будут учитываться все трансформации, примененные к канве ранее: перемещения начала координат, повороты и изменения масштаба.

    Листинг 22.14 иллюстрирует пример.

    Листинг 22.14

    ctxCanvas.save();

    ctxCanvas.strokeRect(0, 0, 50, 50);

    ctxCanvas.scale(3, 1);

    ctxCanvas.strokeRect(0, 0, 50, 50);

    ctxCanvas.restore();

    ctxCanvas.save();

    ctxCanvas.scale(1, 3);

    ctxCanvas.strokeRect(0, 0, 50, 50);

    ctxCanvas.restore();

    ctxCanvas.save();

    ctxCanvas.scale(3, 3);

    ctxCanvas.strokeRect(0, 0, 50, 50);

    ctxCanvas.restore();

    Web-сценарий из листинга 22.14 делает следующее:

    1. Сохраняет текущее состояние канвы.

    2. Рисует квадрат размерами 50 50 пикселов, верхний левый угол которого находится в начале координат.

    3. Увеличивает масштаб горизонтальной координатной оси в 3 раза.

    4. Рисует второй квадрат размерами 50 50 пикселов, верхний левый угол которого находится в начале координат.

    5. Восстанавливает сохраненное ранее состояние канвы и сохраняет его снова.

    6. Увеличивает масштаб вертикальной координатной оси в 3 раза.

    7. Рисует третий квадрат размерами 50 50 пикселов, верхний левый угол которого находится в начале координат.

    8. Восстанавливает сохраненное ранее состояние канвы и сохраняет его снова.

    9. Увеличивает масштаб обоих координатных осей в 3 раза.

    10. Рисует четвертый квадрат размерами 50 50 пикселов, верхний левый угол которого находится в начале координат.

    11. Восстанавливает сохраненное ранее состояние канвы.

    В результате мы увидим четыре прямоугольника с реальными размерами 50 50, 150 50, 50 150 и 150 150 пикселов.

    Управление наложением графики

    Когда мы рисуем новую фигуру на том месте канвы, где уже присутствует ранее нарисованная фигура, новая фигура накладывается на старую, перекрывая ее. Это поведение канвы по умолчанию, которое мы можем изменить.

    Для управления наложением графики предусмотрено свойство

    globalCompositeOperation.

    Вот его допустимые значения:

    — "source-over" — новая фигура накладывается на старую, перекрывая ее (значение по умолчанию);

    — "destination-over" — новая фигура перекрывается старой;

    — "source-in" — отображается только та часть новой фигуры, которая накладывается на старую. Остальные части новой и старой фигур не выводятся;

    — "destination-in" — отображается только та часть старой фигуры, на которую накладывается новая. Остальные части новой и старой фигур не выводятся;

    — "source-out" — отображается только та часть новой фигуры, которая не накладывается на старую. Остальные части новой фигуры и вся старая фигура не выводятся;

    — "destination-out" — отображается только та часть старой фигуры, на которую не накладывается новая. Остальные части новой фигуры и вся старая фигура не выводятся;

    — "source-atop" — отображается только та часть новой фигуры, которая накладывается на старую; остальная часть новой фигуры не выводится. Старая фигура выводится целиком и находится ниже новой;

    — "destination-atop" — отображается только та часть старой фигуры, которая накладывается на новую; остальная часть старой фигуры не выводится. Новая фигура выводится целиком и находится ниже старой;

    — "lighter" — цвета накладывающихся частей старой и новой фигур складываются, результирующий цвет получается более светлым, окрашиваются накладывающиеся части фигур;

    — "darker" — цвета накладывающихся частей старой и новой фигур вычитаются, в полученный цвет, который получается более темным, окрашиваются накладывающиеся части фигур;

    — "xor" — отображаются только те части старой и новой фигур, которые не накладываются друг на друга;

    — "copy" — выводится только новая фигура; все старые фигуры удаляются с канвы.

    Заданный нами способ наложения графики действует только для графики, которую мы нарисуем после этого. На уже нарисованную графику он влияния не оказывает.

    Листинг 22.15 иллюстрирует пример.

    Листинг 22.15

    ctxCanvas.fillStyle = "blue";

    ctxCanvas.fillRect(0, 50, 400, 200);

    ctxCanvas.fillStyle = "red";

    ctxCanvas.globalCompositeOperation = "source-over";

    ctxCanvas.fillRect(100, 0, 200, 300);

    Web-сценарий из листинга 22.15 рисует два накладывающихся прямоугольника разных цветов и позволит изучить поведение канвы при разных значениях свойства globalCompositeOperation. Изменяем значение этого свойства, перезагружаем Web-страницу нажатием клавиши <F5> и смотрим, что получится.

    Создание маски

    О масках мы уже знаем из главы 21. В терминологии канвы так называется особая фигура, задающая своего рода "окно", сквозь которое будет видна часть графики, нарисованной на канве. Вся графика, не попадающая в это "окно", будет скрыта. При этом сама маска на канву не выводится.

    Маской может быть только сложный контур, рисование которого описано ранее. И создается она примерно так же.

    Вот последовательность действий.

    1. Рисуем сложный контур, который станет маской.

    2. Обязательно делаем его закрытым.

    3. Вместо вызова методов stroke или fill вызываем метод clip, который не принимает параметров и не возвращает результата.

    4. Рисуем графику, которая будет находиться под маской.

    В результате нарисованная нами на шаге 4 графика будет частично видна сквозь маску. Требуемый результат достигнут.

    Листинг 22.16 иллюстрирует пример.

    Листинг 22.16

    ctxCanvas.beginPath();

    ctxCanvas.moveTo(100, 150);

    ctxCanvas.lineTo(200, 0);

    ctxCanvas.lineTo(200, 300);

    ctxCanvas.closePath();

    ctxCanvas.clip();

    ctxCanvas.fillRect(0, 100, 400, 100);

    Web-сценарий из листинга 22.16 сначала рисует маску в виде треугольника, а потом — прямоугольник, часть которого будет видна сквозь маску.

    Создание графического логотипа Web-сайта

    Вооружившись необходимыми знаниями о канве HTML 5, контексте рисования и его свойствах и методах, давайте попрактикуемся в Web-художествах. Создадим графический логотип для нашего Web-сайта, который поместим в контейнер cheader вместо маловыразительного текстового заголовка.

    Сначала сформулируем требования.

    — Логотип нашего Web-сайта будет представлять собой подчеркнутую надпись "Справочник по HTML и CSS" с тенью.

    — Ширина логотипа будет такой, чтобы занимать все пространство между левым краем контейнера cheader и левым краем Web-формы поиска.

    — Ширина логотипа будет меняться при изменении размеров окна Web-обозревателя.

    Откроем Web-страницу index.htm в Блокноте, удалим все содержимое контейнера cheader и вставим в него такой HTML-код:

    <CANVAS ID="cnv" WIDTH="600" HEIGHT="80"></CANVAS>

    Он создает канву cnv, в которой и будет рисоваться логотип.

    Так, канва у нас готова. Теперь нам нужно написать Web-сценарий, который будет получать размеры канвы и рисовать в ней логотип таким образом, чтобы он занял канву целиком.

    Откроем файл Web-сценария main.js в Блокноте. И подумаем.

    Где нам поместить код, выполняющий рисование логотипа? Может быть, в теле функции, передаваемой параметром методу onReady объекта Ext? Тогда логотип будет нарисован всего однажды — после загрузки Web-страницы.

    Но мы хотим сделать так, чтобы ширина логотипа менялась при изменении ширины окна Web-обозревателя. Для этого нам следует сделать две вещи. Во-первых, придется в ответ на изменение ширины окна менять размеры канвы cnv — это очевидно. Во-вторых, понадобится после каждого изменения размеров канвы перерисовывать логотип — с учетом изменившихся размеров канвы.

    Вывод: поместим код, выполняющий рисование логотипа, в тело функции adjustContainers. Эта функция, как мы помним, устанавливает размеры контейнеров, составляющих дизайн нашей Web-страницы, и выполняется при каждом изменении размеров окна Web-обозревателя — как раз то, что нам нужно.

    Поместим в конец тела функции adjustContainers два выражения:

    Ext.get("cnv"). set({ width: elCSearch.getX() — 40 });

    drawHeader();

    Первое выражение устанавливает ширину канвы cnv, чтобы она заняла все пространство между левым краем контейнера cheader и левым краем Web-формы поиска. Нужное значение ширины получается следующим образом.

    1. Берется значение горизонтальной координаты свободного контейнера csearch, в котором находится Web-форма поиска (см. главу 21). (Контейнер csearch хранится в переменной elCSearch.) Получается значение ширины, которую может занять канва, без учета внутренних отступов.

    2. Из полученной ширины вычитается 20 — размер внутреннего отступа слева в пикселах, заданного в именованном стиле, привязанном к контейнеру cheader.

    3. Из полученной разности вычитается еще 20 (итого получается 40) — размер отступа между правым краем канвы и левым краем контейнера csearch с Web- формой поиска. Это нужно, чтобы канва не примыкала к Web-форме поиска вплотную.

    Полученное значение ширины присваивается атрибуту WIDTH тега <CANVAS> с помощью метода set объекта Ext Core Element (см. главу 15).

    Второе выражение вызывает функцию drawHeader, которая и выполнит рисование логотипа. Листинг 22.17 содержит код, который объявляет эту функцию; мы можем поместить его в любое место файла main.js.

    Листинг 22.17

    function drawHeader() {

    var elCanvas = Ext.get("cnv");

    var cnvWidth = elCanvas.getAttribute("width");

    var ctx = elCanvas.dom.getContext("2d"); ctx.beginPath();

    ctx.strokeStyle = "#B1BEC6";

    ctx.moveTo(0, 60);

    ctx.lineTo(cnvWidth, 60);

    ctx.stroke();

    ctx.shadowOffsetX = 2;

    ctx.shadowOffsetY = 2;

    ctx.shadowBlur = 2;

    ctx.shadowColor = "#CDD9DB";

    ctx.font = "normal 20pt Arial";

    ctx.textAlign = "right";

    ctx.textBaseline = "bottom";

    ctx.fillStyle = "#3B4043";

    ctx.scale(2, 1.3);

    ctx.fillText("Справочник по HTML и CSS", cnvWidth / 2, 60 / 1.3,

    cnvWidth / 2);

    }

    Рассмотрим листинг 22.17 по частям.

    Сначала получаем канву cnv:

    var elCanvas = Ext.get("cnv");

    Затем получаем текущую ширину канвы:

    var cnvWidth = elCanvas.getAttribute("width");

    Рисуем горизонтальную линию, которая "вытянется" на всю ширину канвы и подчеркнет текст заголовка, который мы выведем потом:

    ctx.beginPath();

    ctx.strokeStyle = "#B1BEC6";

    ctx.moveTo(0, 60);

    ctx.lineTo(cnvWidth, 60);

    ctx.stroke();

    Задаем параметры тени для текста:

    ctx.shadowOffsetX = 2;

    ctx.shadowOffsetY = 2;

    ctx.shadowBlur = 2;

    ctx.shadowColor = "#CDD9DB";

    Задаем шрифт текста. Берем его параметры из стиля переопределения тега <H1>,

    созданного нами в таблице стилей main.css:

    ctx.font = "normal 20pt Arial";

    Задаем для текста горизонтальное выравнивание по правому краю и вертикальное выравнивание по нижнему краю символов:

    ctx.textAlign = "right";

    ctx.textBaseline = "bottom";

    Так нам будет проще вычислять координаты для вывода текста. Задаем цвет заливки — он станет цветом выводимого текста: ctx.fillStyle = "#3B4043";

    Увеличиваем масштаб системы координат канвы:

    ctx.scale(2, 1.3);

    Отметим, что масштаб у горизонтальной оси координат больше, чем у вертикальной, — значит, текст будет растянут по горизонтали.

    Выводим текст "Справочник по HTML и CSS" в виде заливки без контура:

    ctx.fillText("Справочник по HTML и CSS", cnvWidth / 2, 60 / 1.3, cnvWidth / 2);

    Отметим несколько моментов.

    — В качестве горизонтальной координаты вывода текста мы указали ширину канвы. Если учесть, что ранее мы задали горизонтальное выравнивание по правому краю, текст будет выровнен по правому краю канвы.

    — В качестве вертикальной координаты вывода текста мы указали позицию, на которой находится уже нарисованная нами горизонтальная линия. Если учесть, что мы ранее задали вертикальное выравнивание по нижнему краю символов, получится так, что текст будет находиться выше этой линии; таким образом, линия подчеркнет текст.

    — Мы указали максимальную ширину выводимого текста, равной ширине канвы. Благодаря этому текст при любом изменении ширины окна Web-обозревателя не вылезет за края канвы (подробнее см. в разделе, посвященном выводу текста).

    — Вероятно, это самый важный момент. Текст мы вывели после изменения масштаба системы координат, но значения координат, которые указали при выводе, принадлежат старой системе координат. Чтобы преобразовать их в новой системе координат, мы разделим их на соответствующие величины масштабов (2 и 1,3).

    Выполнение функции drawHeader закончено.

    Откроем Web-страницу в Web-обозревателе. Посмотрим на новый логотип. Конечно, если постараться, можно сделать и лучше. Пусть это будет вашим домашним заданием.

    На этом автор прощается с вами. Счастливо вам доделать Web-сайт — справочник по HTML и CSS! И успехов на поприще современного Web-дизайна!









    Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх