Membuat animasi teks terpisah

Ringkasan dasar tentang cara membuat animasi kata dan huruf terpisah.

Dalam postingan ini, saya ingin membagikan pemikiran tentang cara menyelesaikan animasi dan interaksi teks terpisah untuk web yang minimal, mudah diakses, dan berfungsi di seluruh browser. Coba demo.

Demo

Jika Anda lebih suka menonton video, berikut versi YouTube untuk postingan ini:

Ringkasan

Animasi teks terpisah bisa sangat menarik. Kita tidak akan membahas sebagian besar potensi animasi dalam postingan ini, tetapi hal ini akan memberikan fondasi untuk membangunnya. Tujuannya adalah untuk menganimasikan secara progresif. Teks harus dapat dibaca secara default, dengan animasi yang dibuat di atasnya. Efek gerakan teks terpisah dapat menjadi berlebihan dan berpotensi mengganggu, jadi kita hanya akan memanipulasi HTML, atau menerapkan gaya gerakan jika pengguna tidak keberatan dengan gerakan.

Berikut adalah ringkasan umum alur kerja dan hasilnya:

  1. Siapkan variabel kondisional gerakan yang dikurangi untuk CSS dan JS.
  2. Siapkan utilitas teks terpisah di JavaScript.
  3. Atur kondisional dan utilitas saat pemuatan halaman.
  4. Tulis transisi dan animasi CSS untuk huruf dan kata (bagian yang keren!).

Berikut adalah pratinjau hasil bersyarat yang kita inginkan:

screenshot devtools Chrome dengan panel Elements terbuka dan gerakan yang dikurangi disetel ke 'reduce' dan h1 ditampilkan tanpa pemisahan
Pengguna lebih memilih gerakan yang dikurangi: teks dapat dibaca/tidak terpecah

Jika pengguna memilih gerakan yang dikurangi, kita akan membiarkan dokumen HTML dan tidak melakukan animasi. Jika gerakannya sudah bagus, kita lanjutkan dengan memotongnya menjadi beberapa bagian. Berikut adalah pratinjau HTML setelah JavaScript memisahkan teks menurut huruf.

screenshot devtools Chrome dengan panel Elements terbuka dan gerakan yang dikurangi disetel ke 'reduce' dan h1 ditampilkan tanpa pemisahan
Pengguna tidak keberatan dengan gerakan; teks dibagi menjadi beberapa elemen <span>

Menyiapkan kondisional gerakan

Kueri media @media (prefers-reduced-motion: reduce) yang tersedia dengan mudah akan digunakan dari CSS dan JavaScript dalam project ini. Kueri media ini adalah kondisional utama kami untuk memutuskan apakah akan memisahkan teks atau tidak. Kueri media CSS akan digunakan untuk menahan transisi dan animasi, sedangkan kueri media JavaScript akan digunakan untuk menahan manipulasi HTML.

Menyiapkan kondisional CSS

Saya menggunakan PostCSS untuk mengaktifkan sintaksis Kueri Media Level 5, tempat saya dapat menyimpan boolean kueri media ke dalam variabel:

@custom-media --motionOK (prefers-reduced-motion: no-preference);

Menyiapkan kondisional JS

Di JavaScript, browser menyediakan cara untuk memeriksa kueri media. Saya menggunakan destrukturisasi untuk mengekstrak dan mengganti nama hasil boolean dari pemeriksaan kueri media:

const {matches:motionOK} = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

Kemudian, saya dapat menguji motionOK, dan hanya mengubah dokumen jika pengguna belum meminta untuk mengurangi gerakan.

if (motionOK) {
  // document split manipulations
}

Saya dapat memeriksa nilai yang sama menggunakan PostCSS untuk mengaktifkan sintaksis @nest dari Draf Nest 1. Hal ini memungkinkan saya menyimpan semua logika tentang animasi dan persyaratan gayanya untuk induk dan turunan, di satu tempat:

letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}

Dengan properti kustom PostCSS dan boolean JavaScript, kita siap mengupgrade efek secara bersyarat. Hal ini akan membawa kita ke bagian berikutnya tempat saya memerinci JavaScript untuk mengubah string menjadi elemen.

Memisahkan Teks

Huruf teks, kata, baris, dll. tidak dapat dianimasikan satu per satu dengan CSS atau JS. Untuk mencapai efek ini, kita membutuhkan kotak. Jika kita ingin menganimasikan setiap huruf, setiap huruf harus menjadi elemen. Jika kita ingin menganimasikan setiap kata, setiap kata harus berupa elemen.

  1. Membuat fungsi utilitas JavaScript untuk membagi string menjadi elemen
  2. Mengatur penggunaan utilitas ini

Fungsi utilitas pemisahan huruf

Tempat yang menyenangkan untuk memulai adalah fungsi yang mengambil string dan menampilkan setiap huruf dalam array.

