Эффект Ripple кнопок Material Design

Эффект Ripple кнопок Material Design

Material Design особенно вдохновляет своим кнопочным компонентом. Он использует эффект Ripple, чтобы дать пользователям обратную связь.

Как работает этот эффект? Кнопки Material Design не просто демонстрируют аккуратную анимацию Ripple, но и меняют свое положение в зависимости от того, где нажата каждая кнопка.

Мы можем достичь того же результата. Начнем с краткого решения с использованием ES6+ JavaScript, прежде чем рассмотрим несколько альтернативных подходов.

HTML

Наша цель-избежать любой посторонней HTML-разметки. Так что мы будем идти с минимумом:

<button>Find out more</button>

Стиль кнопки

Нам нужно будет стилизовать несколько элементов нашей ripple динамически, используя JavaScript. Но все остальное можно сделать в CSS. Для наших кнопок необходимо включить только два свойства.

button {

    position: relative;

    overflow: hidden;

}

Использование position: relative позволяет нам использовать position: absolute на нашем элементе Ripple, который нам нужен для управления его положением. Между тем, overflow: hidden предотвращает выход Ripple за пределы краев кнопки. Все остальное необязательно. Но сейчас наша кнопка выглядит немного старомодно. Вот более современная отправная точка:

/* 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;

}

Стиль Ripple

Позже мы будем использовать JavaScript, чтобы вводить рябь в наш HTML в виде промежутков с классом .ripple. Но прежде чем перейти к JavaScript, давайте определим стиль для этих Ripple в 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%. И чтобы гарантировать, что каждая Ripple возникает из ничего, мы установили шкалу по умолчанию на 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 для динамического задания положения и размера Ripple. Он должен быть основан на размере кнопки, в то время как положение должно основываться как и на положении кнопки, так и на положении курсора.

Мы начнем с пустой функции, которая принимает событие click в качестве аргумента:

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;

Теперь мы можем определить остальные свойства, необходимые для Ripple: 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”);

Перед добавлением нашего элемента span в DOM рекомендуется проверить наличие любых существующих Ripple, которые могут остаться от предыдущих щелчков, и удалить их перед выполнением следующего.

const ripple = button.getElementsByClassName(“ripple”)[0];

if (ripple) {

ripple.remove();

}

В качестве заключительного шага мы добавляем span как дочерний элемент к элементу button, чтобы он был введен внутри кнопки.

button.appendChild(circle);

Когда наша функция завершена, все, что осталось, это вызвать ее. Это можно было бы сделать несколькими способами. Если мы хотим добавить Ripple к каждой кнопке на нашей странице, мы можем использовать что-то вроде этого:

const buttons = document.getElementsByTagName(“button”);

for (const button of buttons) {

button.addEventListener(“click”, createRipple);

}

Что дальше?

Что делать, если мы хотим пойти дальше и объединить этот эффект с другими изменениями положения или размера нашей кнопки? Возможность настройки – это, в конце концов, одно из главных преимуществ, которые мы имеем, решив воссоздать эффект самостоятельно. Чтобы проверить, насколько легко расширить нашу функцию, можно добавить эффект “magnet” (магнит), который заставляет нашу кнопку двигаться к нашему курсору, когда курсор находится в определенной области.

Мы должны полагаться на некоторые из тех же переменных, определенных в функции Ripple. Вместо того чтобы повторять код без необходимости, мы должны хранить их там, где они доступны для обоих методов. Но мы также должны держать общие переменные привязанными к каждой отдельной кнопке.

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

Две важные новые переменные-magneticPullX и magneticPullY. Они контролируют, насколько сильно наш магнит тянет кнопку за курсором. Итак, когда мы определяем центр нашей Ripple, нам нужно настроить как положение новой кнопки (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);

}

Другие методы

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

CSS

Если пользователь отключил JavaScript, эффект Ripple не имеет никаких резервов. Но можно приблизиться к исходному эффекту только с помощью CSS, используя :active pseudo-class  для ответа на клики. Основное ограничение заключается в том, что Ripple может возникать только в одном месте — обычно в центре кнопки — вместо того, чтобы реагировать на положение наших щелчков.

<button class=”ripple”>Button</button>

Pre-ES6 JavaScript

Демо-версия Leandro Parice похожа на нашу реализацию, но совместима с более ранними версиями JavaScript.

<button>Test 1</button>

<button>Test 2</button>

<button>Test 3</button>

<p>Based on GeekLaunch channel video: https://www.youtube.com/watch?v=QI2rDHQM5Pc</p>

jQuery

В этом примере используется jQuery для достижения эффекта Ripple. Если у вас уже есть jQuery в качестве зависимости, это может помочь сэкономить вам несколько строк кода.

<button class=”c-button c-button–purple” type=”button”>

<div class=”c-ripple js-ripple”>

<span class=”c-ripple__circle”></span>

</div>

Material Design Ripple Effect

</button>

<hr class=”o-divider”>

<button class=”c-button c-button–blue” type=”button”>

<div class=”c-ripple js-ripple”>

<span class=”c-ripple__circle”></span>

</div>

Short Button

</button>

<hr class=”o-divider”>

<button class=”c-button c-button–red” type=”button”>

<div class=”c-ripple js-ripple”>

<span class=”c-ripple__circle”></span>

</div>

Long Button to Stress-Test Ripple Animation

</button>

React

Наконец, последний пример. Хотя можно использовать функции React, такие как state и refs, чтобы помочь создать эффект Ripple, они не являются необходимыми. Положение и размер Ripple должны быть рассчитаны для каждого клика, поэтому нет никакого преимущества в том, чтобы держать эту информацию в state. Кроме того, мы можем получить доступ к нашему элементу button из события click, поэтому нам также не нужен refs.

В этом примере React используется функция createRipple, идентичная первой этой статьи. Основное отличие состоит в том, что наша функция ограничена компонентом Button. Кроме того, event listener onClick теперь является частью JSX:

<div id=”root”></div>


.

Брет Камерон Avatar