היתרונות של שימוש במאפיינים מותאמים אישית במערכות עיצוב ובספריות רכיבים.
קוראים לי דייב ואני מפתח Front-end בכיר ב-Nordhealth. אני עובד על העיצוב והפיתוח של מערכת העיצוב Nord, שכוללת בניית רכיבי אינטרנט לספריית הרכיבים שלנו. רציתי לשתף איך פתרנו את הבעיות שקשורות לעיצוב של רכיבי אינטרנט באמצעות מאפייני CSS מותאמים אישית, וגם כמה מהיתרונות הנוספים של שימוש במאפיינים מותאמים אישית במערכות עיצוב ובספריות רכיבים.
איך אנחנו יוצרים רכיבי אינטרנט
כדי לבנות את רכיבי האינטרנט שלנו, אנחנו משתמשים ב-Lit, ספרייה שמספקת הרבה קוד boilerplate, כמו מצב, סגנונות בהיקף, תבניות ועוד. Lit היא לא רק קלה, אלא גם מבוססת על ממשקי API מקוריים של JavaScript. המשמעות היא שאנחנו יכולים לספק חבילת קוד רזה שמנצלת את התכונות שכבר קיימות בדפדפן.
import {html, css, LitElement} from 'lit';
export class SimpleGreeting extends LitElement {
static styles = css`:host { color: blue; font-family: sans-serif; }`;
static properties = {
name: {type: String},
};
constructor() {
super();
this.name = 'there';
}
render() {
return html`Hey
${this.name}, welcome to Web Components!`;
}
}
customElements.define('simple-greeting', SimpleGreeting);
אבל הדבר הכי אטרקטיבי ברכיבי Web הוא שהם פועלים כמעט עם כל מסגרת JavaScript קיימת, או אפילו בלי מסגרת בכלל. אחרי שהפניה לחבילת ה-JavaScript הראשית מופיעה בדף, השימוש ברכיב אינטרנט דומה מאוד לשימוש ברכיב HTML מקורי. הסימן האמיתי היחיד לכך שזה לא אלמנט HTML מקורי הוא המקף העקבי בתוך התגים, שהוא תקן לציון לדפדפן שמדובר ברכיב אינטרנט.
הצפנת סגנון של Shadow DOM
בדומה לרכיבי HTML מקוריים שיש להם Shadow DOM, גם לרכיבי אינטרנט יש Shadow DOM. Shadow DOM הוא עץ מוסתר של צמתים בתוך רכיב. הדרך הטובה ביותר להבין את זה היא לפתוח את בודק האינטרנט ולהפעיל את האפשרות 'הצגת עץ Shadow DOM'. אחרי שתעשו את זה, נסו להסתכל על רכיב קלט מקורי בכלי לבדיקת רכיבים – עכשיו תהיה לכם אפשרות לפתוח את הקלט הזה ולראות את כל הרכיבים בתוכו. אפשר לנסות את זה אפילו עם אחד מרכיבי האינטרנט שלנו – נסו לבדוק את רכיב הקלט המותאם אישית שלנו כדי לראות את Shadow DOM שלו.