export const byLetter = text =>
  [...text].map(span)

Sintaksis spread dari ES6 benar-benar membantu mempermudah tugas tersebut.

Fungsi utilitas kata pemisahan

Serupa dengan memisahkan huruf, fungsi ini mengambil string dan menampilkan setiap kata dalam array.

export const byWord = text =>
  text.split(' ').map(span)

Metode split() pada string JavaScript memungkinkan kita menentukan karakter yang akan dipotong. Saya melewati ruang kosong, yang menunjukkan pemisahan di antara kata.

Membuat fungsi utilitas kotak

Efek ini memerlukan kotak untuk setiap huruf, dan kita melihat dalam fungsi tersebut, bahwa map() dipanggil dengan fungsi span(). Berikut adalah fungsi span().

const span = (text, index) => {
  const node = document.createElement('span')

  node.textContent = text
  node.style.setProperty('--index', index)

  return node
}

Penting untuk diperhatikan bahwa properti kustom yang disebut --index ditetapkan dengan posisi array. Memiliki kotak untuk animasi huruf memang bagus, tetapi memiliki indeks untuk digunakan dalam CSS merupakan tambahan yang kecil dengan dampak yang besar. Yang paling terkenal dalam dampak besar ini adalah mengejutkan. Kita dapat menggunakan --index sebagai cara untuk mengimbangi animasi agar tampilannya bertahap.

Kesimpulan utilitas

Modul splitting.js dalam penyelesaian:

const span = (text, index) => {
  const node = document.createElement('span')

  node.textContent = text
  node.style.setProperty('--index', index)

  return node
}

export const byLetter = text =>
  [...text].map(span)

export const byWord = text =>
  text.split(' ').map(span)

Berikutnya adalah mengimpor dan menggunakan fungsi byLetter() dan byWord() ini.

Orkestrasi terpisah

Dengan utilitas pemisahan yang siap digunakan, menggabungkan semuanya berarti:

  1. Menemukan elemen yang akan dibagi
  2. Memisahkan dan mengganti teks dengan HTML

Setelah itu, CSS akan mengambil alih dan menganimasikan elemen / kotak.

Menemukan Elemen

Saya memilih untuk menggunakan atribut dan nilai untuk menyimpan informasi tentang animasi yang diinginkan dan cara memisahkan teks. Saya suka memasukkan opsi deklaratif ini ke dalam HTML. Atribut split-by digunakan dari JavaScript, untuk menemukan elemen dan membuat kotak untuk huruf atau kata. Atribut letter-animation atau word-animation digunakan dari CSS, untuk menargetkan turunan elemen dan menerapkan transformasi serta animasi.

Berikut adalah contoh HTML yang menunjukkan kedua atribut tersebut:

<h1 split-by="letter" letter-animation="breath">animated letters</h1>
<h1 split-by="word" word-animation="trampoline">hover the words</h1>

Menemukan elemen dari JavaScript

Saya menggunakan sintaksis pemilih CSS untuk kehadiran atribut guna mengumpulkan daftar elemen yang ingin teksnya dibagi:

const splitTargets = document.querySelectorAll('[split-by]')

Menemukan elemen dari CSS

Saya juga menggunakan pemilih kehadiran atribut di CSS untuk memberi semua animasi huruf gaya dasar yang sama. Nanti, kita akan menggunakan nilai atribut untuk menambahkan gaya yang lebih spesifik untuk mencapai suatu efek.

letter-animation {
  @media (--motionOK) {
    /* animation styles */
  }
}

Memisahkan teks di tempatnya

Untuk setiap target pemisahan yang ditemukan di JavaScript, kita akan membagi teksnya berdasarkan nilai atribut dan memetakan setiap string ke <span>. Kemudian, kita dapat mengganti teks elemen dengan kotak yang kita buat:

splitTargets.forEach(node => {
  const type = node.getAttribute('split-by')
  let nodes = null

  if (type === 'letter') {
    nodes = byLetter(node.innerText)
  }
  else if (type === 'word') {
    nodes = byWord(node.innerText)
  }

  if (nodes) {
    node.firstChild.replaceWith(...nodes)
  }
})

Kesimpulan orkestrasi

index.js dalam penyelesaian:

import {byLetter, byWord} from './splitting.js'

const {matches:motionOK} = window.matchMedia(
  '(prefers-reduced-motion: no-preference)'
)

if (motionOK) {
  const splitTargets = document.querySelectorAll('[split-by]')

  splitTargets.forEach(node => {
    const type = node.getAttribute('split-by')
    let nodes = null

    if (type === 'letter')
      nodes = byLetter(node.innerText)
    else if (type === 'word')
      nodes = byWord(node.innerText)

    if (nodes)
      node.firstChild.replaceWith(...nodes)
  })
}

