Веб-проект: страница с кнопкой, которая убегает от мышки
easy

Веб-проект: страница с кнопкой, которая убегает от мышки

Нетрудно найти, легко написать, невозможно поймать

Сегодня мы сделаем страницу с текстом и кнопкой, которая убегает от курсора и на которую нельзя нажать. В реальности такая кнопка бесполезна, но при создании этой страницы мы потренируемся работать с 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`;
    });
  });

Редактор:

Инна Долога

Обложка:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Маша Климентьева

Соцсети:

Юлия Зубарева

Фронтенд-разработка — востребованная профессия
На новом курсе «Практикума» о фронтенде вас обучат самым востребованным технологиям: JS и TypeScript, Flexbox и Grid, React, Git, Bash и др. Это то, что нужно работодателям сегодня. Старт — бесплатно.
Попробовать бесплатно
Фронтенд-разработка — востребованная профессия Фронтенд-разработка — востребованная профессия Фронтенд-разработка — востребованная профессия Фронтенд-разработка — востребованная профессия
Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Начать карьеру в ИТ
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Еще по теме
easy