Xlera8

אפקט ריחוף מפואר עבור האווטאר שלך

האם אתה מכיר את האפקט הזה שבו ראשו של מישהו חודר דרך עיגול או חור? האנימציה המפורסמת של Porky Pig שבה הוא מנופף לשלום בזמן שהוא יוצא מסדרה של טבעות אדומות היא הדוגמה המושלמת, ו Kilian Valkhof למעשה יצר את זה מחדש כאן ב-CSS-Tricks לפני כמה זמן.

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

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

ה-HTML: רק אלמנט אחד

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

<img src="" alt="">

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

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

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

עוצב על ידי חברת cang

אני מקווה לראות הרבה דוגמאות לכך ככל האפשר באמצעות תמונות אמיתיות - אז בבקשה שתף את התוצאה הסופית שלך בהערות כשתסיים כדי שנוכל לבנות אוסף!

לפני שקפוץ ל-CSS, בואו ננתח תחילה את האפקט. התמונה גדלה עם ריחוף, כך שבטוח נשתמש בה transform: scale() שם. יש מעגל מאחורי האווטאר, ושיפוע רדיאלי אמור לעשות את העבודה. לבסוף, אנחנו צריכים דרך ליצור גבול בתחתית העיגול שיוצר את המראה של האווטאר מאחורי העיגול.

בואו נתחיל לעבוד!

אפקט קנה המידה

נתחיל בהוספת הטרנספורמציה:

img { width: 280px; aspect-ratio: 1; cursor: pointer; transition: .5s;
}
img:hover { transform: scale(1.35);
}

שום דבר לא מסובך עדיין, נכון? בוא נמשיך הלאה.

המעגל

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

img { --b: 5px; /* border width */ width: 280px; aspect-ratio: 1; background: radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), #C02942 calc(100% - var(--b)) 99%, #0000 ); cursor: pointer; transition: .5s;
}
img:hover { transform: scale(1.35);
}

שימו לב למשתנה CSS, --b, אני משתמש שם. הוא מייצג את עובי ה"גבול" שבעצם משמש רק כדי להגדיר את עצירות הצבע הקשות עבור החלק האדום של השיפוע הרדיאלי.

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

נתחיל בהגדרת משתנה CSS, --f, שמגדיר את "גורם קנה המידה", והשתמש בו כדי לקבוע את גודל המעגל. אני משתמש 1 כערך ברירת המחדל, כמו שזה הסולם הראשוני של התמונה והעיגול שממנו אנו הופכים.

הנה הדגמה כדי להמחיש את הטריק. העבר את העכבר כדי לראות מה קורה מאחורי הקלעים:

הוספתי צבע שלישי ל radial-gradient כדי לזהות טוב יותר את אזור השיפוע בעת ריחוף:

radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), #C02942 calc(100% - var(--b)) 99%, lightblue
);

כעת עלינו למקם את הרקע שלנו במרכז המעגל ולוודא שהוא תופס את מלוא הגובה. אני אוהב להצהיר הכל ישירות על background מאפיין קיצור, כדי שנוכל להוסיף את מיקום הרקע שלנו ולוודא שהוא לא חוזר על ידי הדבקה בערכים האלה מיד לאחר radial-gradient():

background: radial-gradient() 50% / calc(100% / var(--f)) 100% no-repeat;

הרקע ממוקם במרכז (50%), יש רוחב שווה ל calc(100%/var(--f)), ובעל גובה שווה ל 100%.

שום דבר לא מתקלקל מתי --f שווה ל 1 - שוב, קנה המידה הראשוני שלנו. בינתיים, השיפוע תופס את מלוא רוחב המיכל. כשאנחנו מגדילים --f, גודל האלמנט גדל - הודות ל- scale() טרנספורמציה - וגודל השיפוע יורד.

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

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

הגבול התחתון

ניסיתי לראשונה להתמודד עם זה עם border-bottom נכס, אבל לא הצלחתי למצוא דרך להתאים את גודל הגבול לגודל לעיגול. הנה הטוב ביותר שיכולתי להשיג ואתה יכול מיד לראות שזה שגוי:

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

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

עכשיו יש לנו את ה"גבול" התחתון שלנו (למעשה א outline) בשילוב עם ה"גבול" שנוצר על ידי הגרדיאנט ליצירת עיגול שלם. אנחנו עדיין צריכים להסתיר חלקים מה- outline (מלמעלה ומהצדדים), שאליו נגיע עוד רגע.

הנה הקוד שלנו עד כה, כולל עוד כמה משתני CSS שבהם תוכל להשתמש כדי להגדיר את גודל התמונה (--s) וצבע ה"גבול" (--c):

img { --s: 280px; /* image size */ --b: 5px; /* border thickness */ --c: #C02942; /* border color */ --f: 1; /* initial scale */ width: var(--s); aspect-ratio: 1; cursor: pointer; border-radius: 0 0 999px 999px; outline: var(--b) solid var(--c); outline-offset: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b)); background: radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), var(--c) calc(100% - var(--b)) 99%, #0000 ) 50% / calc(100% / var(--f)) 100% no-repeat; transform: scale(var(--f)); transition: .5s;
}
img:hover { --f: 1.35; /* hover scale */
}

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

