Статьи

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

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

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

Чтобы действительно объяснить, что было не так с кодом на нашем последнем шаге и почему важна инкапсуляция, нам нужно сначала объяснить область видимости переменной.

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

01
02
03
04
05
06
07
08
09
10
function do_this(){
  var very_helpful_variable = 20;
  …
  // This shows ’20’, just like you expect
  alert(very_helpful_variable);
}
 
function do_that(){
  alert(very_helpful_variable);
}

Ваша переменная прекрасно работает внутри функции, которую она объявила, но вне этой функции она как будто никогда не существовала! Это потому, что do_that() не входит в область действия переменной very_helpful_variable .

Переменные доступны только внутри блока кода, где они объявлены, это их область действия. Как только этот блок кода завершится, его переменные будут удалены.

Посмотрите на эти примеры:

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
30
31
32
33
34
35
var w = 1;
 
function a(){
  var x = 2;
   
  function b(){
    var y = 3;
     
    alert(w);
    alert(x);
    alert(y);
    alert(z);
  }
   
  alert(w);
  alert(x);
  alert(y);
  alert(z);
}
 
function c(){
  var z = 4;
   
  alert(w);
  alert(x);
  alert(y);
  alert(z);
   
  b();
}
 
alert(w);
alert(x);
alert(y);
alert(z);

Сначала у нас есть переменная w , которая объявлена ​​вне каких-либо функций. Она называется глобальной переменной , и она будет работать где угодно, поскольку ее область действия — весь документ.

Далее следует переменная x , так как она объявлена ​​внутри функции a() , она будет работать только внутри этой функции. Это также включает в себя работу внутри функции b() , так как b() находится внутри a() .

Однако переменная, определенная внутри b() (например, y ), не будет работать во внешней функции, поскольку она находится за пределами ее области видимости.

Вы также можете заметить, что мы безуспешно пытались вызвать функцию b() из функции c() ; Имена функций следуют тем же правилам, что и другие переменные.

Еще одна изюминка с JavaScript: если мы просто начнем использовать имя переменной внутри функции, не объявляя ее ключевым словом var , то браузер предположит, что эта переменная должна быть глобальной. Итак, если вы не уверены, что всегда объявляете свои переменные с помощью ключевого слова var , вы получите глобальные переменные и не сможете их реализовать!

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
var timer;
   
function run_right(stage, left){
  …
  timer = setTimeout(function(){run_right(2, left);}, 200);
  …
}
 
function stop_running(){
  document.getElementById(‘j’).style.backgroundPosition = «0px 0px»;
  // If ‘timer’ wasn’t set as global, we couldn’t stop it here
  clearTimeout(timer);
}

Чтобы очистить таймер, нам нужно было, чтобы stop_running() находился в области видимости переменной timer . Итак, мы сделали timer глобальной переменной, которая может использоваться везде, что может быть не так с этим?

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

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

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

Теперь вы можете спросить себя: «Глобальные переменные очень удобны! Что если я просто внимательно посмотрю свой код и убедлюсь, что у меня нет конфликтов?» Это может сработать в идеальном мире, но на самом деле у вас часто будет несколько человек, работающих над разными частями одной и той же страницы, или вам придется возвращаться и обновлять разные части вашего кода спустя годы, или даже иметь код от сторонних разработчиков на ваша страница, которая будет вне вашего контроля (например, платная реклама).

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

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

JavaScript имеет тип переменной, называемой объектом . Объекты — это определяемые пользователем наборы данных, которые содержат информацию и функции (называемые соответственно свойствами и методами ). Мы напишем функцию, которая создает специальный объект, в котором «запекаются» все функции, которые нам нужны, и это даже позволит нам иметь более одного робота без необходимости дублировать наш код!

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
var RobotMaker = function(robot, run_speed, jump_height){
 
  // We will put all of our functions and variables in this area.
  // This is inside our ‘impenetrable’ wall, so nothing in this
  // area will conflict with other code.
   
  return {
    // Inside here, we place all of our ‘doors’ …
    // these will be the only way anything can get
    // in or out of this code.
    // And, since this is still within the same ‘scope’
    // as RobotMaker, we can use any variables mentioned above!
  }
}

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