אחד היתרונות (או החסרונות, תלוי בנקודת המבט שלכם) של Shadow DOM הוא אנקפסולציה של סגנון. אם כותבים CSS בתוך רכיב האינטרנט, הסגנונות האלה לא יכולים לחלחל החוצה ולהשפיע על הדף הראשי או על רכיבים אחרים. הם מוגבלים לרכיב. בנוסף, קוד CSS שנכתב לדף הראשי או לרכיב אינטרנט ראשי לא יכול לחלחל לרכיב האינטרנט שלכם.
היתרון של האנקפסולציה הזו של סגנונות הוא בספריית הרכיבים שלנו. כך אנחנו יכולים להיות בטוחים יותר שכאשר מישהו משתמש באחד מהרכיבים שלנו, הוא ייראה כמו שתכננו, בלי קשר לסגנונות שהוחלו על דף האב. כדי לוודא עוד יותר, אנחנו מוסיפים all: unset;
ל-root, או ל-host, של כל רכיבי האינטרנט שלנו.
:host {
all: unset;
display: block;
box-sizing: border-box;
text-align: start;
/* ... */
}
אבל מה קורה אם למישהו שמשתמש ברכיב האינטרנט שלכם יש סיבה מוצדקת לשנות סגנונות מסוימים? אולי יש שורת טקסט שצריכה ניגודיות גבוהה יותר בגלל ההקשר שלה, או שצריך להגדיל את עובי הגבול? אם אין סגנונות שיכולים להיכנס לרכיב, איך אפשר לפתוח את אפשרויות הסגנון האלה?
כאן נכנסים לתמונה מאפיינים מותאמים אישית של CSS.
מאפייני CSS בהתאמה אישית
מאפיינים בהתאמה אישית הם בדיוק מה שהשם שלהם מרמז – מאפייני CSS שאתם יכולים לקבוע את השם שלהם בעצמכם ולהחיל עליהם כל ערך שנדרש. הדרישה היחידה היא להוסיף שני מקפים לפני כל אחת מהן. אחרי שמצהירים על מאפיין מותאם אישית, אפשר להשתמש בערך שלו ב-CSS באמצעות הפונקציה var()
.
:root {
--n-color-accent: rgb(53, 89, 199);
/* ... */
}
.n-color-accent-text {
color: var(--n-color-accent);
}
בנוגע להורשה, כל המאפיינים המותאמים אישית עוברים בירושה, וזה תואם להתנהגות האופיינית של מאפייני CSS רגילים וערכים רגילים. אפשר להשתמש בכל מאפיין מותאם אישית שמוחל על רכיב אב או על הרכיב עצמו כערך במאפיינים אחרים. אנחנו משתמשים באופן נרחב במאפיינים מותאמים אישית עבור טוקנים של עיצוב, ומחילים אותם על רכיב הבסיס באמצעות מסגרת CSS שלנו. המשמעות היא שכל הרכיבים בדף יכולים להשתמש בערכי הטוקנים האלה, בין אם מדובר ברכיב אינטרנט, במחלקת עזר של CSS או במפתח שרוצה לשלוף ערך מרשימת הטוקנים שלנו.
היכולת הזו להעביר מאפיינים מותאמים אישית בירושה, באמצעות הפונקציה var()
, מאפשרת לנו לחדור ל-Shadow DOM של רכיבי האינטרנט שלנו ולתת למפתחים שליטה מדויקת יותר בעיצוב הרכיבים שלנו.
מאפיינים מותאמים אישית ברכיב אינטרנט של Nord
בכל פעם שאנחנו מפתחים רכיב למערכת העיצוב שלנו, אנחנו משתמשים בגישה מחושבת ל-CSS שלו – אנחנו שואפים ליצור קוד רזה אבל קל מאוד לתחזוקה. ה-design tokens שיש לנו מוגדרים כמאפיינים מותאמים אישית בתוך מסגרת ה-CSS הראשית שלנו ברכיב הבסיס.
:root {
--n-space-m: 16px;
--n-space-l: 24px;
/* ... */
--n-color-background: rgb(255, 255, 255);
--n-color-border: rgb(216, 222, 228);
/* ... */
}
אחרי כן, הרכיבים שלנו מפנים לערכי האסימונים האלה. במקרים מסוימים, נחיל את הערך ישירות על מאפיין ה-CSS, אבל במקרים אחרים נגדיר מאפיין מותאם אישית חדש בהקשר מסוים ונחיל עליו את הערך.
:host {
--n-tab-group-padding: 0;
--n-tab-list-background: var(--n-color-background);
--n-tab-list-border: inset 0 -1px 0 0 var(--n-color-border);
/* ... */
}
.n-tab-group-list {
box-shadow: var(--n-tab-list-border);
background-color: var(--n-tab-list-background);
gap: var(--n-space-s);
/* ... */
}
בנוסף, נבצע הפשטה של חלק מהערכים שספציפיים לרכיב אבל לא נמצאים בטוקנים שלנו, ונהפוך אותם למאפיין בהתאמה אישית לפי הקשר. מאפיינים מותאמים אישית שרלוונטיים לרכיב מספקים לנו שני יתרונות עיקריים. קודם כל, זה אומר שאפשר להשתמש ב-CSS בצורה יותר יעילה, כי אפשר להחיל את הערך הזה על כמה מאפיינים בתוך הרכיב.
.n-tab-group-list::before {
/* ... */
padding-inline-start: var(--n-tab-group-padding);
}
.n-tab-group-list::after {
/* ... */
padding-inline-end: var(--n-tab-group-padding);
}
בנוסף, השימוש במאפיין מותאם אישית מאפשר לשנות את מצב הרכיב ואת הווריאציה בצורה נקייה מאוד – רק המאפיין המותאם אישית צריך להשתנות כדי לעדכן את כל המאפיינים האלה, למשל כשמעצבים מצב ריחוף או מצב פעיל, או במקרה הזה, וריאציה.
:host([padding="l"]) {
--n-tab-group-padding: var(--n-sp
ace-l);
}
אבל היתרון המשמעותי ביותר הוא שכשאנחנו מגדירים את המאפיינים המותאמים אישית ההקשריים האלה ברכיב, אנחנו יוצרים מעין API של CSS מותאם אישית לכל אחד מהרכיבים שלנו, שהמשתמש ברכיב יכול להשתמש בו.
<nord-tab-group label="T>itl<e"
>!<-- ... --
/nord>-t<ab-gr>oup
style
nord-tab-group {
--n-tab-group-padding: var(--n-space<-xl);
>
}
/style
בדוגמה הקודמת מוצג אחד מרכיבי האינטרנט שלנו עם מאפיין מותאם אישית הקשרי שמשתנה באמצעות בורר. התוצאה של הגישה הזו היא רכיב שמספק למשתמש גמישות מספקת בסגנון, תוך שמירה על רוב הסגנונות בפועל. בנוסף, כבונוס, לנו, כמפתחי רכיבים, יש אפשרות ליירט את הסגנונות האלה שהמשתמשים החילו. אם נרצה לשנות או להאריך את אחת מהמאפיינים האלה, נוכל לעשות זאת בלי שהמשתמש יצטרך לשנות את הקוד שלו.
אנחנו חושבים שהגישה הזו יעילה מאוד, לא רק לנו כיוצרים של רכיבי מערכת העיצוב, אלא גם לצוות הפיתוח שלנו כשהוא משתמש ברכיבים האלה במוצרים שלנו.
שימוש מתקדם במאפיינים מותאמים אישית
בזמן כתיבת המאמר הזה, אנחנו לא חושפים את מאפייני ההקשר המותאמים אישית האלה בתיעוד שלנו, אבל אנחנו מתכננים לעשות זאת כדי שצוות הפיתוח הרחב שלנו יוכל להבין את המאפיינים האלה ולהשתמש בהם. הרכיבים שלנו ארוזים ב-npm עם קובץ מניפסט, שמכיל את כל מה שצריך לדעת עליהם. לאחר מכן אנחנו משתמשים בקובץ המניפסט כנתונים כשאנחנו פורסים את אתר התיעוד שלנו, באמצעות Eleventy והתכונה Global Data שלו. אנחנו מתכננים לכלול את מאפייני ההקשר המותאמים אישית האלה בקובץ הנתונים של המניפסט.
תחום נוסף שבו אנחנו רוצים להשתפר הוא האופן שבו מאפיינים מותאמים אישית תלויי-הקשר מקבלים ערכים בירושה. לדוגמה, אם רוצים לשנות את הצבע של שני רכיבי קו מפריד, צריך להגדיר סלקטורים לשני הרכיבים האלה או להחיל את מאפיין ההתאמה האישית ישירות על האלמנט עם מאפיין הסגנון. יכול להיות שזה נראה בסדר, אבל יהיה יותר מועיל אם המפתח יוכל להגדיר את הסגנונות האלה ברכיב מכיל או אפילו ברמת הבסיס.
<nord-divider></nord-divider>
<section>
<nord-divider></nord-divider>
<!-- ... -->
</section>
<style>
nord-divider {
--n-divider-color: var(--n-color-status-danger);
}
section {
padding: var(--n-space-s);
background: var(--n-color-surface-raised);
}
section nord-divider {
--n-divider-color: var(--n-color-status-success);
}
</style>
הסיבה לכך שצריך להגדיר את הערך של המאפיין המותאם אישית ישירות ברכיב היא שאנחנו מגדירים אותם באותו אלמנט באמצעות בורר המארח של הרכיב. האסימונים הגלובליים לעיצוב שבהם אנחנו משתמשים ישירות ברכיב עוברים ללא שינוי, בלי שהבעיה הזו תשפיע עליהם, ואפשר אפילו ליירט אותם ברכיבי הורה. איך אפשר ליהנות מכל העולמות?
מאפיינים מותאמים אישית פרטיים וציבוריים
מאפיינים מותאמים אישית פרטיים הם משהו שנוצר על ידי Lea Verou, והם מאפיינים מותאמים אישית 'פרטיים' בהקשר של הרכיב עצמו, אבל מוגדרים כמאפיינים מותאמים אישית 'ציבוריים' עם חלופה.
:host {
--_n-divider-color: var(--n-divider-color, var(--n-color-border));
--_n-divider-size: var(--n-divider-size, 1px);
}
.n-divider {
border-block-start: solid var(--_n-divider-size) var(--_n-divider-color);
/* ... */
}
הגדרת מאפיינים מותאמים אישית לפי ההקשר מאפשרת לנו להמשיך לעשות את כל מה שעשינו קודם, כמו ירושה של ערכי טוקן גלובליים ושימוש חוזר בערכים בקוד הרכיב. אבל הרכיב גם יירש בצורה חלקה הגדרות חדשות של המאפיין הזה בעצמו או בכל רכיב אב.
<nord-divider></nord-divider>
<section>
<nord-divider></nord-divider>
<!-- ... -->
</section>
<style>
nord-divider {
--n-divider-color: var(--n-color-status-danger);
}
section {
padding: var(--n-space-s);
background: var(--n-color-surface-raised);
--n-divider-color: var(--n-color-status-success);
}
</style>
אפשר לטעון שהשיטה הזו לא באמת 'פרטית', אבל אנחנו עדיין חושבים שזה פתרון אלגנטי לבעיה שהטרידה אותנו. כשתהיה לנו הזדמנות, נטפל בבעיה הזו ברכיבים שלנו כדי שלצוות הפיתוח תהיה יותר שליטה על השימוש ברכיבים, ועדיין יוכל ליהנות מההגבלות שהגדרנו.
מקווה שהתובנה הזו לגבי השימוש שלנו ברכיבי אינטרנט עם מאפיינים מותאמים אישית של CSS הייתה שימושית. אשמח לשמוע מה דעתכם. אם תבחרו להשתמש באחת מהשיטות האלה בעבודה שלכם, תוכלו לפנות אליי בטוויטר @DavidDarnes. אפשר למצוא את Nordhealth גם ב-Twitter בכתובת @NordhealthHQ, וגם את שאר חברי הצוות שלי, שעבדו קשה כדי ליצור את מערכת העיצוב הזו וליישם את התכונות שמוזכרות במאמר הזה: @Viljamis, @WickyNilliams ו-@eric_habich.
תמונה ראשית (Hero) של Dan Cristian Pădureț