Membuat animasi teks terpisah

Ringkasan dasar tentang cara membuat animasi huruf dan kata yang terpisah.

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

Demo

Jika Anda lebih suka menonton 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 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 setuju dengan gerakan.

Berikut ringkasan umum alur kerja dan hasilnya:

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

Berikut pratinjau hasil bersyarat yang akan kita buat:

Screenshot Chrome DevTools dengan panel Elements terbuka dan gerakan yang dikurangi disetel ke 'reduce' dan h1 ditampilkan tanpa dibagi
Pengguna lebih memilih gerakan yang dikurangi: teks dapat dibaca / tidak terpisah

Jika pengguna lebih memilih gerakan yang dikurangi, kita tidak mengubah dokumen HTML dan tidak melakukan animasi. Jika gerakan sudah sesuai, kita lanjutkan dengan memotongnya menjadi beberapa bagian. Berikut pratinjau HTML setelah JavaScript memisahkan teks berdasarkan huruf.

Screenshot Chrome DevTools dengan panel Elements terbuka dan gerakan yang dikurangi disetel ke 'reduce' dan h1 ditampilkan tanpa dibagi
Pengguna tidak masalah dengan gerakan; teks dibagi menjadi beberapa elemen <span>

Menyiapkan kondisi gerakan

Kueri media @media (prefers-reduced-motion: reduce) yang tersedia dan mudah digunakan akan digunakan dari CSS dan JavaScript dalam project ini. Kueri media ini adalah kondisi 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 kondisi CSS

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

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

Menyiapkan kondisi JS

Di JavaScript, browser menyediakan cara untuk memeriksa kueri media. Saya menggunakan destructuring 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 Draf 1. Dengan begitu, saya dapat menyimpan semua logika tentang animasi dan persyaratan gaya 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. Hal ini akan membawa kita ke bagian berikutnya tempat saya menguraikan JavaScript untuk mengubah string menjadi elemen.

Memisahkan Teks

Huruf, kata, baris, dll., teks tidak dapat dianimasikan satu per satu dengan CSS atau JS. Untuk mendapatkan efek tersebut, kita memerlukan kotak. Jika kita ingin menganimasikan setiap huruf, maka setiap huruf harus menjadi 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 sangat membantu mempercepat tugas tersebut.

Fungsi utilitas pemisahan kata

Mirip 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 dipotong. Saya melewati ruang kosong, yang menunjukkan pemisahan antar-kata.

Fungsi utilitas kotak pembuatan

Efeknya 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 bernama --index sedang disetel dengan posisi array. Memiliki kotak untuk animasi huruf sangat bagus, tetapi memiliki indeks untuk digunakan di CSS adalah tambahan yang tampaknya kecil dengan dampak yang besar. Dampak besar yang paling terlihat adalah mengejutkan. Kita dapat menggunakan --index sebagai cara untuk mengimbangi animasi agar terlihat 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)

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

Orkestrasi pemisahan

Dengan utilitas pemisahan 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 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 menempatkan 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 elemen turunan 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 memisahkan teksnya:

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

Menemukan elemen dari CSS

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

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

Memisahkan teks di tempat

Untuk setiap target pemisahan yang kami temukan di JavaScript, kami akan memisahkan 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 setelah selesai:

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. Impor beberapa fungsi utilitas pembantu.
  2. Periksa apakah gerakan sudah sesuai untuk pengguna ini, jika tidak, jangan lakukan apa pun.
  3. Untuk setiap elemen yang ingin dibagi.
    1. Pisahkan berdasarkan cara yang mereka inginkan.
    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 yang didorong CSS. 🤓

Huruf terpisah

Sebagai dasar untuk efek huruf terpisah, saya menemukan CSS berikut berguna. Saya menempatkan semua transisi dan animasi di belakang kueri media gerakan, lalu memberikan properti tampilan dan gaya untuk setiap huruf turunan baru span untuk menentukan apa yang harus dilakukan dengan ruang kosong:

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

Gaya ruang putih penting agar rentang yang hanya berupa spasi, tidak diciutkan oleh mesin tata letak. Sekarang kita masuk ke bagian yang menyenangkan dan stateful.

Contoh huruf terpisah transisi

Contoh ini menggunakan transisi CSS untuk efek teks terpisah. Dengan transisi, kita memerlukan status agar mesin dapat membuat animasi di antaranya, dan saya memilih tiga status: tidak ada kursor, kursor berada di dalam kalimat, kursor berada di huruf.

Saat pengguna mengarahkan kursor ke kalimat, alias penampung, saya akan mengecilkan semua elemen turunan seolah-olah pengguna mendorongnya lebih jauh. Kemudian, saat pengguna mengarahkan kursor ke huruf, saya akan memajukannya.

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

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

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

Contoh huruf terpisah yang dianimasikan

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

@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 bagi saya di sini dalam contoh ini, dengan memanfaatkan unit ch sebagai panjang jarak yang tepat.

word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
Flexbox devtools yang menampilkan jarak antar-kata

Contoh kata pemisahan transisi

Dalam contoh transisi ini, saya menggunakan pengarahan kursor lagi. Karena efek awalnya menyembunyikan konten hingga kursor diarahkan, 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 kata yang dipisah dan dianimasikan

Dalam contoh animasi ini, saya menggunakan @keyframes CSS lagi untuk membuat animasi tak terbatas yang bertahap 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?! 🙂

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

Sumber

Lebih banyak demo dan inspirasi

Remix komunitas