Планирование вызова функции через setTimeout и setInterval

setTimeout



Данный метод представляет собой конструкцию:

Код

setTimeout(func, delay);


в скобках находятся два аргумента, первый из них(обозначен сейчас как func) - это функция, которую нам нужно запустить , второй аргумент(обозначен как delay) - время задержки в миллисекундах до того момента, когда функция начнёт свою работу (например, если нужно запустить функцию через 3 секунды, то вместо delay мы должны написать 3000)

Напишем простую функцию, результатом работы которой должно быть сообщение "Привет!" в модальном окне.
Функцию назовём, к примеру, helloy:

Код

function helloy() {
  alert('Привет!');
};


всё просто: при вызове этой функции в браузере откроется модальное окно с текстом "Привет!"

Давайте для вызова нашей функции helloy используем метод setTimeout, мы тем самым спланируем вызов нашей функции через какой-то определённый, нужный нам интервал времени.
Допустим, мы хотим, чтобы наша функция сработала через 3 секунды, тогда код будет выглядеть следующим образом:

Код

setTimeout(helloy, 3000);

function helloy() {
  alert('Привет!');
};


Протестировать

если мы нашу конструкцию

Код
setTimeout(helloy, 3000);


сохраним в какую-то переменную, например, с названием timer:

Код
let timer = setTimeout(helloy, 3000);


то тем самым мы в этой переменной сохраняем числовой идентификатор нашего таймера. Для чего это нужно?

Допустим, для того, чтобы в нужный момент мы могли остановить наш таймер, делается это так, с помощью команды clearTimeout(), где в скобках мы как раз и указываем нашу переменную timer:

Код

let timer = setTimeout(helloy, 3000);
clearTimeout(timer);

function helloy() {
  alert('Привет!');
};


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

setInterval



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

Другими словами, если мы в нашем предыдущем коде заменим setTimeout на setInterval (я при этом ещё заменил всплывающее модальное окно на вывод текстового сообщения в консоль):

Код

let timer = setInterval(helloy, 3000);

function helloy() {
  console.log('Привет!');
};


откроем теперь в браузере нашу страницу и консоль разработчика, то через каждые три секунды в консоли будет добавляться до бесконечности текстовое сообщение "Привет".

Остановить это безобразие можно таким же образом, как мы делали выше: добавив команду clearTimeout().

Рекурсивный вызов setTimeout



Вернёмся вновь к нашему коду с командой setInterval:

Код

let timer = setInterval(helloy, 3000);

function helloy() {
  console.log('Привет!');
};


в данном случае у нас установлена задержка в 3 секунды, после чего функция срабатывает. И сам код функции в этом примере минимальный.
Но что если код функции будет достаточно объёмный и время её работы будет больше, чем заданный интервал задержки?

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

В связи с этой проблемой и используется рекурсивный setTimeout. Суть его в том, что функция вызывает сама себя.
Рассмотрим такой пример:

Код

let timer = setTimeout(function log() {
  console.log('Привет!');
  setTimeout(log, 3000);
});


здесь внутри функции размещен еще один setTimeout, за счёт которого, каким бы долгим не был выполняемый функцией код по времени, заданный интервал в 3 секунды будет выдерживаться всегда.
А за счёт того, что функция каждый раз вызывает сама себя, происходит тоже самое: беспрерывный вывод текстовых сообщений в консоль, как и в случае с методом setInterval.

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



Создайте у себя в редакторе кода новый html-файл с таким содержимым:

Код

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <button class="btn">Animate</button>
  <div class="wrapper">
  <div class="box"></div>
  </div>
</body>
</html>


Содержимое файла со стилями style.css следующее (он должен у вас быть в одной папке с html-файлом):

Код

.box {
  position: absolute;
  width: 100px;
  height: 100px;
  background-color: blue;
}
.wrapper {
  position: relative;
  width: 400px;
  height: 400px;
  border: 3px solid red;
}
.btn-block {
  padding: 10px;
  background-color: grey;
  margin-top: 10px;
}
button {
  margin-bottom: 10px;
  margin-top: 10px;
  width: 100px;
  height: 40px;
  background-color: yellow;
  border: none;
}


В итоге, если вы сейчас откроете в браузере ваш html-файл, то увидите следующее:



Нам нужно с помощью JavaScript построить простейшую анимацию: синий квадрат должен плавно переместиться по диагонали в нижний правый угол.

Но предварительно создайте файл main.js в той же папке, где у вас сейчас style.css и index.html. В файле index.html не забудьте подключить только что созданный файл main.js

Всё, теперь можно приступать к написанию нашего скрипта анимации. Анимация должна начинаться при клике на кнопку "Animate" , поэтому первым делом найдём её в коде страницы и сохраним в переменную btn.

