וולפנשטיין ל-CSS בלבד הוא פרויקט קטן שעשיתי לפני כמה שבועות. זה היה ניסוי עם טרנספורמציות והנפשות תלת-ממדיות של CSS.
השראה הדגמה של FPS ועוד אחת Wolfenstein CodePen, החלטתי לבנות גרסה משלי. הוא מבוסס באופן רופף על פרק 1 - קומה 9 של משחק התלת מימד המקורי של Wolfenstein.
עורך: המשחק הזה דורש תגובה מהירה בכוונה כדי להימנע ממסך Game Over.
הנה סרטון משחק:
בקצרה, הפרויקט שלי אינו אלא אנימציית CSS ארוכה מתוסרטת בקפידה. בנוסף כמה מקרים של פריצת תיבת סימון.
:checked ~ div { animation-name: spin; }
הסביבה מורכבת מפרצופי רשת תלת מימדיים והאנימציות הן לרוב תרגומים וסיבובים תלת מימדיים רגילים. שום דבר לא ממש מפואר.
עם זאת, שתי בעיות היו מסובכות במיוחד לפתרון:
- שחק את האנימציה של "ירי נשק" בכל פעם שהשחקן לוחץ על אויב.
- כשהבוס הנע במהירות קיבל את המכה האחרונה, היכנס להילוך איטי דרמטי.
ברמה הטכנית, זה אומר:
- הפעל מחדש אנימציה כאשר תיבת הסימון הבאה מסומנת.
- האט את האנימציה, כאשר תיבת סימון מסומנת.
למעשה, אף אחד מהם לא נפתר כראוי בפרויקט שלי! או שבסופו של דבר השתמשתי בדרכים לעקיפת הבעיה או שפשוט ויתרתי.
מצד שני, לאחר קצת חפירות, בסופו של דבר מצאתי את המפתח לשתי הבעיות: שינוי המאפיינים של הפעלת אנימציות CSS. במאמר זה, נחקור עוד בנושא זה:
- הרבה דוגמאות אינטראקטיביות.
- ניתוחים: איך כל דוגמה עובדת (או לא עובדת)?
- מאחורי הקלעים: איך דפדפנים מטפלים במצבי אנימציה?
תן לי "לזרוק את הלבנים שלי".
בעיה 1: הפעלה חוזרת של אנימציה
הדוגמה הראשונה: "רק עוד תיבת סימון"
האינטואיציה הראשונה שלי הייתה "פשוט הוסף תיבת סימון נוספת", מה שלא עובד:
כל תיבת סימון פועלת בנפרד, אך לא שתיהן יחד. אם תיבת סימון אחת כבר מסומנת, השנייה כבר לא פועלת.
כך זה עובד (או "לא עובד"):
- אל האני
animation-name
of<div>
isnone
כברירת מחדל. - המשתמש לוחץ על תיבת סימון אחת,
animation-name
הופך להיותspin
, והאנימציה מתחילה מההתחלה. - לאחר זמן מה, המשתמש לוחץ על תיבת הסימון השנייה. כלל CSS חדש נכנס לתוקף, אבל
animation-name
is עודspin
, כלומר לא הוספה או הוסרה אנימציה. האנימציה פשוט ממשיכה להתנגן כאילו כלום לא קרה.
הדוגמה השנייה: "שיבוט האנימציה"
גישת עבודה אחת היא לשכפל את האנימציה:
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2; }
הנה איך זה עובד:
animation-name
isnone
בתחילה.- המשתמש לוחץ על "ספין!",
animation-name
הופך להיותspin1
. האנימציהspin1
הוא התחיל מההתחלה כי זה עתה נוסף. - המשתמש לוחץ על "סובב שוב!",
animation-name
הופך להיותspin2
. האנימציהspin2
הוא התחיל מההתחלה כי זה עתה נוסף.
שימו לב שבשלב מס' 3, spin1
מוסר בגלל סדר כללי ה-CSS. זה לא יעבוד אם "סובב שוב!" נבדק תחילה.
הדוגמה השלישית: "הוספת אותה אנימציה"
גישת עבודה נוספת היא "להוסיף את אותה אנימציה":
#spin1:checked ~ div { animation-name: spin; }
#spin2:checked ~ div { animation-name: spin, spin; }
זה דומה לדוגמא הקודמת. אתה באמת יכול להבין את ההתנהגות כך:
#spin1:checked ~ div { animation-name: spin1; }
#spin2:checked ~ div { animation-name: spin2, spin1; }
שימו לב שכאשר "סובב שוב!" מסומן, האנימציה הישנה הפועלת הופכת לאנימציה השנייה ברשימה החדשה, מה שעלול להיות לא אינטואיטיבי. תוצאה ישירה היא: הטריק לא יעבוד אם animation-fill-mode
is forwards
. הנה הדגמה:
אם אתה תוהה מדוע זה המקרה, הנה כמה רמזים:
animation-fill-mode
isnone
כברירת מחדל, כלומר "לאנימציה אין השפעה כלל אם היא לא משחקת".animation-fill-mode: forwards;
פירושו "לאחר שהאנימציה תסתיים, היא חייבת להישאר בפריים המפתח האחרון לנצח".spin1
ההחלטה של תמיד מתעלמתspin2
זה בגללspin1
מופיע מאוחר יותר ברשימה.- נניח שהמשתמש לוחץ על "ספין!", ממתין לסיבוב מלא, ואז לוחץ על "ספין שוב!". ברגע זה.
spin1
הוא כבר גמור, וspin2
רק מתחיל.
דיון
כלל אצבע: אינך יכול "להפעיל מחדש" אנימציית CSS קיימת. במקום זאת, אתה רוצה להוסיף ולהפעיל אנימציה חדשה. זה עשוי להיות מאושר על ידי המפרט של W3C:
ברגע שהאנימציה התחילה היא ממשיכה עד שהיא מסתיימת או ששם האנימציה מוסר.
כעת, בהשוואה לשתי הדוגמאות האחרונות, אני חושב שבפועל, "שיבוט אנימציות" אמור לעבוד טוב יותר, במיוחד כאשר מעבד קדם CSS זמין.
בעיה 2: הילוך איטי
אפשר לחשוב שהאטה של אנימציה היא רק עניין של הגדרת זמן ארוך יותר animation-duration
:
div { animation-duration: 0.5s; }
#slowmo:checked ~ div { animation-duration: 1.5s; }
אכן, זה עובד:
… או שכן?
עם כמה שינויים, זה אמור להיות קל יותר לראות את הבעיה.
כן, האנימציה מואטת. ולא, זה לא נראה טוב. הכלב (כמעט) תמיד "קופץ" כאשר אתה מחליף את תיבת הסימון. יתר על כן, נראה שהכלב קופץ למיקום אקראי ולא למיקום הראשוני. כיצד ייתכן?
יהיה קל יותר להבין זאת אם נציג שני "אלמנטים צללים":
שני רכיבי הצל מריצים את אותן אנימציות עם שונות animation-duration
. והם אינם מושפעים מתיבת הסימון.
כאשר אתה מחליף את תיבת הסימון, האלמנט פשוט עובר מיד בין המצבים של שני רכיבי צל.
ציטוט המפרט של W3C:
שינויים בערכים של מאפייני האנימציה בזמן שההנפשה פועלת חלים כאילו האנימציה מכילה את הערכים האלה מהרגע שהחלה.
זה בא בעקבות חסר מדינה עיצוב, המאפשר לדפדפנים לקבוע בקלות את הערך המונפש. החישוב בפועל מתואר כאן ו כאן.
עוד ניסיון
רעיון אחד הוא להשהות את האנימציה הנוכחית, ואז להוסיף אנימציה איטית יותר שמשתלטת משם:
div {
animation-name: spin1;
animation-duration: 2s;
}
#slowmo:checked ~ div {
animation-name: spin1, spin2;
animation-duration: 2s, 5s;
animation-play-state: paused, running;
}
אז זה עובד:
… או שכן?
זה מאט כאשר אתה לוחץ על "Slowmo!". אבל אם תחכה למעגל שלם, תראה "קפיצה". למעשה, זה תמיד קופץ למצב כאשר "Slowmo!" נלחץ על.
הסיבה היא שאין לנו א from
keyframe מוגדר - ואנחנו לא צריכים. כאשר המשתמש לוחץ על "Slowmo!", spin1
מושהה בעמדה כלשהי, ו spin2
מתחיל בדיוק באותו מיקום. אנחנו פשוט לא יכולים לחזות את העמדה הזו מראש... או שאנחנו יכולים?
פתרון עובד
אנחנו יכולים! על ידי שימוש במאפיין מותאם אישית, נוכל לתפוס את הזווית בהנפשה הראשונה, ולאחר מכן להעביר אותה לאנימציה השנייה:
div {
transform: rotate(var(--angle1));
animation-name: spin1;
animation-duration: 2s;
}
#slowmo:checked ~ div {
transform: rotate(var(--angle2));
animation-name: spin1, spin2;
animation-duration: 2s, 5s;
animation-play-state: paused, running;
}
@keyframes spin1 {
to {
--angle1: 360deg;
}
}
@keyframes spin2 {
from {
--angle2: var(--angle1);
}
to {
--angle2: calc(var(--angle1) + 360deg);
}
}
הערה: @property
משמש בדוגמה זו, כלומר לא נתמך על ידי כל הדפדפנים.
הפתרון "המושלם".
יש אזהרה לפתרון הקודם: "יציאה מ-slowmo" לא עובדת טוב.
הנה פתרון טוב יותר:
בגרסה זו, ניתן להיכנס להילוך איטי או לצאת ממנו בצורה חלקה. גם לא נעשה שימוש בתכונה ניסיונית. אז האם זה הפתרון המושלם? כן ולא.
פתרון זה עובד כמו "החלפת" "הילוכים":
- הילוכים: יש שניים
<div>
ס. האחד הוא ההורה של השני. לשניהם יש אתspin
אנימציה אבל עם שונהanimation-duration
. המצב הסופי של האלמנט הוא הצטברות של שתי האנימציות. - הסטות: בהתחלה, רק אחד
<div>
האנימציה שלו פועלת. השני מושהה. כאשר תיבת הסימון מופעלת, שתי האנימציות מחליפות את מצבן.
למרות שאני מאוד אוהב את התוצאה, יש בעיה אחת: זה ניצול נחמד של spin
אנימציה, שאינה עובדת עבור סוגים אחרים של אנימציות באופן כללי.
פתרון מעשי (עם JS)
עבור אנימציות כלליות, אפשר להשיג את פונקציית ההילוך האיטי עם קצת JavaScript:
הסבר מהיר:
- מאפיין מותאם אישית משמש למעקב אחר התקדמות האנימציה.
- האנימציה "מופעלת מחדש" כאשר תיבת הסימון מוחלפת.
- קוד JS מחשב את הנכון
animation-delay
כדי להבטיח מעבר חלק. אני ממליץ את המאמר הזה אם אינך מכיר ערכים שליליים שלanimation-delay
.
אתה יכול לראות בפתרון זה הכלאה של "הפעלה מחדש של אנימציה" וגישת "החלפת הילוכים".
כאן חשוב לעקוב נכון אחר התקדמות האנימציה. דרכים לעקיפת הבעיה אפשריות אם @property
לא זמין. כדוגמה, גרסה זו משתמשת z-index
כדי לעקוב אחר ההתקדמות:
הערה צדדית: במקור, ניסיתי גם ליצור גרסת CSS בלבד אך לא הצלחתי. למרות שלא בטוח ב-100%, אני חושב שזה בגלל animation-delay
is לא ניתן להנפשה.
הנה גרסה עם JavaScript מינימלי. רק "כניסה לסלומו" עובדת.
אנא הודע לי אם אתה מצליח ליצור גרסת CSS עובדת בלבד!
כל אנימציה בהילוך איטי (עם JS)
לבסוף, אני רוצה לחלוק פתרון שעובד עבור (כמעט) כל אנימציה, אפילו עם מספר מסובך @keyframes
:
בעיקרון, אתה צריך להוסיף עוקב אחר התקדמות אנימציה, ואז לחשב בזהירות animation-delay
עבור האנימציה החדשה. עם זאת, לפעמים זה יכול להיות מסובך (אך אפשרי) להשיג את הערכים הנכונים.
לדוגמה:
animation-timing-function
לאlinear
.animation-direction
לאnormal
.- ערכים מרובים ב
animation-name
עם שונהanimation-duration
זה וanimation-delay
.
גם שיטה זו מתוארת כאן עבור API של אנימציות אינטרנט.
תודות
התחלתי בנתיב הזה לאחר שנתקלתי בפרויקטים של CSS בלבד. חלק היו יצירות אמנות עדינות, וחלקם היו חפצים מורכבים. המועדפים שלי הם אלה הכוללים אובייקטים תלת מימדיים, למשל, זה כדור קופץ וזה קוביית אריזה.
בהתחלה, לא היה לי מושג איך הם נוצרו. מאוחר יותר קראתי ולמדתי ממנו הרבה ההדרכה הנחמדה הזו מאת אנה טודור.
כפי שהתברר, בנייה והנפשה של אובייקטים תלת מימדיים עם CSS אינם שונים בהרבה מביצוע זה מַמחֶה, רק עם טעם קצת שונה.
סיכום
במאמר זה בחנו את ההתנהגות של אנימציות CSS כאשר א animate-*
הנכס משתנה. במיוחד פיתחנו פתרונות ל"השמעה חוזרת של אנימציה" ו"אנימציה איטית".
אני מקווה שתמצא את המאמר הזה מעניין. בבקשה תן לי לדעת את המחשבות שלך!