יצירת אנימציות של טקסט מפוצל

סקירה בסיסית על בניית אנימציות של אותיות ומילים מפוצלות.

בפוסט הזה אני רוצה לשתף איתכם דרכי חשיבה על דרכים לפתרון אנימציות טקסט מפוצלות ואינטראקציות של האינטרנט הן מינימליות, נגישות ועובדות בדפדפנים שונים. אפשר לנסות את ההדגמה.

הדגמה

אם ברצונך ליצור סרטון, הנה גרסת YouTube של הפוסט הזה:

סקירה כללית

אנימציות טקסט מפוצלות יכולות להיות מדהימות. בפוסט הזה כמעט לא נגיד את השטח של פוטנציאל האנימציה, אבל הוא מספק יסודות לבנות. המטרה היא ליצור אנימציה בהדרגה. הטקסט צריך להיות קריא כברירת מחדל, והאנימציה בנויה מעליו. אפקטים של תנועת טקסט מפוצלת יכולים להיות ראוותניים ועשויים להפריע, לכן נתעסק ב-HTML או נחיל סגנונות תנועה אם התנועה נגרמה למשתמש.

הנה סקירה כללית של תהליך העבודה והתוצאות:

  1. הכינו משתנים מותנים של תנועה מופחתת עבור CSS ו-JS.
  2. הכינו פיצולי שירותים לטקסט ב-JavaScript.
  3. ארגון של התניות והכלים בטעינת הדף.
  4. כתיבת מעברי CSS ואנימציות לאותיות ולמילים (החלק של ה-Rad!).

זוהי תצוגה מקדימה של התוצאות המותנות שאנחנו מתכוונים להשיג:

צילום מסך של כלי הפיתוח של Chrome כשחלונית הרכיבים פתוחה והתנועה המוקטנת מוגדרת ל 'הקטנה', וה-h1 מוצג ללא פיצול
המשתמש מעדיף לצמצם את התנועה: הטקסט קריא / לא פוצל

אם משתמש מעדיף צמצום תנועה, אנחנו משאירים את מסמך ה-HTML ללא שינוי ולא יוצרים אנימציה. אם התנועה תקינה, חותכים אותה לחתיכות. זוהי תצוגה מקדימה של ה-HTML לאחר פיצול הטקסט לפי אות ב-JavaScript.

צילום מסך של כלי הפיתוח של Chrome כשחלונית הרכיבים פתוחה והתנועה המוקטנת מוגדרת ל 'הקטנה', וה-h1 מוצג ללא פיצול
המשתמש מסכים עם התנועה; הטקסט מחולק למספר רכיבי <span>

המערכת מכינה את תנאי התנועה

בפרויקט הזה המערכת תשתמש בשאילתת המדיה @media (prefers-reduced-motion: reduce) הזמינה באופן נוח ל-CSS ול-JavaScript. שאילתת המדיה הזו היא המותנה העיקרית שלנו להחלטה אם לפצל טקסט או לא. שאילתת המדיה של ה-CSS תשמש להשהיית מעברים ואנימציות, ואילו שאילתת המדיה של JavaScript תשמש כדי למנוע את השינוי ב-HTML.

הכנת התנאי המותנה ב-CSS

השתמשתי ב-PostCSS כדי להפעיל את התחביר של Media queries Level 5, שבו אפשר לאחסן ערך בוליאני של שאילתת מדיה למשתנה:

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

הכנת ה-JS המותנה

ב-JavaScript, הדפדפן מספק דרך לבדוק שאילתות מדיה, השתמשתי בהשמדה כדי לחלץ את התוצאה הבוליאנית ולשנות את שמה מבדיקת שאילתת המדיה:

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

עכשיו אפשר לבדוק את motionOK ולשנות את המסמך רק אם המשתמש לא ביקש להקטין את התנועה.

if (motionOK) {
  // document split manipulations
}

אפשר לבדוק את אותו הערך באמצעות PostCSS כדי להפעיל את התחביר @nest מטיוטה 1 של Nesting. כך אוכל לאחסן במקום אחד את כל הלוגיקה לגבי האנימציה ואת דרישות הסגנון שלה להורים ולילדים:

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

עם המאפיין המותאם אישית של PostCSS וערך הבוליאני של JavaScript, אנחנו מוכנים לשדרג את האפקט באופן מותנה. זה מכניס אותנו לקטע הבא שבו אני מפרט את ה-JavaScript כדי להמיר מחרוזות לרכיבים.

פיצול טקסט

לא ניתן להוסיף אנימציה של אותיות טקסט, מילים, שורות וכו' בנפרד באמצעות CSS או JS. כדי להשיג את האפקט, אנחנו צריכים תיבות. אם אנחנו רוצים להוסיף אנימציה לכל אות, אז כל אות צריכה להיות רכיב. אם אנחנו רוצים להוסיף אנימציה לכל מילה, כל מילה צריכה להיות רכיב.

  1. יצירת פונקציות עזר של JavaScript לפיצול מחרוזות לרכיבים
  2. תזמור את השימוש בכלים אלו

פונקציית פיצול של אותיות

כיף להתחיל לעבוד עם פונקציה שמקבלת מחרוזת ומחזירה כל אות במערך.

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

תחביר התפוצה מ-ES6 עזר להפוך את המשימה הזו למשימה מהירה.

פונקציית פיצול מילים

בדומה לפיצול אותיות, הפונקציה הזו לוקחת מחרוזת ומחזירה כל מילה במערך.

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

השיטה split() במחרוזות JavaScript מאפשרת לנו לציין את התווים שרוצים לפלח. העברתי רווח ריק, שמציין פיצול בין המילים.

יצירת תיבות לפונקציות שימושיות

כדי להשתמש באפקט צריך להוסיף תיבות לכל אות. אפשר לראות בפונקציות האלה שהפונקציה map() נקראת באמצעות הפונקציה span(). זוהי הפונקציה span().

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

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

  return node
}

חשוב לציין שמאפיין מותאם אישית שנקרא --index מוגדר עם מיקום המערך. תיבות לאנימציות של אותיות הן דבר נהדר, אבל הוספה של אינדקס לשימוש ב-CSS היא תוספת קטנה לכאורה עם השפעה גדולה. הבולט ביותר בהשפעה הגדולה הזו הוא מדהים. נוכל להשתמש ב---index כדרך לקום את האנימציות כדי ליצור מראה מדורג.

מסקנה לגבי תשתיות

המודול splitting.js הושלם:

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)

בשלב הבא מתבצע ייבוא של הפונקציות byLetter() ו-byWord() האלה ושימוש בהן.

תזמור מפוצל

כאשר כלי הפיצול מוכנים לשימוש, המשמעות של שילוב כל אלה היא:

  1. מציאת הרכיבים שיש לפצל
  2. פיצול והחלפת הטקסט ב-HTML

לאחר מכן, ה-CSS יכסה את הרכיבים / תיבות ויציגו את האנימציה שלהם.

חיפוש רכיבים

בחרתי להשתמש במאפיינים ובערכים כדי לאחסן מידע על האנימציה הרצויה ואיך לפצל את הטקסט. אהבתי להוסיף את האפשרויות להצהרה ב-HTML. נעשה שימוש במאפיין split-by ב-JavaScript כדי למצוא רכיבים וליצור תיבות לאותיות או למילים. נעשה שימוש במאפיין letter-animation או word-animation מ-CSS כדי לטרגט ילדים של אלמנטים ולהחיל עליהם פעולות טרנספורמציה ואנימציות.

הנה דוגמה של HTML שממחישה את שני המאפיינים:

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

מציאת אלמנטים מ-JavaScript

השתמשתי בתחביר של סלקטור ב-CSS עבור נוכחות מאפיינים כדי לאסוף את רשימת הרכיבים שרוצים את פיצול הטקסט שלהם:

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

מציאת אלמנטים מ-CSS

השתמשתי גם בבורר הנוכחות של המאפיינים ב-CSS כדי להקצות לכל האנימציות האותיות את אותם סגנונות בסיסיים. בהמשך נשתמש בערך המאפיין כדי להוסיף עוד סגנונות ספציפיים להשגת אפקט.

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

פיצול הטקסט במקום

בכל אחד מהיעדים המפוצלים שנמצא ב-JavaScript, נפצל את הטקסט שלהם בהתאם לערך המאפיין ונמפה כל מחרוזת ל-<span>. עכשיו נוכל להחליף את הטקסט של הרכיב בתיבות שיצרנו:

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)
  }
})

מסקנות תזמור

index.js בהשלמה:

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 באנגלית הבאה:

  1. אפשר לייבא כמה פונקציות עזר של כלי עזר.
  2. כדאי לבדוק אם התנועה מתאימה למשתמש, ואם לא, לא לעשות כלום.
  3. לגבי כל רכיב שרוצים לפצל.
    1. לפצל אותם לפי אופן הפיצול שלהם.
    2. החלפת הטקסט ברכיבים.

פיצול אנימציות ומעברים בין שקפים

פעולת הפיצול של המסמך שלמעלה פתחה גישה למגוון אנימציות ואפקטים פוטנציאליים באמצעות CSS או JavaScript. בסוף המאמר הזה יש כמה קישורים שיכולים לעזור לכם לממש את פוטנציאל הפיצול.

הגיע הזמן להראות מה אפשר לעשות עם זה! אני אשתף 4 אנימציות ומעברים שמבוססים על CSS. 🤓

אותיות מפוצלות

כבסיס לאפקטים של אותיות מפוצלות, מצאתי שה-CSS הבא מועיל. אני מציבה את כל המעברים והאנימציות מאחורי שאילתת מדיה של תנועה, ואז נותנת לכל אות צאצא חדשה span מאפיין של תצוגה וגם סגנון למה שצריך לעשות עם רווחים לבנים:

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

סגנון השטחים הלבנים חשוב כדי שגבולות הפריסה שהם רק רווח לא יכווצו על ידי מנוע הפריסה. עכשיו נעבור לכיף.

דוגמה לאותיות מפוצלות למעבר

בדוגמה הזו נשתמש במעברי CSS לאפקט הטקסט המפוצל. כשיש מעברים, אנחנו צריכים שיהיו מצבים שבהם המנוע ימלא את האנימציה ביניהם, ובחרתי שלושה מצבים: לא להעביר את העכבר, להעביר את העכבר במשפט, להעביר את העכבר מעל לאות.

כשהמשתמש מעביר את העכבר מעל למשפט, כלומר הקונטיינר, המערכת מקטינה את התצוגה של כל הילדים, כאילו המשתמש דחף אותם רחוק יותר. לאחר מכן, כשהמשתמש מעביר את העכבר לאות, אני מעביר אותה קדימה.

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

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

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

דוגמה לאנימציה של אותיות מפוצלות

בדוגמה הזו נעשה שימוש באנימציה מוגדרת מראש של @keyframe כדי ליצור אנימציה אינסופית של כל אות, והיא משתמשת באינדקס המאפיין המותאם אישית המוטבע כדי ליצור אפקט נע.

@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;
  }
}

פיצול מילים

בדוגמאות הבאות, Flexbox הוא סוג של קונטיינר שבו השתמשתי, תוך מינוף היחידה ch כפער תקין.

word-animation {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 1ch;
}
כלי פיתוח של Flexbox שמציגים את הפער בין המילים

דוגמה למילות פיצול של מעבר

בדוגמת המעבר הזו אני משתמש שוב בהעברת העכבר. מכיוון שהאפקט מסתיר בהתחלה את התוכן עד שמעבירים מעליו את העכבר, וידאתי שהאינטראקציה והסגנונות יחולו רק אם יש למכשיר יכולת להעביר את העכבר.

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

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

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

דוגמה לאנימציה של מילים מפוצלות

בדוגמה הזו לאנימציה, אני משתמש שוב ב-CSS @keyframes כדי ליצור אנימציה אינסופית מדורגת בפסקה רגילה של טקסט.

[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;
  }
}

סיכום

עכשיו, אחרי שאת יודעת איך עשיתי את זה, איך היית רוצה?! 🙂

בואו נגוון את הגישות שלנו ונלמד את כל הדרכים לבנות באינטרנט. תוכלו ליצור Codepen או לארח הדגמה משלכם, לשלוח לי ציוץ ואני אוסיף אותה לקטע 'רמיקסים של קהילה' שבהמשך.

מקור

עוד הדגמות והשראה

רמיקסים של הקהילה