Пользовательские эффекты курсора

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

Посмотреть просмотр Скачать исходный код

В данной статье мы рассмотрим, как создать курсор с эффектом преобразования в деформированный круг для элементов навигации. Мы будем использовать Paper.js с Simplex Noise.

Пользовательские эффекты курсора
Пользовательский эффект курсора, который мы собираемся создать

Разметка

Разметка для курсора состоит из <div> для маленькой белой точки и элемента <Canvas> для рисования красного круга при помощи Paper.js.

<body class="tutorial"> <main class="page"> <div class="page__inner"> <!-- Элемент курсора --> <div class="cursor cursor--small"></div> <canvas class="cursor cursor--canvas" resize></canvas>       </div>  </main></body>

Основные цвета и макет

Определяем основные стили.

body.tutorial {  --color-text: #fff;  --color-bg: #171717;  --color-link: #ff0000;  background-color: var(--color-bg);}.page {  position: absolute;  width: 100%;  height: 100%;  display: flex;  justify-content: center;  align-items: center;}.page__inner {  display: flex;  justify-content: center;  width: 100%;}

Стили курсора

Оба элемента курсора имеют фиксированную позицию. На кончике указателя мыши мы настраиваем левую и верхнюю часть маленького курсора. Холст просто заполнит все окно просмотра.

.cursor {  position: fixed;  left: 0;  top: 0;  pointer-events: none;}.cursor--small {  width: 5px;  height: 5px;  left: -2.5px;  top: -2.5px;  border-radius: 50%;  z-index: 11000;  background: var(--color-text);}.cursor--canvas {  width: 100vw;  height: 100vh;  z-index: 12000;}

Ссылки

Для простоты используем один элемент ссылки, содержащий SVG-иконку, которую далее можно анимировать при наведении курсора.

<nav class="nav"> <a href="#" class="link">  <svg class="settings-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">   <g class="settings-icon__group settings-icon__group--1">     <line class="settings-icon__line" x1="79.69" y1="16.2" x2="79.69" y2="83.8"/>     <rect class="settings-icon__rect" x="73.59" y="31.88" width="12.19" height="12.19"/>    </g>   <g class="settings-icon__group settings-icon__group--2">     <line class="settings-icon__line" x1="50.41" y1="16.2" x2="50.41" y2="83.8"/>     <rect class="settings-icon__rect" x="44.31" y="54.33" width="12.19" height="12.19"/>   </g>   <g class="settings-icon__group settings-icon__group--3">     <line class="settings-icon__line" x1="20.31" y1="16.2" x2="20.31" y2="83.8"/>     <rect class="settings-icon__rect" x="14.22" y="26.97" width="12.19" height="12.19"/>   </g>  </svg> </a> <!-- здесь вы можете указать иные ссылки --></nav>

Стили меню навигации и ссылок

Определим стили для меню навигации, его элементов и переходов при наведении.

.nav {  display: flex;  position: absolute;  left: 50%;  top: 50%;  transform: translate(-50%, -50%);  }.link {  display: flex;  width: 75px;  height: 75px;  margin: 0 5px;  justify-content: center;  align-items: center;}.settings-icon {  display: block;  width: 40px;  height: 40px;}.settings-icon__line {  stroke: var(--color-text);  stroke-width: 5px;  transition: all 0.2s ease 0.05s;}.settings-icon__rect {  stroke: var(--color-text);  fill: var(--color-bg);  stroke-width: 5px;  transition: all 0.2s ease 0.05s;}.link:hover.settings-icon__line,.link:hover.settings-icon__rect {  stroke: var(--color-link);  transition: all 0.2s ease 0.05s;}.link:hover.settings-icon__group--1.settings-icon__rect {  transform: translateY(20px);}.link:hover.settings-icon__group--2.settings-icon__rect {  transform: translateY(-20px);}.link:hover.settings-icon__group--3.settings-icon__rect {  transform: translateY(25px);} 
Пользовательские эффекты курсора

Так должен выглядеть полученный результат.

Включение Paper и SimplexNoise

Нам необходимо подключить Paper.js и Simplex Noise.

<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.12.0/paper-core.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.4.0/simplex-noise.min.js"></script>

Скрытие системного курсора

Мы создаем собственный курсор. Так что необходимо, чтобы системный курсор не выводился.

.page,.page a { cursor: none;}

Анимация маленького точечного курсора

Чтобы обеспечить плавную работу, мы используем цикл requestAnimationFrame().

// задаем начальную позицию курсора за пределами экранаlet clientX = -100;let clientY = -100;const innerCursor = document.querySelector(".cursor--small");const initCursor =() => {  // добавляем прослушиватель для отслеживания текущей позиции мыши document.addEventListener("mousemove", e => {    clientX = e.clientX;    clientY = e.clientY;  });    // преобразуем innerCursor в текущую позицию мыши  // используем requestAnimationFrame() для плавной работы const render =() => {    innerCursor.style.transform = `translate(${clientX}px, ${clientY}px)`;    // если вы  используете TweenMax в проекте, то можете без труда также    // использовать TweenMax.set()    // TweenMax.set(innerCursor, {    //   x: clientX,    //   y: clientY    // });        requestAnimationFrame(render);  };  requestAnimationFrame(render);};initCursor();

Параметр круга на холсте

Ниже приводится код красной части курсора. Чтобы переместить красный круг, мы будем использовать линейную интерполяцию.

let lastX = 0;let lastY = 0;let isStuck = false;let showCursor = false;let group, stuckX, stuckY, fillOuterCursor;const initCanvas =() => {  const canvas = document.querySelector(".cursor--canvas");  const shapeBounds = {    width: 75,    height: 75  };  paper.setup(canvas);  const strokeColor = "rgba(255, 0, 0, 0.5)";  const strokeWidth = 1;  const segments = 8;  const radius = 15;    // это понадобится нам позже для деформированного круга const noiseScale = 150; // скорость const noiseRange = 4; // диапазон деформации let isNoisy = false; // состояние    // базовая фигура для деформированного круга const polygon = new paper.Path.RegularPolygon(    new paper.Point(0, 0),    segments,    radius);  polygon.strokeColor = strokeColor;  polygon.strokeWidth = strokeWidth;  polygon.smooth();  group = new paper.Group([polygon]);  group.applyMatrix = false;    const noiseObjects = polygon.segments.map(() => new SimplexNoise());  let bigCoordinates = [];    // функцию для линейной интерполяции значений const lerp =(a, b, n) => {    return(1 - n) * a + n * b;  };    // функцию для сопоставления значений из одного диапазона с иным const map =(value, in_min, in_max, out_min, out_max) => {    return(((value - in_min) *(out_max - out_min)) /(in_max - in_min) + out_min  );  };    // цикл рисования Paper.js   // (60fps при помощи requestAnimationFrame)  paper.view.onFrame = event => {    // используем линейную интерполяцию, круг сместится на 0.2 (20%)    // от расстояния между текущей позицией и координатами мыши для каждого Frame lastX = lerp(lastX, clientX, 0.2);    lastY = lerp(lastY, clientY, 0.2);    group.position = new paper.Point(lastX, lastY);  }}initCanvas();
Пользовательские эффекты курсора

Пользовательский курсор перемещается по экрану.

Обработка состояния наведения

const initHovers = () => {  // находим центр элемента ссылки и устанавливаем stuckX и stuckY  // это надо, чтобы задать позицию деформированного круга const handleMouseEnter = e => {    const navItem = e.currentTarget;    const navItemBox = navItem.getBoundingClientRect();    stuckX = Math.round(navItemBox.left + navItemBox.width / 2);    stuckY = Math.round(navItemBox.top + navItemBox.height / 2);    isStuck = true;  };    // сбрасываем isStuck к mouseLeave const handleMouseLeave = () => {    isStuck = false;  };    // добавляем ко всем элементам прослушиватели событий const linkItems = document.querySelectorAll(".link");  linkItems.forEach(item => {    item.addEventListener("mouseenter", handleMouseEnter);    item.addEventListener("mouseleave", handleMouseLeave);  });};initHovers();

Деформация круга курсора

Ниже приведен фрагмент расширенной версии упомянутого выше способа paper.view.onFrame.

// цикл рисования Paper.js// (60fps при помощи requestAnimationFrame)paper.view.onFrame = event => {    // используем линейную интерполяцию, круг сместится на 0.2 (20%)    // от расстояния между текущей позицией и координатами мыши для каждого Frame if (!isStuck) {    // переворачиваем круг lastX = lerp(lastX, clientX, 0.2);    lastY = lerp(lastY, clientY, 0.2);    group.position = new paper.Point(lastX, lastY);  } else if (isStuck) {    // фиксированная позиция для элемента навигации lastX = lerp(lastX, stuckX, 0.2);    lastY = lerp(lastY, stuckY, 0.2);    group.position = new paper.Point(lastX, lastY);  }    if (isStuck && polygon.bounds.width < shapeBounds.width) {     // растягиваем фигуру polygon.scale(1.08);  } else if (!isStuck && polygon.bounds.width > 30) {    // удаляем шум if (isNoisy) {      polygon.segments.forEach((segment, i) => {        segment.point.set(bigCoordinates[i][0], bigCoordinates[i][1]);      });      isNoisy = false;      bigCoordinates = [];    }    // сжимаем фигуру const scaleDown = 0.92;    polygon.scale(scaleDown);  }    добавляем простой шум if (isStuck && polygon.bounds.width >= shapeBounds.width) {    isNoisy = true;    // сначала получаем координаты большого круга if (bigCoordinates.length === 0) {      polygon.segments.forEach((segment, i) => {        bigCoordinates[i] = [segment.point.x, segment.point.y];      });    }        // перебираем через цикл все точки многоугольника polygon.segments.forEach((segment, i) => {            // получаем новое значение шума      // делим event.count на noiseScale, чтобы приобрести сглаженное значение const noiseX = noiseObjects[i].noise2D(event.count / noiseScale, 0);      const noiseY = noiseObjects[i].noise2D(event.count / noiseScale, 1);            // сопоставляем значение шума с определенным диапазоном const distortionX = map(noiseX, -1, 1, -noiseRange, noiseRange);      const distortionY = map(noiseY, -1, 1, -noiseRange, noiseRange);            // применяем деформацию для координат const newX = bigCoordinates[i][0] + distortionX;      const newY = bigCoordinates[i][1] + distortionY;            // устанавливаем новые (деформированные) координаты segment.point.set(newX, newY);    });      }  polygon.smooth();};
Пользовательские эффекты курсора

Мы реализовали эффект деформированного круга.

Заключение

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *