Превью для статті - Як анімувати SVG підпис

Як анімувати SVG підпис

Давайте подивімось, як можна анімувати SVG-підпис за допомогою пари десятків рядків JavaScript та CSS. У цій демонстрації я навчу вас використовувати JS-функцію для обчислення довжини SVG-шляху (path) та CSS-анімацію для анімування SVG-штрихів (stroke).

Готовий результат можна побачити на моєму CodePen.

SVG-підпис

Почнемо зі створення SVG-підпису. Я використовую Sketch, щоб намалювати SVG-шляхи для літер.

Ось як це виглядає в режимі редагування:

Приклад векторного зображення підпису в Sketch

Наразі підпис містить 8 шляхів (paths) для різних частин підпису. Наприклад, один шлях відповідає літері «S», інший - крапці і так далі.
Після експорту SVG зі Sketch, я змінив послідовність шляхів так, щоб вони зʼявлялися саме в тому порядку, у якому мають анімуватися. Спершу літера “S”, потім крапка, потім літера “B” і так далі. Також я додав класи autograph та autograph__path для більш зручної Css-стилізації та маніпуляцій за допомогою JS.

Ось фінальний файл підпису:

html
<svg class="autograph" height="103" viewBox="0 0 424 103" width="424" xmlns="http://www.w3.org/2000/svg">
  <g fill="none" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" transform="translate(2 2)">
    <path class="autograph__path"
    d="m52.9053119 30.5944651c4.4979146-5.981029 6.3597765-11.3263744 5.5855855-16.0360361-2.1621621-13.15315311-10.9863929-13.558429-15.4908974-13.558429-10.0900901 0-20 7.99987048-20.7253188 13.558429-3.0543891 23.4076405 31.8852386 28.5472234 30.7253188 52.441571-.9009009 18.5585586-12.9740033 27.7974004-25 29-12.6126126 1.2612613-28.33708674-6.8576105-27-18 .48048048-4.004004 5.81381381-7.6706707 16-11" />
    <path class="autograph__path"
    d="m61.9059048 93.3815042c1.7863267-3.215388-4.287184-4.6444494-4.6444494-1.4290614-.3572653 2.143592 2.8581227 4.6444494 4.6444494 1.4290614z" />
    <path class="autograph__path" d="m102 1c-1.633522 27.1862259-3.3001883 44.8528926-5 53s-7.033145 22.1471074-16 42" />
    <path class="autograph__path"
    d="m100.288288 6.6036036c4.152508-3.963964 8.176532-5.945946 12.072072-5.945946 11.531532 0 17.63964 3.3423424 16.63964 14.3423424 0 13-12.06006 22.3543543-32 28 21.430945-5.8755744 31.400915 1.8721733 29.90991 23.2432432-1.081081 15.4954958-25.606607 24.6336338-48.90991 27.7567568 18.3783784-31.111111 44.546547-46.5975976 58-49 7.684578-1.3722461.201094 23.4415679-4 51 9.626744-23.6648293 10.754389-40.1268415 26.472991-48.7743243 3.062782-1.6849689 5.899902-2.8000991 9.527009-2.2256757 2.58117.4087788 5.287656.9554275 10 6-7-8-16.06468-6.1122883-19.527009-1.6121622-10.472991 13.6121622 15.527009 13.6121622 16.527009 29.6121622.666667 10.6666667-5.666667 16.3333333-19 17-16.664879 2.1642702 8.261928 6.47368 29-21 9.19448-12.1807945 18.19448-36.8474612 27-74-21.276288 33.987688-24.609621 65.6543547-10 95 21.886044-29.4751082 26.735893-49.2438774 35-51 9.609609-2.042042 6.726727 21.4504503 0 51 4.444444-39.2792794 18.42042-58.8078078 38-51-8.828829 2.1621621-15.315998 15.4560012-16.711712 28.1171176-1.126809 10.2217665 4.625073 22.8828824 11.711712 22.8828824 12.252252 0 19.279279-10.0010654 19.279279-20.5405401 0-23.0630631-9.234234-30.4594599-12.477477-30.4594599 12.432432 3.9639639 30.297297-6.8468469 31.198198 0 2.644012 20.0944903-19 51 5 51 22 0 19.936937-49.1981982 23-51 2.342343 8.6486486 18.480018-2.1232717 18 0-3.570281 15.7924726-11.376494 44.5410656-10 51 4.504505-13.1531532 17-47 45.900901-51.1711712 6.669031-.9625192 8.255066 5.4033441 3.963964 18.5585586 4.204205-13.3333334 2.882883-19.5195196-3.963964-18.5585586-13.059803 1.8329549-22.530745 8.0837151-28.69016 31.4759854-1.237235 12.5179014 1.692518 19.0829633 8.789259 19.6951858 4.616353.3982441 10.995909-1.7223734 19.138668-6.3618524" />
    <path class="autograph__path" d="m178 30c11-4 54 6 73.734117 1.8554251" />
    <path class="autograph__path" d="m419 1-21 27.333" />
    <path class="autograph__path"
    d="m140.729905 31.3135042c1.786326-3.215388-4.287184-4.6444494-4.64445-1.4290614-.357265 2.1435921 2.858123 4.6444494 4.64445 1.4290614z" />
    <path class="autograph__path"
    d="m360.729905 31.3135042c1.786326-3.215388-4.287184-4.6444494-4.64445-1.4290614-.357265 2.1435921 2.858123 4.6444494 4.64445 1.4290614z" />
  </g>
