Простая анимация на javascript

Простая анимация на javascript

Всем привет! Данной статьёй я открываю цикл публикаций на тему создания анимаций на чистом javascript-е.

Зачем вообще делать анимации на чистом js

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

  1. Делается небольшой сайт с минимумом эффектов. Сторонние библиотеки не подключаются. И вдруг возникает необходимость сделать плавное растворение элемента при клике. Стоит ли из-за одного такого пустяка подключать jQuery!? Многие подключат и по факту сделают правильно – в современном мире 98Кб никого не интересуют. Однако, при работе в команде бывают такие упёртые тимлиды, которые не позволят из-за мелкой анимации внедрять в проект стороннюю библиотеку. Вот и придётся вам разбираться в анимации самим.
  2. Используется готовая библиотека, и в ней не находится нужного эффекта. Например, вам нужно, чтобы элемент, перед тем как исчезнуть, три раза повернулся по часовой стрелке, подпрыгнул и только потом растворился. В таком случае библиотеку придётся расширять, и вам опять же понадобится понимание основ организации анимационных эффектов.

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

У нас будет два файла: index.html и script.js. HTML-документ очень прост:

<!DOCTYPE html> 
<html>
<head>
  <meta charset="utf-8">
  <title>JS</title>
  <style>
    #item{
      width: 300px;
      height: 200px;
      background: #f9b;
      margin: 20px;
    }
  </style>
  <script src="script.js"></script>
</head>
<body>
  <div id="item"></div>
</body>
</html>

Мы сделали div 300х200 пикселей, залили его цветом, дали небольшой внешний отступ и присвоили id=item. Ничего больше пока что нам и не нужно.

Организация анимации в javascript.

Теперь организуем работу в js. Для начала повесим на наш div какое-нибудь событие:

// После загрузки документа
window.onload = function(){
  // При клике на элемент с id=item
  document.getElementById('item').onclick = function(){
    // для проверки выводим алерт
    alert(1);
  }
}

Немного комментариев по данной конструкции. Весь javascript-код мы традиционно помещаем внутрь функции, установленной на событие window.onload. Таким образом, мы дожидаемся окончательной загрузки документа и формирования DOM – объектной модели документа.

После этого с помощью document.getElementById мы получаем div с id=item и устанавливаем событие onclick, при наступлении которого произойдёт показ проверочного алерта. На самом деле, вместо алерта мы чуть позже поместим сюда вызов функции fade, которую сейчас начнём рассматривать.

Итак, что же нам нужно для анимации? Попробуем сразу продумать такой интерфейс функции, который в дальнейшем позволит сделать её по-настоящему удобной! Получится что-то такое:

// Функция растворения элемента:
//   elem - объект DOM
//   t - время анимации
//   f - количество кадров в секунду
function fade(elem, t, f){
  // код
}

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

Здесь для начала нужно разобраться с тем, как вообще происходит анимация. Анимация – это просто ступенчатое изменение визуально заметной величины с определённым интервалом. Такой величиной для нас является свойство opacity – прозрачность элемента. А интервал мы создаём с помощью стандартной функции javascript – setInterval.

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

setInterval(function(){
  alert(1);
}, 20);

=то будем видеть алерт каждые 20 миллисекунд. А если напишем

setInterval(function(){
  alert(1);
}, 50);

то каждые 50мс.

С кадрами в секунду это соотносится очень легко. В одной секунде тысяча миллисекунд, значит, если мы хотим показывать человеку 50 кадров в секунду, то 1000 / 50 = 20мс – каждые 20 миллисекунд мы должны повторять действия по интервалу.

Исходя из всего вышесказанного, мы уже можем изобразить базовый хребет нашей функции:

function fade(elem, t, f){
  // кадров в секунду (по умолчанию 50)
  var fps = f || 50; 
  // время работы анимации (по умолчанию 500мс)
  var time = t || 500; 
  
  var timer = setInterval(function(){
    // изменение opacity
    
    // если анимация окончена
    if(/* проверка */){
      // убираем интервал выполнения
      clearInterval(timer);
      // и убираем элемент из потока документа
      elem.style.display = 'none';
    }
  }, (1000 / fps));
}

Конструкции var fps = f || 50 и var time = t || 500 объявляют параметры по умолчанию. То есть, если при вызове мы не передадим в функцию второй и третий элементы, то их значения будут равны соответственно 50 и 500.

Сама конструкция организации setInterval тоже логична. Мы устанавливаем интервал с периодичностью 1000 / fps. После завершения анимации этот интервал нам нужно удалить.

Обратите внимание на строчку elem.style.display = 'none'! Это указание нужно, чтобы после обнуления непрозрачности элемента, он освободил место. Если этого не сделать, элемента не будет видно, но его место будет оставаться пустым – нижние элементы не переместятся туда.

Уже почти всё! Нам осталось добавить только изменение видимого свойства и проверку на завершение анимации.

Как же правильно организовать изменение свойства opacity. Математика здесь элементарная. У нас есть время и количество кадров в секунду. Например, время time = 2000 и количество кадров в секунду fps = 50. Это означает, что всего мы покажем пользователю n = time / fps = 2000 / 50 = 400 кадров. Получается, что значение свойства opacity мы также должны изменить 400 раз. Вспомним, opacity изменяется от x0 = 0 до x1 = 1. Общая формула dx = (x1 – x0) / (time / fps). А если по-простому, то мы 1 делим на 400 и получаем 0.0025.

Занесём это всё в код:

function fade(elem, t, f){
  // кадров в секунду (по умолчанию 50)
  var fps = f || 50; 
  // время работы анимации (по умолчанию 500мс)
  var time = t || 500; 
  // сколько всего покажем кадров
  var steps = time / fps;   
  // текущее значение opacity - изначально 0
  var op = 1;
  // изменение прозрачности за 1 кадр
  var d0 = op / steps;
  
  // устанавливаем интервал (1000 / fps) 
  // например, 50fps -> 1000 / 50 = 20мс  
  var timer = setInterval(function(){
    // уменьшаем текущее значение opacity
    op -= d0;
    // устанавливаем opacity элементу DOM
    elem.style.opacity = op;
    // уменьшаем количество оставшихся шагов анимации
    steps--;
    
    // если анимация окончена
    if(steps == 0){
      // убираем интервал выполнения
      clearInterval(timer);
      // и убираем элемент из потока документа
      elem.style.display = 'none';
    }
  }, (1000 / fps));
}

Основную управляющую роль здесь играет переменная steps, в которой мы храним информацию о том, сколько ещё кадров осталось показать. Когда steps достигнет нуля, анимация завершена.

Всё. Осталось только правильно передать параметры в нашу функцию:

document.getElementById('item').onclick = function(){
  // растворяем его за 2с c частотой 40 кадров в секунду.
  fade(this, 2000, 40);
}

Единственная тонкость – this – это указание на текущий элемент, по которому был произведён клик.

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

Не пропустите!