Код

let btn = document.querySelector('.btn');


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

Код

let square = document.querySelector('.box');


Начинаем писать функцию, ответственную за анимацию. Назовём её myAnimation:

Код

function myAnimation() {
  let position = 0;
};


внутри функции задаётся переменная position, в которой начальное значение равное нулю. У нашего квадрата есть две координаты top и left(первоначально они обе равны 0, квадрат находится в верхнем левом углу), изменяя которые на одинаковое значение одновременно, мы добьёмся перемещения нашего квадарта по диагонали.

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

Код

function myAnimation() {
  let position = 0;

  function frame() {
   
  }
};


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

Код

function myAnimation() {
  let position = 0;

  function frame() {
  if(position == 300) {
  clearInterval();
  } else {
  position++;
  square.style.top = position + 'px';
  square.style.left = position + 'px';
  }
  }
};


что мы написали внутри функции? внутри функции в условии мы прописали, что если значение переменной position будет равно 300, то таймер должен быть остановлен.
Мы это сделали потому, что функцию frame мы будем запускать с помощью команды setInterval(), которую предварительно сохраним в переменной timerId:

Код

function myAnimation() {
  let position = 0;

  let timerId = setInterval(frame, 10);

  function frame() {
  if(position == 300) {
  clearInterval(timerId);
  } else {
  position++;
  square.style.top = position + 'px';
  square.style.left = position + 'px';
  }
  }
};


В круглых скобках у команды setInterval мы поместили функцию, которую нужно вызывать(frame) и поставили задержку в 10 миллисекунд.

И теперь, функция будет выполняться, до тех пор, пока значение переменной position не станет равным 300, к каждой из координат top и left при каждом запуске функции через 10 миллисекунд будет прибавляться по 1px, за счёт чего наш квадратик будет постоянно смещаться вниз и вправо (то есть двигаться по диагонали).

Как только значение переменной position станет равным 300, таймер обнуляется командой clearInterval(timerId).

Осталось только привязать нашу функцию к кнопке, чтобы по клику на неё и запускалась анимация.

Код

btn.addEventListener('click', myAnimation);


Протестировать анимацию

Делегирование событий



В коде нашего файла index.html после блока с классом wrapper давайте добавим блок div с классом btn-block с кнопками внутри него:

Код

  <div class = 'btn-block'>
  <button class = 'first'></button>
  <button></button>
  <button></button>
  <button></button>
  <button></button>
  <button></button>
  <button></button>
  <button></button>
  </div>


Если сейчас вы откроете этот файл в браузере, то увидите и этот блок и кнопки внутри него, фон блока серого цвета, кнопки - жёлтого.



Суть делегирования состоит в том, что работа ведётся с родительским элементом (в данном случае это блок-обёртка div с классом btn-block), а функция назначается для его потомков (элементы button внутри него).

Давайте найдём наш блок-обёртку:

Код

let btnBlock = document.querySelector('.btn-block');


также нам нужно получить все кнопки, лежащие внутри нашего блока-обёртки:

Код

let btns = document.getElementsByTagName('button');


начинаем работу с родительским элементом, прицепляем к нему обработчик события click:

Код

btnBlock.addEventListener('click', function(event) {

});


а далее прописываем содержимое нашей коллбэк-функции:

Код

btnBlock.addEventListener('click', function(event) {
  if(event.target && event.target.tagName == 'BUTTON') {
  console.log('Привет!');
  }
});


Свойство event.target содержит элемент, на котором сработало событие. Это не тот элемент, к которому был привязан обработчик этого события, а именно самый глубокий тег, на который непосредственно был, к примеру, совершен клик.

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

Другими словами, если обработчик у нас привязан к блоку-обёртке div, но кликаем мы по кнопке, лежащей внутри этого блока, то event.target будет содержать конечный тег, в котором случилось событие, то есть button, а не блок-обёртку div.

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

Можно применить делегирование только к элементам, имеющим, к примеру, определённый класс. Например, первая кнопка в блоке у нас имеет класс first, тогда наш код можно переписать таким образом:

Код

btnBlock.addEventListener('click', function(event) {
  if(event.target && event.target.classList.contains('first')) {
  console.log('Привет!');
  }
});


И теперь в консоль будет выводиться "Привет!" только лишь при клике на первую кнопку в блоке, при клике на все остальные кнопки ничего выводиться не будет.

Можно также воспользоваться методом matches(), с помощью которого можно найти, к примеру, конкретный элемент с конкретным классом:

Код

btnBlock.addEventListener('click', function(event) {
  if(event.target && event.target.matches('button.first')) {
  console.log('Привет!');
  }
});

Комментарии к материалу: