Использование метода bind для циклов

Для работы с Two.js сначала необходимо создать экземпляр класса Two и привязать его к HTML-контейнеру. Обычно используется следующий синтаксис:

const elem = document.getElementById('draw-shapes');
const params = { width: 800, height: 600 };
const two = new Two(params).appendTo(elem);

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

После инициализации сцены можно создавать объекты и добавлять их к сцене.

const circle = two.makeCircle(100, 100, 50);
circle.fill = '#FF8000';
circle.stroke = 'orangered';
circle.linewidth = 5;
two.update();

makeCircle создаёт круг с координатами центра (x, y) и радиусом radius. Атрибуты fill, stroke и linewidth задают заливку, цвет границы и толщину линии соответственно.


Проблема с циклами и сохранением контекста

При создании нескольких объектов в цикле часто возникает необходимость привязки событий, а именно — сохранение правильного значения индекса цикла. Пример без использования bind:

for (var i = 0; i < 5; i++) {
  const circle = two.makeCircle(100 + i * 60, 100, 30);
  circle.fill = '#00BCD4';
  circle.stroke = 'black';
  circle.linewidth = 2;
  
  circle._i = i; // попытка сохранить индекс
  circle.addEventListener('click', function() {
    console.log('Кликнут круг с индексом:', i);
  });
}

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


Решение с использованием метода bind

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

for (var i = 0; i < 5; i++) {
  const circle = two.makeCircle(100 + i * 60, 100, 30);
  circle.fill = '#4CAF50';
  circle.stroke = 'black';
  circle.linewidth = 2;

  function handleClick(index) {
    console.log('Кликнут круг с индексом:', index);
  }

  circle.addEventListener('click', handleClick.bind(null, i));
}

Здесь bind(null, i) создаёт новую функцию, в которой первый аргумент (index) фиксирован значением текущего i.

Ключевые моменты:

  • Первый аргумент bind определяет контекст this. Если контекст не требуется, используют null.
  • Последующие аргументы фиксируются и передаются при вызове функции.
  • Каждому объекту назначается отдельная функция с привязанным значением, что предотвращает проблемы замыканий.

Применение в анимации

В Two.js часто создаются динамические сцены с анимацией объектов. При использовании bind можно контролировать отдельные элементы в анимационном цикле:

const circles = [];

for (var i = 0; i < 10; i++) {
  const c = two.makeCircle(50 + i * 50, 300, 20);
  c.fill = '#FFC107';
  c.speed = 0.02 + i * 0.01;
  circles.push(c);

  function animateCircle(circle) {
    circle.translation.x += circle.speed * 5;
    if (circle.translation.x > two.width) circle.translation.x = 0;
  }

  two.bind('update', animateCircle.bind(null, c));
}

two.play();

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


Использование bind с контекстом this

Если необходимо, чтобы обработчик имел доступ к самому объекту через this, первый аргумент bind используется для привязки контекста:

for (var i = 0; i < 5; i++) {
  const rect = two.makeRectangle(100 + i * 70, 400, 40, 40);
  rect.fill = '#9C27B0';

  function logThis() {
    console.log('Текущий объект:', this);
  }

  rect.addEventListener('click', logThis.bind(rect));
}

Теперь внутри logThis ключевое слово this ссылается на конкретный прямоугольник, а не на глобальный объект или undefined.


Советы по оптимизации

  1. Минимизировать использование глобальных переменных в циклах, чтобы исключить конфликт контекста.
  2. Использовать let вместо var, когда возможно, так как let создаёт блочную область видимости и автоматически решает проблему с замыканиями.
  3. Объединять функции анимации и событий с bind для каждого объекта, чтобы обеспечить уникальное поведение.
  4. Документировать привязки, чтобы код оставался читаемым при большом количестве объектов на сцене.

Пример комбинированного подхода

const two = new Two({ width: 600, height: 400 }).appendTo(document.body);

for (let i = 0; i < 8; i++) {
  const c = two.makeCircle(50 + i * 60, 200, 25);
  c.fill = i % 2 === 0 ? '#03A9F4' : '#E91E63';

  function moveCircle(index, circle) {
    circle.translation.y += (index % 2 === 0 ? 1 : -1) * 2;
    if (circle.translation.y > 400) circle.translation.y = 0;
    if (circle.translation.y < 0) circle.translation.y = 400;
  }

  two.bind('update', moveCircle.bind(null, i, c));
}

two.play();

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


Метод bind становится незаменимым инструментом при создании интерактивных сцен с циклическим созданием объектов, сохранением контекста и привязкой индивидуального поведения в Two.js.