Рисуем круг, движущийся вслед за курсором мыши
Сегодня мы с помощью canvas JavaScript нарисуем круг, который будет двигаться вслед за курсором мыши.
Основной подход
Прежде чем мы перейдем к коду, поговорим о том, как мы создадим круг, следующий за курсором мыши. Но сначала нам нужно нарисовать эту окружность:

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

Этот пример не такой уж сложный, чтобы разобраться в нем. Но это не значит, что нет ничего интересного “за кулисами”. Далее при рассмотрении 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, но и вызываем ее. Если вы просмотрите свою страницу в браузере, то увидите следующее:

Наш круг имеет радиус 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. Теперь наш круг повсюду будет следовать за курсором мыши:

Круг двигается вслед за движением мыши, но более ранние позиции круга не стираются. Это создает эффект рисования пальцем - не совсем то, чего мы добивались. Для решения этой проблемы перед прорисовкой круга на новом месте необходимо очистить весь холст.
Чтобы сделать это, добавьте следующую выделенную строку кода в функцию 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.
Мы разделили код обновления координат курсора мыши и код рисования на экране. Это гарантирует, что когда браузер будет готов, мы нарисуем новый круг. Когда круг будет нарисован, позиция курсора в этот момент будет максимально точна.
Вывод
Все это начиналось, как простой эффект. Теперь у нас есть круг, который следует за курсором мыши.