</svg>

Дуже важливо створювати SVG із контурами (strokes), оскільки саме їм ми будемо додавати анімацію. Кожен шлях або контур має свою довжину і ми використуємо це в нашій анімації.

JavaScript-обчислення

Щоб обчислити довжину контуру, можна використати приблизне вгадування 😁. У цьому випадку потрібно задіяти два dash-атрибути:

  • stroke-dasharray - робить контур пунктирним;
  • stroke-dashoffset - зміщує позицію пунктиру за межі видимого шляху;

Ці дві властивості повинні бути рівні одна одної для того, щоб усе працювало як слід.

Але ми - програмісти, і нам не потрібно вгадувати, тож давайте обчислимо це програмно за допомогою JS.

Ось концепція того, як це має працювати:

  • Спочатку задаємо загальну бажану тривалість анімації;
  • Потім використовуємо функцію getTotalLength() для обчислення загальної довжини всіх шляхів разом;
  • Далі розраховуємо тривалість анімації та затримки для кожного шляху пропорційно відношенню їхньої довжини до загальної;
  • Потім встановлюємо stroke-dasharray та stroke-dashoffset, щоб розташувати пунктирний контур поза межами видимого шляху;
  • Нарешті запускаємо анімацію, додавши до body клас animated;

Ось повна JavaScript-функція (з коментарями для легшого розуміння):

javascript
const calcPaths = (totalDur) => {
  document.body.classList.add('animated')

  // знаходимо всі шляхи SVG
  const paths = document.querySelectorAll('.autograph__path')

  // змінна для довжини всіх шляхів
  let len = 0

  // змінна для затримки анімації
  let delay = 0

  // перервати виконання, якщо елементів не знайдено
  if (!paths.length) {
    return false
  }

  // встановлюємо тривалість анімації за замовчуванням, якщо не задано (в секундах)
  const totalDuration = totalDur || 7

  // обчислюємо загальну довжину всіх шляхів
  paths.forEach((path) => {
    const totalLen = path.getTotalLength()
    len += totalLen
  })

  paths.forEach((path) => {
    const pathElem = path

    // довжина поточного шляху
    const totalLen = path.getTotalLength()

    // тривалість анімації для поточного шляху
    const duration = totalLen / len * totalDuration

    // тривалість анімації і затримка
    pathElem.style.animationDuration = `${duration < 0.2 ? 0.2 : duration}s`
    pathElem.style.animationDelay = `${delay}s`

    // встановлюємо dash-array і dash-offset відповідно до довжини шляху - так ховається лінія
    pathElem.setAttribute('stroke-dasharray', totalLen)
    pathElem.setAttribute('stroke-dashoffset', totalLen)

    // встановлюємо затримку для наступного шляху - додано 0.2 секунди для більш реалістичного ефекту
    delay += duration + 0.2
  })

  // додаємо клас 'animated' до body, щоб запустити анімацію
  document.body.classList.add('animated')
  return true
}
calcPaths(5)

‼️ Обов’язково перевірте сумісність із браузерами, перш ніж це використовувати.

Css-анімація

Css-анімація полягає в тому, щоб встановити кінцеве значення stroke-dashoffset назад у нуль.
Я використав linear для animation-timing-function та forwards для animation-fill-mode.
Перша властивість робить анімацію лінійною, що, на мою думку, виглядає найприродніше.
Друга - змушує анімацію залишатися у фінальному стані (100%).

css
.autograph__path {
  opacity: 0;
  animation-timing-function: linear;
  animation-fill-mode: forwards;

  .animated & {
    opacity: 1;
    animation-name: line;
  }
}

@keyframes line {
  100% {
    stroke-dashoffset: 0;
  }
}

Я використав opacity, щоб запобігти мерехтінню під час завантаження. Це працює краще в поєднанні з Critical Css.

Висновок

Ось і все, ми анімували SVG-підпис. Маючи трохи знань про SVG, JavaScript і CSS, ви можете створювати привабливі анімації малювання. В інтернеті є плагіни котрі створюють подібні анімації, але я завжди надаю перевагу меншому обсягу коду. Ця демонстрація зроблена менш ніж у 100 рядках коду.

Повне демо можна переглянути тут - CodePen