Вы можете заметить, что если мы нажмем две кнопки запуска (или кнопку запуска и перехода), не нажимая промежуточную кнопку Стоп , J продолжит выполнять оба действия. Вторая проблема заключается в том, что независимо от того, в каком направлении J смотрит, когда мы нажимаем кнопку « Прыжок» или « Стоп» , он каждый раз смотрит вправо. Наконец, если вы снова нажмете кнопку « Прыжок» , когда J падает с первого прыжка, он будет продолжать падать по странице в бесконечном цикле.

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

Когда мы нажимаем Run Right:

  1. Если J прыгает, ничего не делайте и продолжайте прыжок
  2. Если J бежит налево, остановите его, бегите налево
  3. Беги вправо и оживи до нужного кадра
  4. Если J достигнет конца сцены, остановитесь и встаньте направо

Когда мы нажимаем Run Left:

  1. Если J прыгает, ничего не делайте и продолжайте прыжок
  2. Если J бежит направо, остановите его
  3. Беги налево и оживи до нужного кадра
  4. Если J достигнет конца этапа, остановитесь и стойте влево

Когда мы нажимаем «Stop Running»:

  1. Если J прыгает, ничего не делайте и продолжайте прыжок (мы не хотим останавливаться в воздухе!)
  2. Если бегите направо или налево, остановите бег
  3. Если смотреть направо, стойте лицом вправо. Если смотреть влево, стоять лицом влево

Когда мы нажимаем Перейти:

  1. Если J прыгает, ничего не делайте и продолжайте прыжок (мы не хотим снова прыгать в воздухе!)
  2. Если J бежит направо или налево, остановите бег
  3. Начните прыжок. Если J направлен вправо, прыгните направо. Если смотреть влево, прыгать влево
  4. Земля обращена в том же направлении, что и прыжок

Прежде всего, мы собираемся добавить еще несколько переменных сейчас. Поскольку для бега и прыжков таймер должен вести себя по-разному, у нас будет два отдельных таймера. Мы также хотим ввести boolean переменную (true / false), чтобы отслеживать, должны ли мы смотреть влево или вправо, и мы создадим переменную stage только для того, чтобы избавить нас от необходимости вводить полное имя элемента.

1
2
3
4
// Inside the RobotMaker function …
var stage = document.getElementById(‘stage’);
var run_timer, jump_timer;
var face_right = true;

Затем мы собираемся добавить обратно в наши функции для бега вправо, бега влево и прыжков. Они будут в основном одинаковыми, с некоторыми отличиями. Прежде всего, все ссылки на элемент, который мы анимируем, могут быть заменены на переменную robot (которая будет передана как один из аргументов в функции RobotMaker ). Во-вторых, мы внесли некоторые незначительные изменения в скорость бега и высоту прыжка в функциях, чтобы мы могли изменять их, передавая различные значения. В-третьих, мы используем переменную face_right чтобы отслеживать направление J (и в функции прыжка, используя face_right чтобы решить, какой спрайт прыжка показать). Наконец, мы используем отдельные таймеры для бега и прыжков.

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// Inside the RobotMaker function …
function run_r(phase, left){
  face_right = true;
  if ((left + (15 * run_speed)) < (stage.offsetWidth — robot.offsetWidth)){
     
    left = left + (15 * run_speed);
    robot.style.left = left+»px»;
    switch (phase){
      case 1:
        robot.style.backgroundPosition = «-40px 0px»;
        run_timer = setTimeout(function(){run_r(2, left);}, 200);
        break;
      case 2:
        robot.style.backgroundPosition = «-80px 0px»;
        run_timer = setTimeout(function(){run_r(3, left);}, 200);
        break;
      case 3:
        robot.style.backgroundPosition = «-120px 0px»;
        run_timer = setTimeout(function(){run_r(4, left);}, 200);
        break;
      case 4:
        robot.style.backgroundPosition = «-80px 0px»;
        run_timer = setTimeout(function(){run_r(1, left);}, 200);
        break;
    }
  } else {
    robot.style.backgroundPosition = «0px 0px»;
  }
}
   
