Анимация и эффекты в javascript с помощью mootools. Часть 2

January 28, 2008

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

В прошлый раз мы остановились на изучении методов поиска html-элементов на странице. “Страшное” чередование вызовов getElementsByTagName и getElementById осталось в далеком прошлом: все современные javascript-библиотеки имеют средства для того, чтобы выполнить поиск элемента на странице, записав его характеристики: id, класс css, имя тега или их комбинацию. Честно говоря, в этом плане mootools “слабее” чем jquery (об этой библиотеке я писал в серии статей летом 2007), но сегодня разговор, прежде всего, об анимации.

Начнем мы с простенького задания: при наведении мыши на некоторое изображение оно должно смениться другим. Сложность не в том, чтобы создать обработчик события omouseover и onmouseout, а в сущей безделице: в том, чтобы запустить процесс анимации именно тогда, когда все нужные для нее ресурсы будут загружены браузером. Т.е мы навели мышь на картинку, и она должна тут же смениться новой. А не так: сначала исчезает старая картинка, и только через несколько секунд появляется новая. Даже самый начинающий javascript-разработчик знает, что можно предварительно загрузить нужные картинки, но не показывая их до поры до времени, например, так:
  1. <img src="1.png" onmouseover="this.src='2.png'" onmouseout="this.src='1.png'"/>
  2.  
  3. // и где-то там в коде javascript я запускаю загрузки изображения
  4. var imgHover = new Image ();
  5. imgHover.src = '2.png';
И все было бы хорошо, если бы веб-страница не содержала множество изображений, css и javascript-файлов загружающихся в хаотичном порядке. Вовсе не факт, что к тому времени как пользователь наведет мышь на изображение то, второе, изображение будет уже загружено. В составе mootols есть класс Asset, его назначение – динамическая загрузка css и js-файлов, изображений. Для картинок (к сожалению, только для них) можно установить обработчик события “все картинки были загружены”. Вот первый пример:
  1. new Asset.javascript('js/logic.js', {id: 'js_logic'});
  2. new Asset.css('css/for_ie.css', {id: 'css_ie_fix'});
Первый параметр вызова функции css или javascript – имя загружаемого файла, затем идет перечисление атрибутов, которые будут назначены для динамически созданных тегов "script" и "link". Можно загрузить одну картинку, а в обработчике события “загрузка завершена” назначить, в свою очередь, обработку событий mouseover и mouseout для изображения (предполагается, что на странице есть картинка с id равным “roll”):
  1. function onLoadComplete (){
  2.  $('roll').onmouseover = function(){ this.src = '2.png' };
  3.  $('roll').onmouseout = function(){ this.src = '1.png' }; 
  4. }
  5.  
  6. new Asset.image('2.png', {onload: onLoadComplete});
Если вы хотите динамически загрузить несколько картинок, то более удобным будет воспользоваться функцией images. Ей в качестве параметра передается не только массив с именами картинок, но и две функции (хотя они и не обязательны). Первая функция onComplete вызывается тогда, когда все картинки были загружены, а вторая – onProgress – вызывается каждый раз, как загрузится очередное изображение.
  1. new Asset.images(
  2.  ['1.png', '2.png'], 
  3.  {
  4.  onComplete: function(){ alert('all images loaded'); },
  5.  onProgress: function(){ alert ('next pic was loaded'); }  
  6.  }
  7. );
Загруженную картинку можно автоматически переместить в определенное место страницы. Для этого к результату вызова “new Asset.image” следует применить функцию injectInside. А ей в качестве параметра указать id того элемента страницы, внутрь которого будет помещена эта картинка. В примере предполагается, что существует на странице некоторый тег div с id равным “block”. Картинка не затрет старое содержимое элемента “block”, а добавит изображение к этому содержимому в самый конец.
  1. window.addEvent('domready', function(){
  2.    new Asset.image('2.png', {alt : 'pic', title : 'info' }).injectInside ('block'); 
  3. });
Если вам хочется вставить картинку внутрь block, но не в конце, а в самом начале - используйте вызов injectTop. Найдется и еще пара функций: injectAfter – изображение будет помещено после тега div, injectBefore – до блока div. Естественно, что использовать injectInside, injectBefore, injectTop можно не только для динамически создаваемых картинок, но и для существующих тегов:
  1. $('friend').injectTop ('block');
Здесь предполагается, что friend и block – два блока div размещенных в тексте страницы. Общая рекомендация: не забывайте весь код mootols помещать внутрь функции обрабатывающей событие domready. В противном случае код может и не работать: возможно, на момент выполнения js-кода перемещающего один div внутрь другого сами эти div-ы еще не существуют.

