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 video, berikut versi YouTube dari postingan ini:

Ringkasan

Animasi teks terpisah bisa sangat menarik. Kita hanya akan membahas sedikit potensi animasi dalam postingan ini, tetapi hal ini memberikan dasar untuk membangun animasi. 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 Nesting Draft 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 untuk mengupgrade efek secara kondisional. Kita akan sampai ke bagian berikutnya di mana saya menguraikan JavaScript untuk mengubah {i>string<i} menjadi elemen.

Memisahkan Teks

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

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

Fungsi utilitas pemisahan huruf

Tempat yang menyenangkan untuk memulai adalah dengan 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 pemisahan kata

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 mana yang akan dibagi. Saya melewati ruang kosong, yang menunjukkan pemisahan di antara kata.

Membuat fungsi utilitas box

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 itu bagus, tetapi memiliki indeks untuk digunakan di CSS adalah tambahan yang tampaknya kecil dengan dampak besar. Yang paling penting 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 terpisah yang siap digunakan, menyatukan semuanya berarti:

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

Setelah itu, CSS akan mengambil alih dan akan 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 memberikan gaya dasar yang sama ke semua animasi huruf. Nanti, kita akan menggunakan nilai atribut untuk menambahkan gaya yang lebih spesifik untuk mendapatkan efek.

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

Memisahkan teks di tempat

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 proses:

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. Ganti 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 yang didorong 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 untuk dianimasikan oleh mesin, dan saya memilih tiga status: tanpa kursor, arahkan kursor dalam kalimat, arahkan kursor ke 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;
  }
}

Pisahkan 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;
}
Flexbox devtools menampilkan jarak antar-kata

Contoh kata terpisah 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%);
      }
    }
  }
}

Contoh animasi kata terpisah

Dalam contoh animasi ini, saya menggunakan CSS @keyframes lagi untuk membuat animasi tanpa batas yang berurutan pada paragraf teks biasa.

[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

Sekarang setelah Anda tahu cara saya melakukannya, bagaimana Anda akan melakukannya?! 🙂

Mari kita diversifikasi pendekatan kami dan mempelajari semua cara untuk membangun di web. Buat Codepen atau host demo Anda sendiri, tweet ke saya, dan saya akan menambahkannya ke bagian Remix komunitas di bawah.

Sumber

Demo dan inspirasi lainnya

Remix komunitas