function run_l(phase, left){
  face_right = false;
  if (0 < robot.offsetLeft — (15 * run_speed)){
     
    left = left — (15 * run_speed);
    robot.style.left = left+»px»;
    switch (phase){
      case 1:
        robot.style.backgroundPosition = «-40px -50px»;
        run_timer = setTimeout(function(){run_l(2, left);}, 200);
        break;
      case 2:
        robot.style.backgroundPosition = «-80px -50px»;
        run_timer = setTimeout(function(){run_l(3, left);}, 200);
        break;
      case 3:
        robot.style.backgroundPosition = «-120px -50px»;
        run_timer = setTimeout(function(){run_l(4, left);}, 200);
        break;
      case 4:
        robot.style.backgroundPosition = «-80px -50px»;
        run_timer = setTimeout(function(){run_l(1, left);}, 200);
        break;
    }
  } else {
    robot.style.backgroundPosition = «0px -50px»;
  }
}
   
function jmp(up, top){
  if (face_right){
    robot.style.backgroundPosition = «-160px 0px»;
  } else {
    robot.style.backgroundPosition = «-160px -50px»;
  }
 
  if (up && (robot.offsetTop > (20 * (1 / jump_height)))){
    top = top — (top * .1);
    robot.style.top = top+»px»;
    jump_timer = setTimeout(function(){jmp(up, top);}, 60);
  } else if (up) {
    up = false;
    jump_timer = setTimeout(function(){jmp(up, top);}, 60);
  } else if (!up && (robot.offsetTop < 115)){
    top = top + (top * .1);
    robot.style.top = top+»px»;
    jump_timer = setTimeout(function(){jmp(up, top);}, 60);
  } else {
    robot.style.top = «120px»;
    if (face_right){
      robot.style.backgroundPosition = «0px 0px»;
    } else {
      robot.style.backgroundPosition = «0px -50px»;
    }
 
    jump_timer = false;
  }
   
}

Все эти переменные и функции находятся внутри нашей «стены», поэтому нам нужно сделать «двери», чтобы иметь доступ только к тому, что нам нужно. Эти четыре «двери» будут объектными методами для тех же четырех функций, которые у нас были ранее, и будут ссылаться на защищенные функции выше. Кроме того, мы jump_timer исправление ошибок, проверив в каждой функции, если jump_timer , и затем убедившись, что run_timer . Помните, что эти два таймера находятся в области действия внутри функции RobotMaker() , поэтому мы можем использовать их здесь. Однако, поскольку они не являются глобальными переменными, мы не столкнемся с ними в других местах.

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
30
31
32
33
34
35
// Inside the RobotMaker function …
return {
  run_right : function(){
    if (!jump_timer || jump_timer == undefined){
      clearTimeout(run_timer);
      run_r(1, robot.offsetLeft);
    }
  },
   
  run_left : function(){
    if (!jump_timer || jump_timer == undefined){
      clearTimeout(run_timer);
      run_l(1, robot.offsetLeft);
    }
  },
   
  stop_running : function(){
    if (!jump_timer || jump_timer == undefined){
      clearTimeout(run_timer);
      if (face_right){
        robot.style.backgroundPosition = «0px 0px»;
      } else {
        robot.style.backgroundPosition = «0px -50px»;
      }
    }
  },
   
  jump : function(){
    if (!jump_timer || jump_timer == undefined){
      clearTimeout(run_timer);
      jmp(true, robot.offsetTop);
    }
  }
   
}

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

1
2
var j = RobotMaker(document.getElementById(‘j’), 1, 1);
var j2 = RobotMaker(document.getElementById(‘j2’), .8, 5);

Теперь у нас нет никакой опасности того, что RobotMaker() просачиваться и мешать нашему коду, и мы все еще можем получить RobotMaker() функции через «двери», которые мы установили следующим образом:

1
<input type=»button» value=»Run Left» onclick=»j.run_left();»

Итак, теперь вы можете увидеть готовый продукт на Hyrgo Pen .

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

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

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