Разумеется, что тема динамического изменения содержимого веб-страницы: добавление новых узлов DOM, их перемещение и удаление не так проста. Одно дело если вы разрабатываете простенькие сайты, где подобных операций немного, но как только количество операций начинает увеличиваться, то вам следует подумать, как сделать так, чтобы браузер не слишком сильно “тормозил”. Все же и javascript и DOM не самые быстрые вещи. Я несколько раз сталкивался с тем, что брался некоторый javascript-framework, начиналась разработка с его помощью и … И спустя какое-то время понимали, что удобство разработки это конечно хорошо, почему же код работает так медленно? По этой теме полезным будет почитать комментарии к статье http://habrahabr.ru/blog/webdev/32221.html.

Показанный выше прием с назначением обработчика события “('roll').onmouseover = …” не самый лучший. Возможно, так привязать к некоторому элементу страницы функцию обработчик, чтобы в качестве параметра ей из mootols был передан особый объект Event. Внутри которого найдется парочка интересных свойств и методов. В следующем примере я использую специальную функцию window.addEvent, которая добавляет событие для тега div c id равным “block”. Но так, что внутри этой функции я могу использовать объект event. Он содержит сведения о произошедшем событии (не только координаты мыши в виде переменной event.client.x и event.client.y, но и сведения об нажатых клавишах, также позволит вам управлять обработкой события):

1. Нажаты ли клавиши shift (переменная event.shift), клавиши ctrl и alt (переменные: event.control и event.alt).

2. Если на клавиатуре нажата какая-то из клавиш, то сведения об этом хранятся в переменных (event.key и event.code). А сведения о скроллинге найдутся в переменной event.wheel.

3. Для того чтобы отменить распространение события используйте метод stopPropagation, а для того чтобы отменить действие по-умолчанию – preventDefault. Например, по нажатию на кнопку “отправить форму” вы проверили корректность заполнения полей и решили, что форму отправлять нельзя – данные были введены с ошибкой.
  1. function over(event){  
  2.   alert(event.client.x);
  3. };
  4.  
  5. window.addEvent('domready', function(){
  6.  $('block').addEvent('mouseover', over.bindWithEvent(block));
  7. });
Теперь, вооружившись теорией, перейдем собственно к эффектам в mootols. Для работы с ними нам потребуется объект Fx.Base, на базе которого построены объекты Fx.CSS, Fx.Style и Fx.Styles. Именно с помощью них мы сделаем первый пример: блок div будет плавно менять свой цвет при наведении на него мыши. Методы работы с Fx всегда сходны: сначала нужно создать объект, хранящий сведения о преобразовании некоторого css-атрибута для элемента.
  1. var bgColor = new Fx.Style('block', 'backgroundColor', {duration:500});
Здесь я создал объект bgColor позволяющий управлять для элемента с id равным block значением css-свойства backgroundColor. Третий параметр функции Fx.Style – это объект, содержащий параметры будущей анимации. Кроме параметра duration – длительности в миллисекундах выполняемой анимации можно указать также transition - параметр, управляющий тем, как именно (по какому математическому закону) будет выполняться изменение атрибута backgroundColor от некоторого начального значения до конечного. Параметр unit служит для указания того, в каких единицах измерения задается начальное и конечное значение анимируемого параметра (px, %). Параметр fps (значение по-умолчанию равно 50) влияет на то, с какой частотой будет обновляться значение анимируемого параметра. Также можно указать обработчик события, который будет вызван тогда, когда анимация будет запущена - onStart, и обработчик для ситуации onComplete – когда анимация будет завершена.

После создания объекта анимации необходимо выполнить его запуск, используя функцию start. Первый ее параметр начальное значение атрибута, а второй – конечное:
  1. bgColor.start('#ff00ff', '#ffffff');

  <iframe src="../contents/data/mediawiki/__special__/html/mootools_2/mootools_2_demo_1.html" width="750" height="400">
   нет поддержки iframe
  </iframe>


Запущенную анимацию можно остановить с помощью stop (параметров у функции нет) или же быстро перемотать в нужную точку – set.
  1. // делаем элемент прозрачным
  2. new Fx.Style('block', 'opacity').set(0);