JavaScript dapat dibaca dalam bahasa Inggris berikut:

  1. Mengimpor beberapa fungsi utilitas bantuan.
  2. Periksa apakah gerakan sudah baik untuk pengguna ini, jika tidak, jangan lakukan apa pun.
  3. Untuk setiap elemen yang ingin dipisah.
    1. Pisahkan berdasarkan cara yang diinginkan.
    2. Mengganti teks dengan elemen.

Memisahkan animasi dan transisi

Manipulasi dokumen pemisahan di atas baru saja membuka banyak potensi animasi dan efek dengan CSS atau JavaScript. Ada beberapa link di bagian bawah artikel ini untuk membantu menginspirasi potensi pemisahan Anda.

Saatnya menunjukkan apa yang dapat Anda lakukan dengan fitur ini. Saya akan membagikan 4 animasi dan transisi berbasis CSS. 🤓

Memisahkan huruf

Sebagai dasar untuk efek huruf terpisah, saya merasa CSS berikut berguna. Saya menempatkan semua transisi dan animasi di balik kueri media gerakan, lalu memberi setiap huruf turunan baru span properti tampilan beserta gaya untuk apa yang harus dilakukan dengan spasi kosong:

[letter-animation] > span {
  display: inline-block;
  white-space: break-spaces;
}

Gaya spasi kosong penting agar span yang hanya berupa spasi, tidak diciutkan oleh mesin tata letak. Sekarang, kita akan membahas hal-hal menyenangkan yang bersifat stateful.

Contoh huruf terpisah transisi

Contoh ini menggunakan transisi CSS ke efek teks terpisah. Dengan transisi, kita memerlukan status agar mesin dapat menganimasikannya, dan saya memilih tiga status: tidak mengarahkan kursor, mengarahkan kursor dalam kalimat, mengarahkan kursor pada huruf.

Saat pengguna mengarahkan kursor ke kalimat, alias penampung, saya menskalakan kembali semua turunan seolah-olah pengguna mendorongnya lebih jauh. Kemudian, saat pengguna mengarahkan kursor ke huruf, saya akan menampilkannya.

@media (--motionOK) {
  [letter-animation="hover"] {
    &:hover > span {
      transform: scale(.75);
    }

    & > span {
      transition: transform .3s ease;
      cursor: pointer;

      &:hover {
        transform: scale(1.25);
      }
    }
  }
}

Contoh animasi huruf terpisah

Contoh ini menggunakan animasi @keyframe yang telah ditentukan untuk menganimasikan setiap huruf tanpa batas, dan memanfaatkan indeks properti kustom inline untuk membuat efek bertingkat.

@media (--motionOK) {
  [letter-animation="breath"] > span {
    animation:
      breath 1200ms ease
      calc(var(--index) * 100 * 1ms)
      infinite alternate;
  }
}

@keyframes breath {
  from {
    animation-timing-function: ease-out;
  }
  to {
    transform: translateY(-5px) scale(1.25);
    text-shadow: 0 0 25px var(--glow-color);
    animation-timing-function: ease-in-out;
  }
}

Memisahkan kata

Flexbox berfungsi sebagai jenis penampung untuk saya di sini dalam contoh ini, yang memanfaatkan unit ch dengan baik sebagai panjang celah yang baik.

word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
DevTools flexbox yang menampilkan celah di antara kata-kata

Contoh kata pemisah transisi

Dalam contoh transisi ini, saya menggunakan kursor lagi. Karena efek awalnya menyembunyikan konten hingga mengarahkan kursor, saya memastikan bahwa interaksi dan gaya hanya diterapkan jika perangkat memiliki kemampuan untuk mengarahkan kursor.

@media (hover) {
  [word-animation="hover"] {
    overflow: hidden;
    overflow: clip;

    & > span {
      transition: transform .3s ease;
      cursor: pointer;

      &:not(:hover) {
        transform: translateY(50%);
      }
    }
  }
}

Menganimasikan contoh kata terpisah

Dalam contoh animasi ini, saya menggunakan @keyframes CSS lagi untuk membuat animasi tanpa batas secara bergiliran pada paragraf teks reguler.

[word-animation="trampoline"] > span {
  display: inline-block;
  transform: translateY(100%);
  animation:
    trampoline 3s ease
    calc(var(--index) * 150 * 1ms)
    infinite alternate;
}

@keyframes trampoline {
  0% {
    transform: translateY(100%);
    animation-timing-function: ease-out;
  }
  50% {
    transform: translateY(0);
    animation-timing-function: ease-in;
  }
}

Kesimpulan

Setelah Anda tahu cara saya melakukannya, bagaimana Anda melakukannya? 🙂

Mari kita diversifikasi pendekatan dan pelajari semua cara untuk mem-build di web. Buat Codepen atau hosting demo Anda sendiri, kirim tweet kepada saya, dan saya akan menambahkannya ke bagian Remix komunitas di bawah ini.

Sumber

Demo dan inspirasi lainnya

Remix komunitas