ניצול מלוא הפוטנציאל של שאילתות בקונטיינרים של CSS: לקחים מצוות Netflix

Jeremy Weeks
Jeremy Weeks
Stefan Heymanns
Stefan Heymanns

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

1. עיצוב רכיבים פשוט יותר, "מלמטה למעלה" לעומת "למעלה למטה"

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

דוגמה: שאילתות בקונטיינר לעומת שאילתות מדיה ו-JavaScript

לפני (נדרש JavaScript):

/* Layout with media queries */
.card {
    width: 100%;
}

@media (min-width: 600px) {
    .card {
        width: 50%;
    }
}

@media (min-width: 900px) {
    .card {
        width: 33.33%;
    }
}
// JavaScript to detect parent container size
const container = document.querySelector('.container');
const card = document.querySelector('.card');

function adjustLayout() {
    if (window.innerWidth >= 900) {
        card.style.width = '33.33%';
    } else if (window.innerWidth >= 600) {
        card.style.width = '50%';
    } else {
        card.style.width = '100%';
    }
}

window.addEventListener('resize', adjustLayout);
adjustLayout();

אחרי:

/* Container Query */
.container {
    container-type: inline-size;
}

.card {
    width: 100%;
}

@container (min-width: 600px) {
    .card {
        width: 50%;
    }
}

@container (min-width: 900px) {
    .card {
        width: 33.33%;
    }
}

הדוגמה הזו ממחישה איך מאגר ההורה כבר לא צריך לנהל את הפריסה של מאגר הצאצא. הכלל @container מאפשר ל-.card להגיב לגודל של המאגר המיידי שלו, וכך לפשט את הלוגיקה של הפריסה ולהסיר את הצורך ב-JavaScript לגמרי.

2. תגובה דינמית ללא שאילתות מדיה מורכבות

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

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

לפני:

/* Desktop versus Mobile
this only works if.sidebar is directly contained by a viewport-width element */
.sidebar {
    width: 300px;
}

@media (max-width: 768px) {
    .sidebar {
        width: 100%;
    }
}

אחרי:

/* Responsive sidebar based on container,
.sidebar can be placed in any element of any width */
.container {
    container-type: inline-size;
}

.sidebar {
    width: 100%;
}

@container (min-width: 768px) {
    .sidebar {
        width: 300px;
    }
}

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

3. הפחתת התלות ב-JavaScript לניהול הפריסה

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

דוגמה: הסרת לוגיקה של פריסה שמבוססת על JavaScript

לפני:

const cardContainer = document.querySelector('.card-container');
const cards = cardContainer.children;

function adjustLayout() {
    if (cardContainer.offsetWidth > 900) {
        cards.forEach(card => card.style.width = '33.33%');
    } else if (cardContainer.offsetWidth > 600) {
        cards.forEach(card => card.style.width = '50%');
    } else {
        cards.forEach(card => card.style.width = '100%');
    }
}

window.addEventListener('resize', adjustLayout);
adjustLayout();

אחרי:

.card-container {
    container-type: inline-size;
}

.card {
    width: 100%;
}

@container (min-width: 600px) {
    .card {
        width: 50%;
    }
}

@container (min-width: 900px) {
    .card {
        width: 33.33%;
    }
}

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

4. פחות קוד, פחות באגים

צוות Netflix גילה שהשימוש בשאילתות של קונטיינרים הוביל לפחות שורות קוד ולפחות באגים שקשורים לפריסה. העברת הלוגיקה של הפריסה מ-JavaScript ל-CSS והסרת הצורך בשאילתות מדיה מורכבות מאפשרות למפתחים לכתוב קוד שקל יותר לתחזק.

דוגמה: צמצום הקוד של הפריסה

צוות Netflix שם לב שלאחר ההטמעה של שאילתות בקונטיינרים, חל ירידה משמעותית בקוד ה-CSS – עד 30% ברכיבים מסוימים. במקביל, הצוות הצליח לפשט שאילתות מדיה מורכבות שעלולות לגרום לעימותים, על ידי הסרת הלוגיקה ששלטה ברכיבי הצאצאים, וכך להשיג רמה גבוהה יותר של הפרדת עניינים. הפחתת הקוד לא רק מזרזת את הפיתוח, אלא גם מצמצמת את נקודות הכשל הפוטנציאליות, וכך מפחיתה את מספר הבאגים.

לפני:

/* Before with complex media queries */
.card {
    width: 100%;
}

@media (min-width: 600px) {
    .card {
        width: 50%;
    }
}

@media (min-width: 900px) {
    .card {
        width: 33.33%;
    }
}

אחרי

.container {
    container-type: inline-size;
}

.card {
    width: 100%;
}

@container (min-width: 600px) {
    .card {
        width: 50%;
    }
}

@container (min-width: 900px) {
    .card {
        width: 33.33%;
    }
}

5. חוויית פיתוח משופרת

bq. "התכונה הזו הפכה את החיים שלי לקלים פי מאה"

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

כפי שאמר אחד מהחברים בצוות של Netflix, "כך CSS היה אמור לפעול מההתחלה".

6. חלופה לפוליפיל

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

if (! CSS.supports("container-type:size")) {
  /*use polyfill from
  https://www.npmjs.com/package/container-query-polyfill */
 }

סיכום

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

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