החישוב המשמש על outline-offset הוא הרבה יותר פשוט ממה שהוא נראה. כברירת מחדל, outline מצוייר בחוץ של הקופסה של האלמנט. ובמקרה שלנו, אנחנו צריכים את זה חֲפִיפָה האלמנט. ליתר דיוק, אנחנו צריכים את זה כדי לעקוב אחר המעגל שנוצר על ידי שיפוע.

תרשים של מעבר הרקע.

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

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

Offset = ((f - 1) * S/2) / f = (1 - 1/f) * S/2

אנו מוסיפים סימן שלילי מכיוון שאנו צריכים שהמתאר יעבור מבחוץ לפנים:

Offset = (1/f - 1) * S/2

הנה הדגמה מהירה שמראה כיצד המתאר עוקב אחר השיפוע:

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

outline-offset: calc((1 / var(--f) - 1) * var(--s) / 2) - var(--b));

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

ראשית, בואו נוסיף מקום בחלק העליון עם ריפוד כדי למנוע את החפיפה בחלק העליון:

img { --s: 280px; /* image size */ --b: 5px; /* border thickness */ --c: #C02942; /* border color */ --f: 1; /* initial scale */ width: var(--s); aspect-ratio: 1; padding-block-start: calc(var(--s)/5); /* etc. */
}
img:hover { --f: 1.35; /* hover scale */
}

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

שימו לב שהוספתי את content-box ערך ל- background:

background: radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), var(--c) calc(100% - var(--b)) 99%, #0000 ) 50%/calc(100%/var(--f)) 100% no-repeat content-box;

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

הוספת מסכת CSS לתערובת

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

הנה איור כדי להמחיש מה אנחנו צריכים להסתיר או מה אנחנו צריכים להראות כדי להיות מדויק יותר

מראה כיצד המסכה חלה על החלק התחתון של המעגל.

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

אנו יכולים לזהות שני חלקים של המסכה שלנו:

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

הנה ה-CSS האחרון שלנו:

img { --s: 280px; /* image size */ --b: 5px; /* border thickness */ --c: #C02942; /* border color */ --f: 1; /* initial scale */ --_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box; --_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b)); width: var(--s); aspect-ratio: 1; padding-top: calc(var(--s)/5); cursor: pointer; border-radius: 0 0 999px 999px; outline: var(--b) solid var(--c); outline-offset: var(--_o); background: radial-gradient( circle closest-side, #ECD078 calc(99% - var(--b)), var(--c) calc(100% - var(--b)) 99%, #0000) var(--_g); mask: linear-gradient(#000 0 0) no-repeat 50% calc(-1 * var(--_o)) / calc(100% / var(--f) - 2 * var(--b)) 50%, radial-gradient( circle closest-side, #000 99%, #0000) var(--_g); transform: scale(var(--f)); transition: .5s;
}
img:hover { --f: 1.35; /* hover scale */
}

בואו נפרק את זה mask תכונה. בתור התחלה, שימו לב שדומה radial-gradient() מ background רכוש נמצא שם. יצרתי משתנה חדש, --_g, כדי שהחלקים המשותפים יהפכו את הדברים לפחות עמוסים.

--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box; mask: radial-gradient( circle closest-side, #000 99%, #0000) var(--_g);

לאחר מכן, יש א linear-gradient() גם שם:

--_g: 50% / calc(100% / var(--f)) 100% no-repeat content-box; mask: linear-gradient(#000 0 0) no-repeat 50% calc(-1 * var(--_o)) / calc(100% / var(--f) - 2 * var(--b)) 50%, radial-gradient( circle closest-side, #000 99%, #0000) var(--_g);

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

calc(100% / var(--f) - 2 * var(--b))

גובה המלבן שווה לחצי, 50%, של גודל האלמנט.

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

--_o: calc((1 / var(--f) - 1) * var(--s) / 2 - var(--b));

אחד הדברים המבלבלים כאן הוא שאנחנו צריכים א שלילי קיזוז עבור המתאר (כדי להזיז אותו מבחוץ לפנים) אבל א חיובי היסט עבור השיפוע (כדי לעבור מלמעלה למטה). אז, אם אתה תוהה למה אנחנו מכפילים את הקיזוז, --_o, על ידי -1, ובכן, עכשיו אתה יודע!

הנה הדגמה כדי להמחיש את תצורת השיפוע של המסכה:

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

גלישה את

אוף, סיימנו! ולא רק שסיימנו עם אנימציית ריחוף חלקה, אלא עשינו הכל עם HTML יחיד <img> אֵלֵמֶנט. רק זה ופחות מ-20 שורות של תחבולות CSS!

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

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

אני אשאיר אתכם עם הדגמה אחרונה שמשתמשת בתמונות של מפתחי CSS פופולריים. אל תשכח להראות לי הדגמה עם תמונה משלך כדי שאוכל להוסיף אותה לאוסף!

דבר איתנו

שלום שם! איך אני יכול לעזור לך?