סקירה כללית בסיסית על יצירת אנימציות של מילים ואותיות מפוצלות.
בפוסט הזה אני רוצה לשתף את הדרך שבה אנו פותרים את הבעיה של אנימציות טקסט מחולקות ואינטראקציות באינטרנט, כך שהן יהיו מינימליסטיות, נגישות ויפעלו בכל הדפדפנים. כדאי לנסות את הדגמה.
אם אתם מעדיפים סרטון, הנה גרסה של הפוסט הזה ב-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
הוא 1, ואשנה את המסמך רק אם המשתמש לא ביקש לצמצם את התנועה.
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 מאפשרת לנו לציין אילו תווים לחתוך.
עברתי על רווח ריק, שמציין חלוקה בין מילים.
פונקציית השירות של boxes
האפקט דורש תיבות לכל אות, ואנחנו רואים בפונקציות האלה ש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;
}
סגנון הרווחים הלבנים חשוב כדי שמנוע הפריסה לא יאוחס את ה-spans שהם רק רווח. עכשיו נעבור לחלק הכיפי של נתונים בזיכרון.
דוגמה להעברת אותיות מפוצלות
בדוגמה הזו נעשה שימוש במעברי 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>
שנוצר על ידי gnehcwu ב-CodeSandbox