DevGang
Авторизоваться

Создание анимации скремблирования текста с помощью JavaScript

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

Начало работы с HTML

Для начала мы можем определить структуру нашего навигационного меню в HTML. Мы создадим элемент <nav>, содержащий список ссылок. А для стилизации мы воспользуемся Tailwind CSS:

<nav class="text-sm flex flex-col space-y-2" role="navigation">
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5">General</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5">Work Preferences</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5">Integrations</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5">Billing</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5">Subscription</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5">Security</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5">Data Sources</a>
</nav>

Создание JS-класса для анимации

Мы создадим класс JavaScript, как мы это делали в наших руководствах. Таким образом, мы можем легко применить анимацию к различным элементам страницы. Итак, создайте файл text-scramble.js и включите его в HTML-документ непосредственно перед закрывающим тегом </body>:

<script src="./text-scramble.js"></script>

Теперь в файле JS мы создадим класс TextScramble. Внутри этого класса будет метод init, который инициализирует анимацию:

class TextScramble {
  constructor(element) {
    this.element = element;
    this.links = Array.from(this.element.querySelectorAll('a'));
    this.init();
  }

  init = () => {
    this.links.forEach(link => {
      link.addEventListener('click', (event) => {
        event.preventDefault();
        // Run the scramble animation
      });
    });
  }
}

// Init TextScramble
const scrambleElements = document.querySelectorAll('[data-text-scramble]');
scrambleElements.forEach((element) => {
  new TextScramble(element);
});

Чтобы использовать класс TextScramble, просто добавьте атрибут data-text-scramble в меню навигации:

<nav class="text-sm flex flex-col space-y-2" role="navigation" data-text-scramble>

Затем класс выбирает все ссылки внутри <nav> и запускает анимацию при нажатии каждой ссылки.

Управление активной ссылкой

Теперь в нашем HTML отметьте первую ссылку атрибутом aria-current="page". Этот атрибут сообщает браузеру, какая ссылка в данный момент «активна», и позволяет применять определенные стили, чтобы отличать ее от других ссылок.

<nav class="text-sm flex flex-col space-y-2" role="navigation">
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5 aria-[current]:text-indigo-500 before:absolute before:top-0 before:-left-5 before:h-full before:w-0.5 aria-[current]:before:bg-indigo-400" aria-current="page">General</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5 aria-[current]:text-indigo-500 before:absolute before:top-0 before:-left-5 before:h-full before:w-0.5 aria-[current]:before:bg-indigo-400">Work Preferences</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5 aria-[current]:text-indigo-500 before:absolute before:top-0 before:-left-5 before:h-full before:w-0.5 aria-[current]:before:bg-indigo-400">Integrations</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5 aria-[current]:text-indigo-500 before:absolute before:top-0 before:-left-5 before:h-full before:w-0.5 aria-[current]:before:bg-indigo-400">Billing</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5 aria-[current]:text-indigo-500 before:absolute before:top-0 before:-left-5 before:h-full before:w-0.5 aria-[current]:before:bg-indigo-400">Subscription</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5 aria-[current]:text-indigo-500 before:absolute before:top-0 before:-left-5 before:h-full before:w-0.5 aria-[current]:before:bg-indigo-400">Security</a>
    <a href="#0" class="relative font-medium text-slate-500 hover:text-slate-600 py-0.5 aria-[current]:text-indigo-500 before:absolute before:top-0 before:-left-5 before:h-full before:w-0.5 aria-[current]:before:bg-indigo-400">Data Sources</a>
</nav>

Чтобы сделать активную ссылку более заметной, мы использовали префикс aria-[current]:, чтобы придать ей другой цвет (aria-[current]:text-indigo-500). Мы также добавили вертикальную линию слева от каждой ссылки, используя псевдоэлемент: :before. По умолчанию эта строка прозрачна, но она становится синего цвета, когда ссылка активна (aria-[current]:before:bg-indigo-400).

Прекрасно! Теперь давайте позаботимся об активной ссылке во время навигации.

class TextScramble {
  constructor(element) {
    this.element = element;
    this.links = Array.from(this.element.querySelectorAll('a'));
    this.activeLink = this.links.find(link => link.getAttribute('aria-current') === 'page');
    this.init();
  }

  handleActiveLink = (link) => {
    if (this.activeLink) {
      this.activeLink.removeAttribute('aria-current');
    }
    this.activeLink = link;
    this.activeLink.setAttribute('aria-current', 'page');
  }  

  init = () => {
    this.links.forEach(link => {
      link.addEventListener('click', (event) => {
        event.preventDefault();
        if (link === this.activeLink) return;
        this.handleActiveLink(link);
      });
    });
  }
}

// Init TextScramble
const scrambleElements = document.querySelectorAll('[data-text-scramble]');
scrambleElements.forEach((element) => {
  new TextScramble(element);
});

