סקירה בסיסית של אופן יצירת אנימציות של פיצול אותיות ומילים.
בפוסט הזה אני רוצה לשתף את המחשבות שלי על דרכים לפתור בעיות שקשורות לאנימציות של פיצול טקסט ואינטראקציות באינטרנט, שיהיו מינימליות, נגישות ויפעלו בכל הדפדפנים. כדאי לנסות את ההדגמה.
אם אתם מעדיפים לצפות בסרטון, הנה גרסת YouTube של הפוסט הזה:
סקירה כללית
אנימציות של פיצול טקסט יכולות להיות מדהימות. בפוסט הזה נסקור רק חלק קטן מהאפשרויות של אנימציה, אבל הוא יספק לכם בסיס להמשך. המטרה היא ליצור אנימציה באופן הדרגתי. הטקסט צריך להיות קריא כברירת מחדל, והאנימציה צריכה להיות מובנית מעליו. אפקטים של תנועה בטקסט מפוצל יכולים להיות מוגזמים ולשבש את חוויית הצפייה, ולכן נשנה רק את ה-HTML או נחיל סגנונות של תנועה רק אם המשתמשים לא רגישים לתנועה.
סקירה כללית של תהליך העבודה והתוצאות:
- הכנת משתנים מותנים לצמצום התנועה עבור CSS ו-JS.
- הכנה של כלי עזר לפיצול טקסט ב-JavaScript.
- לנהל את התנאים ואת כלי השירות בטעינת הדף.
- כתיבה של מעברים ואנימציות ב-CSS לאותיות ולמילים (החלק הכיפי!).
זו תצוגה מקדימה של התוצאות המותנות שאנחנו רוצים להשיג:

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

הכנת תנאים של תנועה
בפרויקט הזה נעשה שימוש בשאילתת המדיה @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
מתוך Nesting Draft 1. כך אוכל לאחסן את כל הלוגיקה לגבי האנימציה ודרישות הסגנון שלה עבור רכיב האב ורכיבי הצאצא במקום אחד:
letter-animation {
@media (--motionOK) {
/* animation styles */
}
}
בעזרת מאפיין מותאם אישית של PostCSS וערך בוליאני של JavaScript, אנחנו מוכנים לשדרג את האפקט באופן מותנה. בקטע הבא נפרט את ה-JavaScript להמרת מחרוזות לרכיבים.
פיצול טקסט
אי אפשר להנפיש בנפרד אותיות, מילים, שורות וכו' באמצעות CSS או JS. כדי ליצור את האפקט, אנחנו צריכים תיבות. אם רוצים להנפיש כל אות, כל אות צריכה להיות רכיב. אם רוצים להנפיש כל מילה, כל מילה צריכה להיות אלמנט.
- יצירת פונקציות עזר של JavaScript לפיצול מחרוזות לרכיבים
- תיאום השימוש בכלי השירות האלה
פונקציית עזר לפיצול אותיות
דרך טובה להתחיל היא עם פונקציה שמקבלת מחרוזת ומחזירה כל אות במערך.
export const byLetter = text =>
[...text].map(span)
התחביר של spread מ-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()
ושימוש בהן.
פיצול תזמור
אחרי שמוודאים שכלי הפיצול מוכנים לשימוש, אפשר לשלב את כל הפעולות האלה:
- איך יודעים אילו רכיבים צריך לפצל
- פיצול שלהם והחלפת טקסט ב-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 באופן הבא:
- ייבוא של כמה פונקציות עזר.
- בודקים אם התנועה מתאימה למשתמש הזה. אם לא, לא עושים כלום.
- לכל רכיב שרוצים לפצל.
- מפצלים אותם לפי האופן שבו רוצים לפצל אותם.
- החלפת טקסט ברכיבים.
פיצול אנימציות ומעברים
הפעולה של פיצול המסמך שמתוארת למעלה פותחת אפשרויות רבות לאנימציות ולאפקטים באמצעות 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;
}
דוגמה למילים מפוצלות במעבר
בדוגמה הזו של מעבר אני משתמש שוב בהעברת העכבר. האפקט מסתיר את התוכן עד להעברת העכבר, לכן דאגתי שהאינטראקציה והסגנונות יחולו רק אם המכשיר תומך בהעברת העכבר.
@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 או לארח הדגמה משלכם, לצייץ לי אותה ואוסיף אותה לקטע 'רמיקסים של הקהילה' שבהמשך.
מקור
הדגמות והשראה נוספות
רמיקסים מהקהילה
-
<text-hover>
web component by gnehcwu on CodeSandbox