Статьи

JavaScript анимация, которая работает (часть 2 из 4)

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

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

Javascript-спрайты-J

Познакомьтесь с J, талисманом моей компании Joust Multimedia.

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


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

Для нашей работающей функции мы хотим:

  1. Сдвиньте div немного вправо
  2. Перейти к следующему кадру анимации
  3. Пауза на долю секунды (чтобы сохранить иллюзию «постоянства зрения»)
  4. Зациклить функцию снова

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

1
2
3
4
5
6
7
function run_right(){
  // Move slightly to the right …
  // Change to the next frame of animation …
 
  // this will call ‘run_right’ again after 200 milliseconds
  setTimeout(function(){run_right();}, 200);
}

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
function run_right(slide){
  // Move slightly to the right …
  switch (slide){ // this switch statement checks for different possibilities for ‘slide’
    case 1: // if ‘slide’ equals ‘1’ …
      document.getElementById(‘j’).style.backgroundPosition = «-40px 0px»;
      setTimeout(function(){run_right(2);}, 200);
      break;
    case 2: // if ‘slide’ equals ‘2’ …
      document.getElementById(‘j’).style.backgroundPosition = «-80px 0px»;
      setTimeout(function(){run_right(3);}, 200);
      break;
    case 3: // if ‘slide’ equals ‘3’ …
      document.getElementById(‘j’).style.backgroundPosition = «-120px 0px»;
      setTimeout(function(){run_right(4);}, 200);
      break;
    case 4: // if ‘slide’ equals ‘4’ …
      document.getElementById(‘j’).style.backgroundPosition = «-80px 0px»;
      setTimeout(function(){run_right(1);}, 200);
      break;
  }
}

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

1
<input type=»button» value=»Run Right» onclick=»run_right(1);»

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function run_right(slide, left){
   
  left = left + 15;
  document.getElementById(‘j’).style.left = left+»px»;
   
  switch (slide){ // this switch statement checks for different possibilities for ‘slide’
    case 1: // if ‘slide’ equals ‘1’ …
      document.getElementById(‘j’).style.backgroundPosition = «-40px 0px»;
      setTimeout(function(){run_right(2, left);}, 200);
      break;
    case 2: // if ‘slide’ equals ‘2’ …
      document.getElementById(‘j’).style.backgroundPosition = «-80px 0px»;
      setTimeout(function(){run_right(3, left);}, 200);
      break;
    case 3: // if ‘slide’ equals ‘3’ …
      document.getElementById(‘j’).style.backgroundPosition = «-120px 0px»;
      setTimeout(function(){run_right(4, left);}, 200);
      break;
    case 4: // if ‘slide’ equals ‘4’ …
      document.getElementById(‘j’).style.backgroundPosition = «-80px 0px»;
      setTimeout(function(){run_right(1, left);}, 200);
      break;
  }
}

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

1
<input type=»button» value=»Run Right» onclick=»run_right(1, document.getElementById(‘j’).offsetLeft);»

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

1
2
3
4
5
6
7
8
function run_right(slide, left){
  // If we can add 15 pixels to the left and have J’s right edge not be at the stage’s right edge …
  if ((left + 15) < (document.getElementById(‘stage’).offsetWidth — document.getElementById(‘j’).offsetWidth)){
    // We have room!
  } else { // if we are on the right edge, we need to stop calling the function and return to standing
    document.getElementById(‘j’).style.backgroundPosition = «0px 0px»;
  }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var timer;
 
function run_right(slide, left){
  if ((left + 15) < (document.getElementById(‘stage’).offsetWidth — document.getElementById(‘j’).offsetWidth)){
    left = left + 15;
    document.getElementById(‘j’).style.left = left+»px»;
   
    switch (slide){ // this switch statement checks for different possibilities for ‘slide’
      case 1: // if ‘slide’ equals ‘1’ …
        document.getElementById(‘j’).style.backgroundPosition = «-40px 0px»;
        setTimeout(function(){run_right(2, left);}, 200);
        break;
      case 2: // if ‘slide’ equals ‘2’ …
        document.getElementById(‘j’).style.backgroundPosition = «-80px 0px»;
        setTimeout(function(){run_right(3, left);}, 200);
        break;
      case 3: // if ‘slide’ equals ‘3’ …
        document.getElementById(‘j’).style.backgroundPosition = «-120px 0px»;
        setTimeout(function(){run_right(4, left);}, 200);
        break;
      case 4: // if ‘slide’ equals ‘4’ …
        document.getElementById(‘j’).style.backgroundPosition = «-80px 0px»;
        setTimeout(function(){run_right(1, left);}, 200);
        break;
    }
  } else {
    document.getElementById(‘j’).style.backgroundPosition = «0px 0px»;
  }
}

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

1
2
3
4
function stop_running(){
  document.getElementById(‘j’).style.backgroundPosition = «0px 0px»;
  clearTimeout(timer);
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function run_left(stage, left){
  if ((left — 15) > 0){
    left = left — 15;
    document.getElementById(‘j’).style.left = left+»px»;
    switch (stage){
      case 1:
        document.getElementById(‘j’).style.backgroundPosition = «-40px -50px»;
        timer = setTimeout(function(){run_left(2, left);}, 200);
        break;
      case 2:
        document.getElementById(‘j’).style.backgroundPosition = «-80px -50px»;
        timer = setTimeout(function(){run_left(3, left);}, 200);
        break;
      case 3:
        document.getElementById(‘j’).style.backgroundPosition = «-120px -50px»;
        timer = setTimeout(function(){run_left(4, left);}, 200);
        break;
      case 4:
        document.getElementById(‘j’).style.backgroundPosition = «-80px -50px»;
        timer = setTimeout(function(){run_left(1, left);}, 200);
        break;
    }
  } else {
    document.getElementById(‘j’).style.backgroundPosition = «0px -50px»;
  }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function jump(up, top){
  /*
   * We change J to his jumping sprite …
   */
  document.getElementById(‘j’).style.backgroundPosition = «-160px 0px»;
  /*
   * Here, we need to decide whether he should be traveling up or down…
   */
  if (up && (document.getElementById(‘j’).offsetTop > 20)){
    // if he is currently moving up, and he is more than 20 pixels from the top of the stage …
    top = top — (top * .1);
    document.getElementById(‘j’).style.top = top+»px»;
    timer = setTimeout(function(){jump(up, top);}, 60);
  } else if (up) {
    // if he is currently moving up, but he is almost at the top of the stage and needs to come back down…
    up = false;
    timer = setTimeout(function(){jump(up, top);}, 60);
  } else if (!up && (document.getElementById(‘j’).offsetTop < 115)){
    // if he is moving down, but is more than 5px from the ground, he will continue to fall…
    top = top + (top * .1);
    document.getElementById(‘j’).style.top = top+»px»;
    timer = setTimeout(function(){jump(up, top);}, 60);
  } else {
    // If he is moving down, and he is within 5px of the ground…
    document.getElementById(‘j’).style.top = «120px»;
    document.getElementById(‘j’).style.backgroundPosition = «0px 0px»;
    // We do not call the loop anymore since he is standing still at this point
  }
}

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


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