Ключевые шаги, которые мы предприняли, заключаются в следующем:

  • Мы добавили в класс свойство activeLink, которое находит активную ссылку в массиве ссылок.
  • Затем мы создали метод handleActiveLink, задачей которого является обновление активной ссылки при каждом нажатии ссылки меню. Он удаляет атрибут aria-current из активной ссылки, устанавливает новую активную ссылку и присваивает ей атрибут aria-current="page".
  • Наконец, мы добавили условие внутри метода init, чтобы проверить, активна ли уже выбранная ссылка — if (link === this.activeLink). Если это так, выполнение метода init останавливается с возвратом. В противном случае мы вызываем метод handleActiveLink для обновления активной ссылки.

Обработка эффекта скремблирования

Теперь начинается более сложная часть. Мы представим пару новых свойств и методов для обработки эффекта перемешивания.

class TextScramble {
  constructor(element) {
    this.element = element;
    this.links = Array.from(this.element.querySelectorAll('a'));
    this.activeLink = this.links.find(link => link.getAttribute('aria-current') === 'page');
    this.isScrambling = false;
    this.intervalId = null;    
    this.init();
  }

  handleActiveLink = (link) => {
    if (this.activeLink) {
      this.activeLink.removeAttribute('aria-current');
    }
    this.activeLink = link;
    this.activeLink.setAttribute('aria-current', 'page');
  }
  
  startScramble = (link) => {
    if (this.isScrambling) {
      this.stopScramble(this.activeLink);
    }
    this.isScrambling = true;
    this.intervalId = setInterval(() => {
      // Do stuff...
      console.log('Scrambling...');
    }, 50);
  }
  
  stopScramble = (link) => {
    this.isScrambling = false;
    clearInterval(this.intervalId);
  }  

  init = () => {
    this.links.forEach(link => {
      link.addEventListener('click', (event) => {
        event.preventDefault();
        if (link === this.activeLink) return;
        this.startScramble(link);
        this.handleActiveLink(link);
      });
    });
  }
}

// Init TextScramble
const scrambleElements = document.querySelectorAll('[data-text-scramble]');
scrambleElements.forEach((element) => {
  new TextScramble(element);
});

Начнем с недавно добавленных свойств:

  • Логическое свойство isScrambling указывает, выполняется ли в данный момент анимация. По умолчанию для него установлено значение false, и оно меняется на true при запуске анимации. Это свойство полезно для остановки текущей анимации при нажатии другой ссылки.
  • Переменная intervalId используется для хранения идентификатора интервала, используемого для анимации. Сохранив идентификатор интервала, мы можем позже очистить интервал, используя clearInterval(this.intervalId), когда анимация будет завершена или прервана.

Теперь новые методы:

  • Метод startScramble отвечает за инициацию анимации. Он вызывается при нажатии ссылки меню. Этот метод сначала проверяет, выполняется ли анимация, и при необходимости останавливает ее. Затем он устанавливает для свойства isScrambling значение true и запускает временной интервал, который выполняет функцию каждые 50 миллисекунд — в настоящее время эта функция пуста, но мы вскоре ее заполним.
  • Метод stopScramble используется для остановки анимации. Он вызывается, когда анимация завершается или прерывается. Этот метод устанавливает для свойства isScrambling значение false и останавливает временной интервал с помощью метода cleInterval(this.intervalId).

Обработка префикса и суффикса

Сначала мы создадим свойство под названием this.input; он будет хранить текст ссылки, которая будет анимирована.

Далее мы добавим еще два свойства: this.prefix и this.suffix. Эти свойства будут использоваться для замены текста ссылки во время анимации.

Давайте рассмотрим практический пример, чтобы понять, как это работает. Предположим, вы нажимаете на ссылку с текстом clearInterval(this.intervalId).

Анимация пройдет следующие итерации:

  • «»(this.prefix) + «spfjert» (this.suffix)
  • «G» (this.prefix) + «pfjfqr» (this.suffix)
  • «Ge» (this.prefix) + «jdqll» (this.suffix)
  • «Gen» (this.prefix) + «rmnb» (this.suffix)
  • «Gene» (this.prefix) + «swt» (this.suffix)
  • «Gener» (this.prefix) + «oe» (this.suffix)
  • «Genera» (this.prefix) + «z» (this.suffix)
  • «General» (this.prefix) + «» (this.suffix)

Вначале префикс представляет собой пустую строку, а суффикс состоит из случайных символов. На каждой итерации префикс будет таким же, как первая буква this.input, а суффикс будет содержать все последующие символы. Этот шаблон продолжается до тех пор, пока префикс не будет соответствовать this.input, а суффикс не станет пустой строкой.

Теперь давайте посмотрим, как мы можем интегрировать эту логику в наш класс.

class TextScramble {
  constructor(element) {
    this.element = element;
    this.links = Array.from(this.element.querySelectorAll('a'));
    this.activeLink = this.links.find(link => link.getAttribute('aria-current') === 'page');
    this.input = null;
    this.prefix = '';
    this.suffix = '';
    this.isScrambling = false;
    this.intervalId = null;    
    this.init();
  }

  handleActiveLink = (link) => {
    if (this.activeLink) {
      this.activeLink.removeAttribute('aria-current');
    }
    this.activeLink = link;
    this.activeLink.setAttribute('aria-current', 'page');
  }
  