Изменять можно любой атрибут css, например, меняя left и top можно создать анимацию выезжающей из-за границы экрана (например, при нажатии на кнопку) формы регистрации.
  1. var posLeft = new Fx.Style('block', 'left', {duration:5000});
  2. posLeft.set (-400);// вначале прячем форму за границу экрана
  3. // а по нажатию на кнопку "показать форму" запускаем анимацию:
  4. posLeft.start(-400, 400);

  <iframe src="../contents/data/mediawiki/__special__/html/mootools_2/mootools_2_demo_2.html" width="750" height="400">
   нет поддержки iframe
  </iframe>


Вам может понравиться еще и такой вариант, когда содержимое некоторого блока медленно уезжает за его верхний край или наоборот выезжает из-за него.
  1. var shower = new Fx.Slide('block', {duration: 5000});
  2. $('button').addEvent ('click', function () {
  3.     shower.toggle();
  4. });
Здесь я создал два блока div: первый из них – block, будет попеременно прятаться и показываться (для этого используется функция toggle). Ее в свою очередь вызывает обработчик события “нажатие мыши” на втором блоке div (его id равен button). По-умолчанию содержимое блока будет прятаться за границами блока, уезжая вверх. Но если вы хотите это переопределить, так чтобы блок уезжал влево, то при создании объекта Fx.Slide следует указать атрибут mode равным значению 'horizontal':
  1. new Fx.Slide('block', {mode: 'horizontal' , duration: 5000}).slideIn();
  2. // функция slideIn прячет элемент, а функция slideOut его показывает.

  <iframe src="../contents/data/mediawiki/__special__/html/mootools_2/mootools_2_demo_3.html" width="750" height="400">
   нет поддержки iframe
  </iframe>


Есть альтернативный вариант синтаксиса, чтобы задать анимацию css-свойств с помощью функции effect, но практической разницы с ранее показанными приемами нет:
  1. $('block').effect('backgroundColor', {duration: 2000}).start('#00ff00','#ff00ff');
Гораздо интереснее разобраться с тем, как именно mootools будет вычислять промежуточные значения цветов фона между '#00ff00' и '#ff00ff'. Если вы думаете, что используется линейный подход с увеличением от меньшего к большему с шагом равным длина_интервала/время, то это не совсем так. В составе mootools есть специальный объект Fx.Transitions, представляющий собой коллекцию различных алгоритмов изменения анимируемого параметра. Я далее приведу пару формул таких преобразований, но сначала разберемся, как это работает внутри. Например, вы хотите плавно переместить некоторый элемент, т.е. изменить его атрибут left с начального значения 100 до 1000. Анимация начинается в некоторый момент 0, тут же запускается таймер, который каждые 1000 / fps миллисекунд вызывает функцию расчета нового значения атрибута left. В ней берется время, прошедшее от момента запуска анимации до текущего момента, и делится на duration, получается число от 0 до 1. Затем это значение поступает на вход функции transition, которая, например, берет и вычисляет значение по формуле (это, кстати, формула transition-преобразования по-умолчанию): -(Math.cos(Math.PI * p) - 1) / 2. Здесь p – то самое входное значение (число от 0 до 1). Полученное значение обозначим как delta. И оно поступает на вход следующей формуле: now = (to - from) * delta + from. Очевидно, что from – начальное значение анимируемого параметра, а to – конечное. Полученное значение now присваивается атрибуту left - и … фигурка движется. К некоторым из видов законов изменения добавляются вариации:

1. easeIn – формула transition остается без изменения.

2. easeOut – формула расчета delta меняется на следующую: 1 - transition(1 - p). Визуально этот эффект похож на зеркальное отражение easeIn.

3. easeInOut – формула будет такой: Если значение p еще не дошло до 0.5, т.е. мы не прошли еще половины всего времени анимации, то значение delta считается по формуле: transition(2 * p) / 2. А после того как p превысило половину расчетного времени, то формула меняется на (2 - transition(2 * (1 - p))) / 2.

Основные законы изменения анимируемого параметра:

1. Linear – самый простой вариант, когда параметр изменяется линейно с одинаковым шагом на каждом из этапов (y=x).

2. Quad – зависимость задается в виде квадрата (y=x*x).

3. Cubic – зависимость в виде куба (y = x*x*x).

4. Quart – здесь x возводится в четвертую степень.

5. Quint – здесь пятая степень.

6. Pow – а это универсальная формула основанная на возведении числа в произвольную степень. Ранее описанные Quad, Cubic, Quart, Quint построены на основе общего transition-закона Pow.

7. Circ – формула изменения следующая: 1 - Math.sin(Math.acos(p)).

8. Sine – закон движения похож на график синусоиды: 1 - Math.sin((1 - p) * Math.PI / 2).

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

