От автора: когда я впервые открыл для себя Material Design, меня особенно вдохновила его кнопка. Она использует эффект пульсации, чтобы дать пользователям обратную связь простым и элегантным способом.
Как работает этот эффект? Кнопки Material Design не просто демонстрируют аккуратную анимацию пульсации, но и меняют положение в зависимости от того, где нажимается каждая кнопка.
Мы можем добиться того же результата. Мы начнем с краткого решения, использующего ES6 + JavaScript, прежде чем рассматривать несколько альтернативных подходов.
Наша цель — избежать ненужной разметки HTML. Итак, мы возьмем самый минимум:
<button>Find out more</button>
Стилизация кнопки
Нам нужно будет динамически стилизовать несколько элементов с помощью JavaScript. Но все остальное можно сделать в CSS. Для наших кнопок необходимо включить только два свойства.
button { position: relative; overflow: hidden; }
Использование position: relative позволяет нам использовать position: absolute в пульсирующем элементе, который нам нужен для управления его положением. Между тем, overflow: hidden предотвращает выход пульсации за края кнопки. Все остальное необязательно. Но сейчас наша кнопка выглядит немного устаревшей. Вот более современная отправная точка:
/* Roboto is Material's default font */ @import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); button { position: relative; overflow: hidden; transition: background 400ms; color: #fff; background-color: #6200ee; padding: 1rem 2rem; font-family: 'Roboto', sans-serif; font-size: 1.5rem; outline: 0; border: 0; border-radius: 0.25rem; box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.3); cursor: pointer; }
Стилизация пульсации
Позже мы будем использовать JavaScript для добавления пульсации в HTML в виде промежутков с классом .ripple. Но прежде чем перейти к JavaScript, давайте определим стиль для этих пульсаций в CSS, чтобы они были у нас наготове:
span.ripple { position: absolute; /* The absolute position we mentioned earlier */ border-radius: 50%; transform: scale(0); animation: ripple 600ms linear; background-color: rgba(255, 255, 255, 0.7); }
Чтобы сделать волны пульсации круглыми, мы установили border-radius 50%. А чтобы каждая волна возникала из ничего, мы установили масштаб по умолчанию 0. На данный момент мы не сможем увидеть что-либо, потому что у нас еще нет значений для свойств top, left, width, или height; мы скоро добавим эти свойства с помощью JavaScript.
Что касается CSS, последнее, что нам нужно добавить, это конечное состояние для анимации:
@keyframes ripple { to { transform: scale(4); opacity: 0; } }
Обратили внимание, что мы не определяем начальное состояние в ключевых кадрах с помощью ключевого слова from? Мы можем опустить from, и CSS построит недостающие значения на основе тех, которые применяются к анимированному элементу. Это происходит, если соответствующие значения указаны явно — как в transform: scale(0) — или если они используются по умолчанию, например, opacity: 1.
Теперь о JavaScript
Наконец, нам нужен JavaScript для динамической установки положения и размера пульсации. Размер должен основываться на размере кнопки, в то время как позиция должна основываться как на положении кнопки, так и на положении курсора.
Мы начнем с пустой функции, которая принимает событие клика в качестве аргумента:
function createRipple(event) { // }
Мы получим доступ к кнопке, найдя событие currentTarget.
const button = event.currentTarget;
Затем мы создадим экземпляр элемента span и вычислим его диаметр и радиус на основе ширины и высоты кнопки.
const circle = document.createElement("span"); const diameter = Math.max(button.clientWidth, button.clientHeight); const radius = diameter / 2;
Теперь мы можем определить оставшиеся свойства нам нужны для пульсации: left, top, width и height.
circle.style.width = circle.style.height = `${diameter}px`; circle.style.left = `${event.clientX - (button.offsetLeft + radius)}px`; circle.style.top = `${event.clientY - (button.offsetTop + radius)}px`; circle.classList.add("ripple");
Перед добавлением в DOM элемента span рекомендуется проверить любые существующие пульсации, которые могут остаться от предыдущих кликов, и удалить их перед выполнением новых.
const ripple = button.getElementsByClassName("ripple")[0]; if (ripple) { ripple.remove(); }
В качестве последнего шага мы добавляем span в качестве дочернего элемента к элементу кнопки, чтобы он вставлялся внутрь кнопки.
button.appendChild(circle);
Когда наша функция завершена, остается только ее вызвать. Это можно сделать несколькими способами. Если мы хотим добавить пульсацию к каждой кнопке на странице, то можем использовать что-то вроде этого:
const buttons = document.getElementsByTagName("button"); for (const button of buttons) { button.addEventListener("click", createRipple); }
Теперь у нас есть работающий эффект пульсации!
Продолжая
Что, если мы хотим пойти дальше и объединить этот эффект с другими изменениями положения или размера кнопки? В конце концов, возможность настройки — одно из главных преимуществ, которые мы имеем, решив воссоздать эффект самостоятельно. Чтобы проверить, насколько легко расширить нашу функцию, я решил добавить эффект «магнита», который заставляет кнопку перемещаться к курсору, когда курсор находится в определенной области.
Нам нужно полагаться на некоторые из тех же переменных, которые определены в функции пульсации. Вместо того, чтобы без надобности повторять код, мы должны хранить их там, где они доступны для обоих методов. Но мы также должны привязать общие переменные к каждой отдельной кнопке. Один из способов добиться этого — использовать классы, как в примере ниже:
Поскольку эффект магнита должен отслеживать курсор каждый раз, когда он перемещается, нам больше не нужно вычислять положение курсора для создания пульсации. Вместо этого мы можем положиться на cursorX и cursorY.
Две важные новые переменные — это magneticPullX и magneticPullY. Они контролируют, насколько сильно наш метод притягивает кнопку к курсору. Итак, когда мы определяем центр волны, нам нужно отрегулировать как положение новой кнопки (x и y), так и притяжение.
const offsetLeft = this.left + this.x * this.magneticPullX; const offsetTop = this.top + this.y * this.magneticPullY;
Чтобы применить эти комбинированные эффекты ко всем кнопкам, нам нужно создать новый экземпляр класса для каждой:
const buttons = document.getElementsByTagName("button"); for (const button of buttons) { new Button(button); }
Другие техники
Конечно, это только один способ добиться эффекта пульсации. На CodePen есть множество примеров, демонстрирующих различные реализации. Ниже приведены некоторые из моих любимых.
Чистый CSS
Если пользователь отключил JavaScript, наш эффект не имеет резервных вариантов. Но можно приблизиться к исходному эффекту с помощью простого CSS, используя псевдо-класс :active для ответа на клик. Основное ограничение заключается в том, что пульсация может возникать только в одном месте — обычно в центре кнопки — вместо того, чтобы реагировать на положение кликов. Этот пример Бена Сабо особенно краток:
До-ES6 JavaScript
Демонстрация Леандро Париса похожа на нашу, но совместима с более ранними версиями JavaScript:
jQuery
В этом примере используется jQuery. Если у вас уже есть jQuery в качестве зависимости, это может помочь вам сэкономить несколько строк кода.
React
И, наконец, последний пример от меня. Хотя можно использовать такие функции React, как состояние и ссылки, для создания эффекта пульсации, это не является строго необходимым. Положение и размер пульсации необходимо рассчитывать для каждого клика, поэтому нет смысла держать эту информацию в состоянии. Кроме того, мы можем получить доступ к элементу кнопки из события click, поэтому нам тоже не нужны ссылки.
В этом примере React используется функция createRipple, идентичная первой реализации в этой статье. Основное отличие состоит в том, как метод компонента Button привязан к этому компоненту. Кроме того, прослушиватель событий onClick теперь является частью JSX:
Автор: Bret Cameron
Источник: css-tricks.com
Редакция: Команда webformyself.