Рисуем круг, движущийся вслед за курсором мыши

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

Основной подход

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

circle_scale_144

Надпись на картинке: Круг нарисован без соблюдения масштаба.

Положение круга будет меняться в зависимости от того, где именно находится курсор мыши. Это означает, что при каждом изменении положения курсора мыши нам нужно перерисовывать круг:

circle_position_changing_144

Этот пример не такой уж сложный, чтобы разобраться в нем. Но это не значит, что нет ничего интересного “за кулисами”. Далее при рассмотрении JavaScript мы коснемся некоторых важных аспектов приведенного кода, которые вы, возможно, не поняли.

С чего начать работу?

Первое, что нам нужно — это страница, готовая к работе с canvas JavaScript. Если у вас еще нет ее, то поместите следующий код в пустую HTML-страницу:

<!DOCTYPE html>
<html> 
<head>
  <title>Canvas Follow Mouse</title>
  <style>
    canvas {
      border: #333 10px solid;
    } 
    body {
      padding: 50px;
    }
  </style>
</head> 
<body>
  <canvas id="myCanvas" width="550px" height="350px"></canvas> 
  <script>
    var canvas = document.querySelector("#myCanvas");
    var context = canvas.getContext("2d");
  </script> 
</body> 
</html>

Есть canvas JavaScript, который имеет идентификатор id со значением «myCanvas». Для экономии времен, я предоставил вам две строки кода, необходимых для доступа к элементу canvas и его контексту рендеринга. Если все это для вас ново, уделите несколько минут и прочитайте статью «С чего начать работу с элементом Canvas».

Рисуем круг

Первое, что мы собираемся сделать, это нарисовать круг. Внутри тега <script> добавьте следующий код после строки с переменной context:

function update() {
  context.beginPath();
  context.arc(100, 100, 50, 0, 2 * Math.PI, true);
  context.fillStyle = "#FF6A6A";
  context.fill();
}
update();

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

circle_temp_144

Наш круг имеет радиус 50 пикселей и расположен в точке (100, 100). На данный момент, мы собираемся сохранить позицию круга фиксированной. Это не надолго, до тех пор пока мы не получим позицию курсора мыши в canvas с помощью методов JavaScript!

Получение позиции курсора мыши

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

Прослушивание события мыши

Давайте посмотрим на код для первой части. Добавьте следующий код чуть выше функции update:

var mouseX = 0;
var mouseY = 0;

canvas.addEventListener("mousemove", setMousePosition, false);

function setMousePosition(e) {
  mouseX = e.clientX;
  mouseY = e.clientY;
}

Этот код выполняет довольно простые вещи. Мы отслеживаем событие MouseMove на холсте. И когда это событие наступает, мы вызываем обработчик события setMousePosition:

function setMousePosition(e) {
  mouseX = e.clientX;
  mouseY = e.clientY;
}

Функция setMousePosition присваивает текущее горизонтальное и вертикальное положение курсора мыши свойствам mouseX и mouseY. Она делает это, опираясь на свойства clientX и clientY, предоставляемые MouseEvent.

Получение точных координат курсора мыши

Позиция курсора мыши, сохраненная в свойствах mouseX и mouseY, по умолчанию содержит координаты, заданные относительно левого верхнего угла окна браузера. Значения координат курсора мыши, которые мы имеем сейчас, неточные, так как не учитывает положение элемента canvas JavaScript.

Чтобы исправить это, у нас есть функция GetPosition:

function getPosition(el) {
  var xPosition = 0;
  var yPosition = 0;

  while (el) {
    xPosition += (el.offsetLeft - el.scrollLeft + el.clientLeft);
    yPosition += (el.offsetTop - el.scrollTop + el.clientTop);
    el = el.offsetParent;
  }
  return {
    x: xPosition,
    y: yPosition
  };
}

Добавьте эту функцию в нижнюю часть кода, ниже функции update.

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

Чтобы использовать функцию GetPosition и исправить значения mouseX и mouseY, внесите следующие дополнения и изменения, которые я выделил:

var canvasPos = getPosition(canvas);
var mouseX = 0;
var mouseY = 0;

canvas.addEventListener("mousemove", setMousePosition, false);

function setMousePosition(e) {
  mouseX = e.clientX - canvasPos.x;
  mouseY = e.clientY - canvasPos.y;
}

Теперь переменная CanvasPos хранит позицию, которую возвращает функция GetPosition. В обработчике событий setMousePosition мы используем значения х и у, возвращенные из canvasPos для коррекции значения, сохраненного с помощью переменных mouseX и mouseY.

Перемещение круга

В предыдущем разделе мы получили код курсора мыши и переменные mouseX и mouseY, хранящие текущее положение курсора мыши на холсте. Остается только подключить эти значения к нашему коду прорисовки круга внутри функции update, чтобы получить положение круга, отражающее позицию курсора мыши для рисования на canvas JavaScript.

Для начала преобразуем функцию update в объект обратного вызова requestAnimationFrame. Благодаря этому функция синхронизируется со скоростью рисования браузера (около 60 раз в секунду). Добавьте следующую выделенную строку в нижней части функции update:

function update() {
  context.beginPath();
  context.arc(100, 100, 50, 0, 2 * Math.PI, true);
  context.fillStyle = "#FF6A6A";
  context.fill();

  requestAnimationFrame(update);
}
update();

Теперь нам нужно обновить код прорисовки круга, чтобы использовать значения mouseX и mouseY вместо фиксированной позиции (100, 100), которую мы определили первоначально. Внесите в выделенную строку следующие изменения:

function update() {
  context.beginPath();
  context.arc(mouseX, mouseY, 50, 0, 2 * Math.PI, true);
  context.fillStyle = "#FF6A6A";
  context.fill();

  requestAnimationFrame(update);
}
update();

После этого сохраните HTML-документ и просмотрите его в браузере. Посмотрите, что происходит при перемещении мыши по canvas JavaScript. Теперь наш круг повсюду будет следовать за курсором мыши:

circle_no_redraw_144

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

Чтобы сделать это, добавьте следующую выделенную строку кода в функцию update:

unction update() {
  context.clearRect(0, 0, canvas.width, canvas.height);

  context.beginPath();
  context.arc(mouseX, mouseY, 50, 0, 2 * Math.PI, true);
  context.fillStyle = "#FF6A6A";
  context.fill();

  requestAnimationFrame(update);
}

Строка, которую мы добавили, содержит вызов метода clearRect, который отвечает за очистку всех пикселей холста. Для его применения нужно передать размеры области, которую нужно очистить:

context.clearRect (0, 0, canvas.width, canvas.height);

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

Зачем использовать requestAnimationFrame?

Весь код, связанный с рисунком, находится внутри функции update, которая циклично выполняет функцию requestAnimationFrame. Никакой анимации здесь нет. Мы просто перемещаем курсор мыши и обновляем позицию круга только, когда курсор перемещается. Учитывая все это, почему не весь код прорисовки присутствует в обработчике события MouseMove? Это выглядело бы примерно так:

canvas.addEventListener("mousemove", setMousePosition, false);

function setMousePosition(e) {
  mouseX = e.clientX - canvasPos.x;
  mouseY = e.clientY - canvasPos.y;

  context.clearRect(0, 0, canvas.width, canvas.height);

  context.beginPath();
  context.arc(mouseX, mouseY, 50, 0, 2 * Math.PI, true);
  context.fillStyle = "#FF6A6A";
  context.fill();
}

Если вы смогли сделать это изменение (и полностью избавились от функции update), наш пример все равно будет продолжать работать. Наш пример может работать так же, как requestAnimationFrame.

Причина кроется в помогающем нам браузере, который не делает лишнюю работу и выполняет «правильные» вещи, так как наша конечная цель. Когда дело доходит до рисования на canvas JavaScript, мы хотим быть синхронными с браузером в тот момент, когда он готов к прорисовке пикселей.

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

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

Вывод

Все это начиналось, как простой эффект. Теперь у нас есть круг, который следует за курсором мыши.

Перевод статьи “Follow the Mouse Cursor” был подготовлен дружной командой проекта Сайтостроение от А до Я.