10. Bounce – представьте как мяч отскакивает от пола, только его движения не только затухают а могут и расти небольшими шажками двигаясь от начального значения к конечному.

11. Elastic – вспомните из курса математики график любой асимптотической функции. Так вот этот тот самый закон.


  <iframe src="../contents/data/mediawiki/__special__/html/mootools_2/mootools_2_demo_5.html" width="750" height="400">
   нет поддержки iframe
  </iframe>


Конечно, вот так “на слух” воспринять и представить как может изменяться цвет или координаты элемента страницы довольно тяжело. Поэтому я предлагаю вам посетить следующие две страницы на официальном сайте библиотеки: http://docs.mootools.net/Effects/Fx.Transitions.js и http://demos.mootools.net/Fx.Transitions. На первой странице вы найдете картинки с нарисованными графиками изменения transition для каждой из формул, а вторая страница представляет собой интерактивный учебник. Там вы увидите форму, где в падающем списке можно выбрать интересующий вас закон движения, указать время в течении которого будет длиться анимация, а затем понаблюдать за перемещением анимируемого кубика. Естественно, что разработчики mootols не открыли Америки и подобных библиотек содержащих “пачку” законов анимации очень много. Есть такие библиотеки и для flash и для javascript. Может оказаться полезным и следующая ссылка http://coderepos.org/share/wiki. Там вы найдете еще одну интересную библиотечку tween-ов для javascript. Для flash 8/9 подобная функциональность сосредоточена в пакетах fl.motion, mx.transitions. Интерес представляет и следующий пример http://demos.mootools.net/Fx.Morph. В нем показано то, как можно выполнить преобразование одного элемента (его стили, размеры, шрифты) в другой, так чтобы за счет одновременного и плавного изменения этих характеристик создавался эффект морфинга. Хотя код примера достаточно велик, но в основе его лежат уже знакомые вам приемы, попробуйте разобраться в нем самостоятельно.

Теперь рассмотрим, как можно создать всплывающую подсказку для некоторых элементов. Стандартом html для каждого из элементов предусмотрен атрибут title -стандартная подсказка, но мы хотим большего: задать анимацию появления и исчезновения подсказки, управлять ее внешним видом: шрифты, фоновый цвет, “иконка” в углу - и все это нам позволит mootools. Внешний вид подсказок зависит от того тега, которому она назначена, так для тегов “a” будет добавлена строка заголовка содержащая ссылку, на которую указывает тег “a”.


  <iframe src="../contents/data/mediawiki/__special__/html/mootools_2/mootools_2_demo_4.html" width="750" height="400">
   нет поддержки iframe
  </iframe>

  1. <a href=”http://grav.ru” id="grav" title="Изучает гравитационное поле Земли">ГРАВИМЕТРИЯ</span>
  2. <span class="hclass" title="Рыхлая крупнообломочная осадочная горная порода">ГРАВИЙ</span>
И код создающий tooltips:
  1. new Tips($('grav'));
  2. new Tips(<span class=('.hclass'));
? ?????? ?????? ? ???? ?????? id ????????, ??? ???????? ????? ??????? ??????????? ?????????, ? ?? ?????? ????????? ???? ????????? ?? ???? ???????? ? ??????? ?????? ?hclass? (???????? ????????, ??? ? ????????? ??????? $ ? " />). При создании объекта Tips можно указать также параметры:

1. maxTitleChars – максимальная длина заголовка tooltip-а (количество символов).

2. showDelay – задает время, через которое после наведения мыши на элемент подсказка должна появиться (задается в миллисекундах).

3. hideDelay – время, через которое подсказка исчезнет.

4. offsets – можно указать смещение, на котором относительно позиции мыши будет показан tooltip (задавать значение нужно так: {x: 100, y: 100}).

5. fixed – булева переменная, признак того, будет ли при перемещении курсора мыши подсказка следовать вслед за ним или останется на месте.

6. className – префикс css-классов используемых для оформления tooltip-а. Всего есть три класса, которые используются для этого: tool-tip, tool-title, tool-text. Служат они соответственно для оформления всего блока tooltip-а, его заголовка и зоны размещения основного текста. Имя этих классов должно начинаться с префикса заданного className-ом.

Конечно, это не все возможности mootools. Еще интересно рассказать о поддержке DnD и нескольких элементах управления (accordion, scroller). Но, наверное, это будет гораздо позже и в другой серии статей, посвященных библиотекам, ориентированным на, именно, проектирование сложного пользовательского интерфейса.