  startScramble = (link) => {
    if (this.isScrambling) {
      this.stopScramble(this.activeLink);
    }
    this.isScrambling = true;
    this.input = link.textContent;
    this.prefix = '';
    this.suffix = 'xxx';    
    this.intervalId = setInterval(() => this.scrambleIteration(link), 50);
  }

  scrambleIteration = (link) => {
    let nextChar = this.input.charAt(this.prefix.length);
    if (nextChar === '') {
      this.stopScramble(link);
    } else {
      this.prefix += nextChar;
      this.suffix = 'xxx';
      link.textContent = this.prefix;
      link.setAttribute('data-scramble-suffix', this.suffix);
    }
  }  
  
  stopScramble = (link) => {
    link.textContent = this.input;
    link.setAttribute('data-scramble-suffix', '');
    this.isScrambling = false;
    clearInterval(this.intervalId);
  }

  init = () => {
    this.links.forEach(link => {
      link.addEventListener('click', (event) => {
        event.preventDefault();
        if (link === this.activeLink) return;
        this.startScramble(link);
        this.handleActiveLink(link);
      });
    });
  }
}

// Init TextScramble
const scrambleElements = document.querySelectorAll('[data-text-scramble]');
scrambleElements.forEach((element) => {
  new TextScramble(element);
});

Помимо добавления this.input, this.prefix и this.suffix, мы также интегрировали методы startScramble и stopScramble для обновления значений this.prefix и this.suffix на каждой итерации.

Более того, во время скремблирования метод scrambleIteration добавляет к ссылке атрибут data-scramble-suffix, который содержит значение свойства this.suffix. Этот атрибут будет использоваться для отображения суффикса с использованием псевдоэлемента ::after ссылки. В Tailwind все, что вам нужно сделать, это добавить класс after:content-[attr(data-scramble-suffix)] ко всем ссылкам меню.

Генерация случайных символов

Последний шаг — сгенерировать случайные символы соответствующей длины для суффикса. Для этого мы создадим метод под названием randomChars, возвращающий строку случайных букв:

randomChars = (length) => {
  let result = '';
  for (let i = 0; i < length; i++) {
    const randomIndex = Math.floor(Math.random() * this.charsetLength);
    result += `${this.charset[randomIndex]}`;
  }
  return result;
}

Теперь нам просто нужно использовать этот метод в методах startScramble и scrambleIteration. Итак, окончательный код класса будет выглядеть так:

class TextScramble {
  constructor(element) {
    this.element = element;
    this.links = Array.from(this.element.querySelectorAll('a'));
    this.activeLink = this.links.find(link => link.getAttribute('aria-current') === 'page');
    this.input = null;
    this.prefix = '';
    this.suffix = '';
    this.isScrambling = false;
    this.intervalId = null;
    this.charset = 'abcdefghijklmnopqrstuvwxyz';
    this.charsetLength = this.charset.length;
    this.scrambleIteration = this.scrambleIteration.bind(this);
    this.init();
  }

  handleActiveLink = (link) => {
    if (this.activeLink) {
      this.activeLink.removeAttribute('aria-current');
    }
    this.activeLink = link;
    this.activeLink.setAttribute('aria-current', 'page');
  }

  startScramble = (link) => {
    if (this.isScrambling) {
      this.stopScramble(this.activeLink);
    }
    this.isScrambling = true;
    this.input = link.textContent;
    this.prefix = '';
    this.suffix = this.randomChars(this.input.length);
    this.intervalId = setInterval(() => this.scrambleIteration(link), 50);
  }

  scrambleIteration = (link) => {
    let nextChar = this.input.charAt(this.prefix.length);
    if (nextChar === '') {
      this.stopScramble(link);
    } else {
      this.prefix += nextChar;
      this.suffix = this.randomChars(this.input.length - this.prefix.length);
      link.textContent = this.prefix;
      link.setAttribute('data-scramble-suffix', this.suffix);
    }
  }

  stopScramble = (link) => {
    link.textContent = this.input;
    link.setAttribute('data-scramble-suffix', '');
    this.isScrambling = false;
    clearInterval(this.intervalId);
  }

  randomChars = (length) => {
    let result = '';
    for (let i = 0; i < length; i++) {
      const randomIndex = Math.floor(Math.random() * this.charsetLength);
      result += `${this.charset[randomIndex]}`;
    }
    return result;
  }

  init = () => {
    this.links.forEach(link => {
      link.addEventListener('click', (event) => {
        event.preventDefault();
        if (link === this.activeLink) return;
        this.startScramble(link);
        this.handleActiveLink(link);
      });
    });
  }
}

// Init TextScramble
const scrambleElements = document.querySelectorAll('[data-text-scramble]');
scrambleElements.forEach((element) => {
  new TextScramble(element);
});

https://cruip-tutorials.vercel.app/text-scramble/

Заключение

Создание привлекательных эффектов и анимации, которые не ставят под угрозу удобство использования веб-сайта, может оказаться сложной задачей. В этом уроке мы постарались добавить немного веселья во взаимодействие с меню, не ухудшив при этом общее впечатление. Достигли ли мы своей цели? Свяжитесь с нами и дайте нам знать, что вы думаете!

Источник:

#JavaScript #HTML
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу