Сегодня мы сделаем страницу с текстом и кнопкой, которая убегает от курсора и на которую нельзя нажать. В реальности такая кнопка бесполезна, но при создании этой страницы мы потренируемся работать с DOM и методами обработки событий, а также научимся определять позицию элемента — в веб-разработке это довольно частая задача.
Логика проекта
Суть проекта проста — кнопка не даёт попасть на неё курсором и убегает от него. Для этого сделаем такое:
- Создадим веб-страницу и добавим HTML-разметку.
- Добавим на страницу кнопку с помощью стилей CSS.
- Напишем JS-скрипт, который сделает нашу кнопку интерактивной. Кнопка будет реагировать на движение курсора, перемещаясь на фиксированное расстояние в противоположную сторону.
Создаём веб-страницу
Создадим HTML-страницу с базовой разметкой:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Здесь подключим стили -->
<title>Догонялки</title>
</head>
<body>
<h1>Чтобы получить премию, нажмите на красную кнопку</h1>
<!-- Здесь будет код кнопки -->
<!--Здесь подключим скрипт -->
</body>
</html>
У нас получилась такая страница с заголовком:
Добавляем кнопку
Наша кнопка будет не картинкой, а CSS-элементом — мы создадим её с помощью кода, который возьмём с сайта uiverse.io. Там есть много интересных и красивых UI-элементов, и все они распространяются по открытой лицензии. Это значит, что код можно свободно использовать в своих проектах.
HTML-код нашей кнопки будет таким:
<button class="btn-class-name">
<span class="back"></span>
<span class="front"></span>
</button>
В button
есть два элемента back
и front
: back
создаёт эффект высоты и объёма кнопки, front
отвечает за её большую видимую часть.
Добавим в HTML-код страницы идентификатор кнопки, чтобы мы в дальнейшем могли обращаться к ней в скрипте:
<button class="btn-class-name" id="runawayBtn">
Поскольку страница пока не знает, как ей отрисовать кнопку, она показывает её в виде еле заметного прямоугольного элемента под заголовком:
Настраиваем стили
Создадим файл styles.css
и подключим его в HTML:
<link rel="stylesheet" href="styles.css">
Теперь добавим стили для страницы и заголовка.
/* фон страницы */
body {
/* флекс-контейнер */
display: flex;
/* выравнивание по центру */
justify-content: center;
/* без отступов */
margin: 0;
/* цвет фона */
background-color: #e5e5e5;
}
/* стили заголовка */
h1 {
/* семейство шрифтов */
font-family: "Courier New", Courier, monospace;
/* выводим всё заглавными буквами */
text-transform: uppercase;
/* размер шрифта */
font-size: 26px;
/* отступы сверху, по бокам и снизу */
padding: 1em 0 0 1em;
/* цвет шрифта */
color: #1a2b4c;
/* выравнивание по центру */
text-align: center;
}
Мы добавили фон, отцентровали элементы в контейнере, задали шрифт и стилизовали заголовок:
Теперь добавим стили для кнопки:
/* общие стили кнопки */
.btn-class-name {
/* основной цвет */
--primary: 255, 90, 120;
/* дополнительный цвет */
--secondary: 150, 50, 60;
/* ширина */
width: 60px;
/* высота */
height: 50px;
/* без рамок */
border: none;
/* без контура */
outline: none;
/* меняем форму курсора на указатель */
cursor: pointer;
/* внешний контур */
outline: 10px solid rgb(var(--primary), .5);
/* полное скругление */
border-radius: 100%;
/* относительное позиционирование */
position: relative;
/* анимация в течение 0,3 секунды */
transition: .3s;
}
/* стили заднего слоя кнопки */
.btn-class-name .back {
/* фон */
background: rgb(var(--secondary));
/* полное скругление */
border-radius: 100%;
/* абсолютное позиционирование */
position: absolute;
/* отступ слева */
left: 0;
/* отступ сверху */
top: 0;
/* ширина */
width: 100%;
/* высота */
height: 100%;
}
/* стили переднего слоя кнопки */
.btn-class-name .front {
/* фон в виде линейного градиента */
background: linear-gradient(0deg, rgba(var(--primary), .6) 20%, rgba(var(--primary)) 50%);
/* тень под элементом */
box-shadow: 0 .5em 1em -0.2em rgba(var(--secondary), .5);
/* полное скругление */
border-radius: 100%;
/* абсолютное позиционирование */
position: absolute;
/* рамка */
border: 1px solid rgb(var(--secondary));
/* отступ слева */
left: 0;
/* отступ сверху */
top: 0;
/* ширина */
width: 100%;
/* высота */
height: 100%;
/* включаем флексбокс-модель */
display: flex;
/* горизонтальное выравнивание по центру */
justify-content: center;
/* вертикальное выравнивание по центру */
align-items: center;
/* смещение вниз для анимации нажатия */
transform: translateY(-15%);
/* анимация в течение 0,15 секунды */
transition: .15s;
/* цвет */
color: rgb(var(--secondary));
}
/* стили переднего слоя в активном состоянии */
.btn-class-name:active .front {
/* без анимации нажатия */
transform: translateY(0%);
/* без тени */
box-shadow: 0 0;
}
Мы задали цвета кнопки с помощью переменных --primary
и --secondary
. Если захотим изменить цвета, не придётся менять значения в каждом свойстве.
С помощью transform
и transition
мы настроили интерактивность кнопки. Изменение свойства transform
в псевдоклассе :active
для .front
создаёт анимацию при нажатии кнопки. Передний слой кнопки перемещается вверх и вниз.
Использование градиента для фона .front
имитирует глубину и текстуру. Свойство box-shadow
усиливает трёхмерный вид кнопки, особенно когда она неактивна, а затем исчезает при нажатии, создавая видимость, что кнопка нажата.
Вот что получилось:
Пока кнопка ещё не умеет убегать, но понажимаем её и посмотрим, как работает анимация:
Поскольку мы указали в стилях body
свойство display: flex
, то по умолчанию элементы контейнера выстраиваются слева направо в горизонтальную линию. Когда будем писать скрипт, сделаем так, чтобы кнопка располагалась в центре экрана.
Пишем скрипт
Мы нарисовали кнопку, теперь займёмся скриптом, который будет отвечать за то, чтобы она убегала от курсора. Логика работы нашего скрипта будет такой:
- После того как структура документа DOM полностью загружена, скрипт начинает работу.
- Находим кнопку по идентификатору
runawayBtn
и получаем её начальные координаты. - Вычисляем центральную позицию для кнопки относительно размеров окна браузера и размещаем кнопку в центре экрана.
- Добавляем кнопке обработчик события, который активируется, когда пользователь пытается навести на неё курсор.
- При срабатывании события кнопка убегает от курсора, меняя положение на странице, чтобы избежать нажатия.
Создадим файл script.js. Все действия в скрипте начинаются после того, как документ полностью загружен:
document.addEventListener("DOMContentLoaded", () => {
Здесь мы использовали метод addEventListener
для добавления обработчика события к объекту document
.
Затем создадим переменные для получения кнопки и расчёта её позиционирования. Сначала ищем нужный элемент для работы по его идентификатору и создаём переменную со ссылкой на кнопку:
const btn = document.getElementById("runawayBtn");
Наш скрипт будет получать текущее положение и размеры кнопки через getBoundingClientRect()
— это нужно для дальнейших расчётов перемещения кнопки:
const btnRect = btn.getBoundingClientRect();
Эти переменные задаём с помощью ключевого слова const
, поскольку их значения не будут меняться в процессе работы скрипта.
Теперь нам нужно вычислить центральную позицию для кнопки относительно окна браузера и установить её в этом месте с фиксированным позиционированием:
const centerX = (window.innerWidth - btnRect.width) / 2;
const centerY = (window.innerHeight - btnRect.height) / 2;
btn.style.position = "fixed";
btn.style.left = `${centerX}px`;
btn.style.top = `${centerY}px`;
Свойство window.innerWidth
возвращает ширину внутренней области окна браузера, то есть видимой области, где отображается контент. А с помощью btnRect.width
мы получим ширину кнопки. Для расположения кнопки по центру окна мы вычитаем ширину кнопки из ширины окна и делим результат на два. Точно так же поступаем с высотой кнопки.
Теперь устанавливаем для кнопки стиль position: fixed
, чтобы она оставалась в одном месте независимо от прокрутки. Применяем рассчитанные через свойства left
и top
координаты, чтобы кнопка располагалась точно по центру видимой части окна браузера.
Теперь пропишем основную часть логики. Нам нужно создать две функции:
- Первая будет рассчитывать координаты курсора относительно центра кнопки.
- Вторая на основе этого значения рассчитает новые координаты для кнопки.
Начинаем с координат курсора:
function getMouseCoords(event, btnRect) {
return {
x: event.clientX - (btnRect.left + window.scrollX + btnRect.width / 2),
y: event.clientY - (btnRect.top + window.scrollY + btnRect.height / 2),
};
}
Функция getMouseCoords
принимает на вход два значения:
- Параметр
event
, представляющий объект события. Он содержит информацию о событии мышки, включая координаты курсора. Его мы создадим позже. - Объект
btnRect
, который хранит информацию о размерах и позиции кнопки.
Функция вычитает из текущих координат курсора половину ширины и высоты кнопки для нахождения её центра (event.clientX
и event.clientY
), учитывая при этом текущую прокрутку страницы (window.scrollX
и window.scrollY
). Таким образом функция определяет, насколько слева, справа, выше или ниже центра кнопки находится курсор.
После того как функция getMouseCoords
вычисляет разницу между местоположением курсора и центром кнопки, вторая функция calculateNewPosition
на основе этой разницы рассчитывает новое местоположение кнопки, ограничивая его видимой областью окна браузера.
function calculateNewPosition(deltaX, deltaY, btnRect) {
let newX = btnRect.left + deltaX + window.scrollX;
let newY = btnRect.top + deltaY + window.scrollY;
newX = Math.max(0, Math.min(newX, window.innerWidth - btnRect.width));
newY = Math.max(0, Math.min(newY, window.innerHeight - btnRect.height));
return { newX, newY };
}
Функция calculateNewPosition
принимает на вход три параметра:
- смещение кнопки по горизонтали
deltaX
, - смещение кнопки по вертикали
deltaY
, - объект
btnRect
, представляющий текущие размеры и позицию кнопки.
Функция начинает с текущего положения элемента btnRect.left
и btnRect.top
, добавляет к нему смещение и учитывает текущую прокрутку окна window.scrollX
и window.scrollY
. Затем функция ограничивает эти новые координаты, чтобы кнопка не уходила за границы видимой части окна браузера. Для этого используем Math.max
и Math.min
, чтобы координаты не были меньше нуля и не превышали размеры окна за вычетом размеров самой кнопки. В результате функция возвращает новые координаты newX
и newY
.
Внутри функции задаём переменные через let
, поскольку значения переменной меняются.
К этому моменту наш скрипт умеет:
- получать ссылку на кнопку;
- располагать её в центре экрана;
- вычислять разницу между местоположением курсора и центром кнопки;
- рассчитывать новое местоположение кнопки.
Теперь нужно сделать так, чтобы всё это работало, когда курсор наводится на кнопку. Для этого используем обработчик события мыши, повесим его на кнопку и пропишем логику движения.
btn.addEventListener("mousemove", (event) => {
const btnRect = btn.getBoundingClientRect();
const maxDistance = 150;
const mousePos = getMouseCoords(event, btnRect);
Мы добавили к нашей кнопке обработчик события mousemove
, который активируется каждый раз, когда курсор мыши появляется над кнопкой. При активации события получаем текущее положение и размеры кнопки с помощью метода getBoundingClientRect()
.
Далее задаём переменную maxDistance
: в ней хранится максимальное расстояние, на которое кнопка может переместиться за одно событие. После этого вызывается функция getMouseCoords
: в неё передаётся текущее событие мыши и размеры кнопки.
const deltaX = mousePos.x > 0 ? -maxDistance : maxDistance;
const deltaY = mousePos.y > 0 ? -maxDistance : maxDistance;
Затем определяем значения deltaX
и deltaY
: они указывают направление и величину перемещения кнопки в ответ на положение курсора. Если координата x
курсора больше нуля, это значит, что курсор находится справа от центра кнопки. Тогда deltaX
устанавливается как отрицательное значение maxDistance
, заставляя кнопку перемещаться влево. Аналогично происходит для оси y
.
const { newX, newY } = calculateNewPosition(deltaX, deltaY, btnRect);
В этой строке вызывается функция calculateNewPosition
с параметрами deltaX
, deltaY
и текущими размерами и положением кнопки. Новые координаты определяют, куда должна переместиться кнопка.
btn.style.left = `${newX}px`;
btn.style.top = `${newY}px`;
});
При этом обновляются CSS-стили кнопки, устанавливая её новое положение на странице. Свойства left
и top
становятся равными значениям newX
и newY
.
Подключаем скрипт
Поскольку наш скрипт зависит от элементов DOM, важно подключить его в конце, перед закрывающим тегом </body>
, чтобы он выполнился только после полной загрузки документа. Вставляем эту строку в HTML-разметку:
<script src="script.js"></script>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Здесь подключим стили -->
<link rel="stylesheet" href="styles.css">
<title>Догонялки</title>
</head>
<body>
<h1>Чтобы получить премию, нажмите на красную кнопку</h1>
<!-- Здесь будет код кнопки -->
<button class="btn-class-name" id="runawayBtn">
<span class="back"></span>
<span class="front"></span>
</button>
<!--Здесь подключим скрипт -->
<script src="script.js"></script>
</body>
</html>
/* фон страницы */
body {
/* флекс-контейнер */
display: flex;
/* выравнивание по центру */
justify-content: center;
/* без отступов */
margin: 0;
/* цвет фона */
background-color: #e5e5e5;
}
/* стили заголовка */
h1 {
/* семейство шрифтов */
font-family: "Courier New", Courier, monospace;
/* капитализация символов */
text-transform: uppercase;
/* размер шрифта */
font-size: 26px;
/* отступы сверху, по бокам и снизу */
padding: 1em 0 0 1em;
/* цвет шрифта */
color: #1a2b4c;
/* выравнивание по центру */
text-align: center;
}
/* общие стили кнопки */
.btn-class-name {
/* основной цвет */
--primary: 255, 90, 120;
/* дополнительный цвет */
--secondary: 150, 50, 60;
/* ширина */
width: 60px;
/* высота */
height: 50px;
/* без рамок */
border: none;
/* без контура */
outline: none;
/* меняем форму курсора на указатель */
cursor: pointer;
/* внешний контур */
outline: 10px solid rgb(var(--primary), .5);
/* полное скругление */
border-radius: 100%;
/* относительное позиционирование */
position: relative;
/* анимация в течение 0,3 секунды */
transition: .3s;
}
/* стили заднего слоя кнопки */
.btn-class-name .back {
/* фон */
background: rgb(var(--secondary));
/* полное скругление */
border-radius: 100%;
/* абсолютное позиционирование */
position: absolute;
/* отступ слева */
left: 0;
/* отступ сверху */
top: 0;
/* ширина */
width: 100%;
/* высота */
height: 100%;
}
/* стили переднего слоя кнопки */
.btn-class-name .front {
/* фон в виде линейного градиента */
background: linear-gradient(0deg, rgba(var(--primary), .6) 20%, rgba(var(--primary)) 50%);
/* тень под элементом */
box-shadow: 0 .5em 1em -0.2em rgba(var(--secondary), .5);
/* полное скругление */
border-radius: 100%;
/* абсолютное позиционирование */
position: absolute;
/* рамка */
border: 1px solid rgb(var(--secondary));
/* отступ слева */
left: 0;
/* отступ сверху */
top: 0;
/* ширина */
width: 100%;
/* высота */
height: 100%;
/* флексбокс-модель */
display: flex;
/* горизонтальное выравнивание по центру */
justify-content: center;
/* вертикальное выравнивание по центру */
align-items: center;
/* смещение вниз для анимации нажатия */
transform: translateY(-15%);
/* анимация в течение 0,15 секунды */
transition: .15s;
/* цвет */
color: rgb(var(--secondary));
}
/* стили переднего слоя в активном состоянии */
.btn-class-name:active .front {
/* без анимации нажатия */
transform: translateY(0%);
/* без тени */
box-shadow: 0 0;
}
document.addEventListener("DOMContentLoaded", () => {
const btn = document.getElementById("runawayBtn");
const btnRect = btn.getBoundingClientRect();
const centerX = (window.innerWidth - btnRect.width) / 2;
const centerY = (window.innerHeight - btnRect.height) / 2;
btn.style.position = "fixed";
btn.style.left = `${centerX}px`;
btn.style.top = `${centerY}px`;
function getMouseCoords(event, btnRect) {
return {
x: event.clientX - (btnRect.left + window.scrollX + btnRect.width / 2),
y: event.clientY - (btnRect.top + window.scrollY + btnRect.height / 2),
};
}
function calculateNewPosition(deltaX, deltaY, btnRect) {
let newX = btnRect.left + deltaX + window.scrollX;
let newY = btnRect.top + deltaY + window.scrollY;
newX = Math.max(0, Math.min(newX, window.innerWidth - btnRect.width));
newY = Math.max(0, Math.min(newY, window.innerHeight - btnRect.height));
return { newX, newY };
}
btn.addEventListener("mousemove", (event) => {
const btnRect = btn.getBoundingClientRect();
const maxDistance = 150;
const mousePos = getMouseCoords(event, btnRect);
const deltaX = mousePos.x > 0 ? -maxDistance : maxDistance;
const deltaY = mousePos.y > 0 ? -maxDistance : maxDistance;
const { newX, newY } = calculateNewPosition(deltaX, deltaY, btnRect);
btn.style.left = `${newX}px`;
btn.style.top = `${newY}px`;
});
});