Сегодня мы будем делать код для "Анимастера". Это скрипт, позволяющий создавать и управлять анимациями.
Уже написано несколько функций, делающих элементарные анимации.
При клике на кнопку play возле названия анимации запустят ее.
-1. Создай функцию с названием animaster
-2. Сейчас функции-анимации (move
, fadeIn
, scale
) лежат в глобальной области видимости и имеют очень простые названия — их легко случайно переопределить. Поэтому спрячь их определения внутрь функции animaster
. Функция animaster
должна возвращать объект с методами для запуска разных анимаций. Сделай так, чтобы кнопки на страничке все еще показывали анимацию.
Использовать анимации теперь можно будет вот так:
animaster().scale(element, 500, .8)
-3. По аналогии с функцией fadeIn
создай функцию fadeOut
, скрывающую элемент.
-4. Мы хотим, немного облегчить жизнь пользователям анимастера, поэтому напишем для них несколько сложных анимаций. Сложные анимации состоят из нескольких простых, запущенных в правильном порядке и в нужное время. Добавь в интерфейс анимастера три новые анимации. Не забудь сделать так, чтобы с html-странички можно было их запустить. Вот такие анимации должен реализовывать анимастер:
- `moveAndHide` — блок должен одновременно сдвигаться на 100 пикселей вправо и на 20 вниз, а потом исчезать. Метод на вход должен принимать продолжительность анимации. При этом 2/5 времени блок двигается, 3/5 — исчезает.
- `showAndHide` — блок должен появиться, подождать и исчезнуть. Каждый шаг анимации длится 1/3 от времени, переданного аргументом в функцию.
- `heartBeating` — имитация сердцебиения. Сначала элемент должен увеличиться в 1,4 раза, потом обратно к 1. Каждый шаг анимации занимает 0,5 секунды. Анимация должна повторяться бесконечно.
-5. Сделай возможность отключать анимацию сердцебиения:
- сделай, чтобы метод `heartBeating` возвращал объект с методом `stop`
- при вызове метода `stop` анимация должна прекращаться (скорее всего, нужно останавливать таймер, которым ты анимацию запускаешь)
- добавь в разметку кнопку stop по аналогии с кнопкой play
- в коде добавь обработчик клика по аналогии с тем, как это сделано для кнопки play. Обработчик должен вызывать метод `stop` анимации
-6. Сделай функции отмены для каждой элементарной операции. Создай служебные (не доступные снаружи анимастера) функции resetFadeIn
, resetFadeOut
и resetMoveAndScale
. Все эти функции принимают на вход элемент, на котором надо сбросить состояние.
resetFadeIn
и resetFadeOut
должны менять обратно css-классы в classList — каждая из них должны убирать все дописанные стили, устанавливая их в null
:
element.style.заданныйАнимациейСтиль = null;
-7. Сделай кнопку reset для анимации moveAndHide
по аналогии с кнопкой stop из одного из предыдущих пунктов. При нажатии на кнопку reset последующие шаги анимации должны отменяться аналогично stop из heartBeating
, а элемент должен мгновенно вернуться в исходное состояние с помощью функций из предыдущего пункта.
- Сделай так, чтобы анимацию
move
можно было вызвать не только так:
animaster().move(element, 500, {x: 20, y: 20});
Но и так:
animaster().addMove(500, {x: 20, y:20}).play(element);
Для этого:
- добавь методы
addMove
иplay
в объект, возвращаемый функциейanimaster
- в объекте, возвращаемом из анимастера, должно появиться поле
_steps
— массив шагов анимации (в начале пустой). Поле_steps
— приватное. Приватные поля по договоренности между разработчиками начинаются с нижнего подчеркивания. - метод
addMove
должен возвращатьthis
(то есть тот объект анимастера, который вернулся из вызова animaster) - метод
addMove
должен добавлять в полеthis._steps
новый шаг анимации.
Структуру, в которой будешь хранить каждый шаг, придумай сам. Каждый шаг должен содержать следующую информацию:
- название элементарной операции (
move
,fadeIn
) - продолжительность шага в миллисекундах
- дополнительные параметры (смещение для
move
, соотношение дляscale
) - метод
play
должен принимать элемент, который будем анимировать, в качестве аргумента - метод
play
должен выполнять по очереди все шаги анимации, которые лежат в массивеthis._steps
, применяя их к переданному ему элементу.
-
Сделай так, чтобы метод
move
работал на написанных на предыдущем шаге методах. -
Сделай по аналогии с методом
addMove
методыaddScale
,addFadeIn
иaddFadeOut
. Перепиши соответствующие анимации, чтобы они переиспользовали эти методы. -
Теперь у тебя должно быть возможным такое использование:
const customAnimation = animaster()
.addMove(200, {x: 40, y: 40})
.addScale(800, 1.3)
.addMove(200, {x: 80, y: 0})
.addScale(800, 1)
.addMove(200, {x: 40, y: -40})
.addScale(800, 0.7)
.addMove(200, {x: 0, y: 0})
.addScale(800, 1);
customAnimation.play(element);
В этот момент анимация запускается, элемент перемещается по четырем точкам и меняет размеры. В конце элемент возвращается в исходную точку. Если customAnimation.play(element)
запустить несколько раз с разными элементами, то анимации должны работать.
-
Перепиши сложные анимации
moveAndHide
,showAndHide
иheartBeating
на использование новых методовadd...
иplay
. Чтобы сделатьshowAndHide
, добавь методaddDelay
, добавляющий шаг, в котором ничего не происходит в течение заданного времени. Чтобы сделатьheartBeating
, добавь вplay
в аргументы флагcycled
, означающий, что шаги анимации надо повторять бесконечно. -
Метод
play
должен возвращать объект с методомstop
(применяется дляheartBeating
) иreset
. Методreset
должен возвращать элемент в исходное состояние (то, которое было до анимации):
- если элемент был скрыт, то скрыть его
- если был показан, то показать
- если элемент был сдвинут или его размер изменен, то вернуть в исходное состояние
Для этого воспользуйся функциями отмены элементарных операций.
- Добавь метод
buildHandler
, результат которого можно сразу передать вaddEventListener
:
const worryAnimationHandler = animaster()
.addMove(200, {x: 80, y: 0})
.addMove(200, {x: 0, y: 0})
.addMove(200, {x: 80, y: 0})
.addMove(200, {x: 0, y: 0})
.buildHandler();
document
.getElementById('worryAnimationBlock')
.addEventListener('click', worryAnimationHandler);
Особенность addEventListener
в том, что если ему во второй параметр передать не стрелочную функцию, то в качестве контекста вызова this
он передаст ей DOM-элемент с которым происходит событие.
-
* Реализуй какую-нибудь еще анимацию, которой пока нет. В качестве идей, можешь использовать этот сайт: http://leaverou.github.io/animatable/ Если на этой странице нажать кнопку "Animate All", становится весело =)
-
* Сейчас есть проблема, что если написать вот так:
const a = animaster().addMove(111, {x: 10, y: -10})
const b = a.addFadeOut(400);
То при запуске a.play(el)
, выполнится и перемещение и исчезновение, хотя это и не ожидается.
Сделай так, чтобы в такой записи a
и b
представляли собой разные наборы анимаций и можно было на любом шаге в цепи сохранить анимацию и воспользоваться ею.