השווה & השוואת כיתוב

למאפיין lang יכולה להיות שפה אחת בלבד. כלומר, למאפיין <html> יכולה להיות רק שפה אחת, גם אם יש כמה שפות בדף. מגדירים את lang כשפה הראשית של הדף.

מה אסור לעשות
<html lang="ar,en,fr,pt">...</html>
אין תמיכה בכמה שפות.
מה מותר לעשות
<html lang="ar">...</html>
מגדירים רק את השפה הראשית של הדף. במקרה הזה, השפה היא ערבית.

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

לא מספיק תיאורי
Check out our guide to web performance <a href="/guide">here</a>.
תוכן שימושי!
Check out <a href="/guide">our guide to web performance</a>.

בדיקה אם אנימציה מפעילה פריסה

אנימציה שמזיזה רכיב באמצעות משהו אחר מ-transform צפויה להיות איטית. בדוגמה הבאה, השגתי את אותו תוצאה חזותית באמצעות אנימציה של top ו-left ושימוש ב-transform.

מה אסור לעשות
.box {
  position: absolute;
  top: 10px;
  left: 10px;
  animation: move 3s ease infinite;
}

@keyframes move {
  50% {
     top: calc(90vh - 160px);
     left: calc(90vw - 200px);
  }
}
מה מותר לעשות
.box {
  position: absolute;
  top: 10px;
  left: 10px;
  animation: move 3s ease infinite;
}

@keyframes move {
  50% {
     transform: translate(calc(90vw - 200px), calc(90vh - 160px));
  }
}

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

באמצעות אותו סימון, אפשר להחליף את padding-top: 56.25% ב-aspect-ratio: 16 / 9 ולהגדיר את aspect-ratio ליחס מסוים של width / height.

שימוש ב-padding-top
.container {
  width: 100%;
  padding-top: 56.25%;
}
שימוש ב-aspect-ratio
.container {
  width: 100%;
  aspect-ratio: 16 / 9;
}

השימוש ב-aspect-ratio במקום ב-padding-top ברור הרבה יותר, ולא מחייב שינוי יסודי של מאפיין המילוי כדי לבצע פעולה מחוץ להיקף הרגיל שלו.

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

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

לא מומלץ – רצף ארוך מדי
async function logInOrder(urls) {
  for (const url of urls) {
    const response = await fetch(url);
    console.log(await response.text());
  }
}
הקוד נראה הרבה יותר מסודר, אבל האחזור השני לא מתחיל עד שהאחזור הראשון נקרא במלואו, וכן הלאה. הבדיקה הזו איטית בהרבה מהדוגמה של ההבטחות שמבצעת את האחזורים במקביל. למרבה המזל, יש דרך ביניים אידיאלית.
מומלץ – יפה ומקביל
function markHandled(...promises) {
  Promise.allSettled(promises);
}

async function logInOrder(urls) {
  // fetch all the URLs in parallel
  const textPromises = urls.map(async (url) => {
    const response = await fetch(url);
    return response.text();
  });

  markHandled(...textPromises);

  // log them in sequence
  for (const textPromise of textPromises) {
    console.log(await textPromise);
  }
}
בדוגמה הזו, כתובות ה-URL מאוחרות ומוקרות במקביל, אבל החלק reduce "החכם" מוחלף בלולאת for רגילה, משעממת וקריאה.

כתיבת מאפיינים מותאמים אישית ב-Houdini

הנה דוגמה להגדרת מאפיין מותאם אישית (למשל: משתנה CSS), אבל עכשיו עם תחביר (סוג), ערך ראשוני (חלופה) וערכו של בוליאני בירושה (האם הוא יקבל בירושה את הערך מההורה או לא?). הדרך הנוכחית לעשות זאת היא באמצעות CSS.registerProperty() ב-JavaScript, אבל ב-Chromium 85 ואילך תהיה תמיכה בתחביר @property בקובצי ה-CSS:

קובץ JavaScript נפרד (Chromium 78)
CSS.registerProperty({
  name: '--colorPrimary',
  syntax: '',
  initialValue: 'magenta',
  inherits: false
});
הכלול בקובץ CSS (Chromium 85)
@property --colorPrimary {
  syntax: '';
  initial-value: magenta;
  inherits: false;
}

עכשיו אפשר לגשת ל---colorPrimary כמו לכל מאפיין CSS מותאם אישית אחר, דרך var(--colorPrimary). עם זאת, ההבדל הוא ש---colorPrimary לא נקרא רק כמחרוזת. יש בו נתונים!

ב-CSS backdrop-filter חל אפקט אחד או יותר על רכיב שקוף או שקוף למחצה. כדי להבין את זה, כדאי לעיין בתמונות הבאות.

ללא שקיפות בחזית
משולש שמופיע מעל מעגל. לא ניתן לראות את העיגול דרך המשולש.
.frosty-glass-pane {
  backdrop-filter: blur(2px);
}
שקיפות בחזית
משולש שמופיע מעל מעגל. המשולש שקוף, כך שאפשר לראות דרכו את העיגול.
.frosty-glass-pane {
  opacity: .9;
  backdrop-filter: blur(2px);
}

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

עם זאת, בניגוד לאירוע unload, יש שימושים לגיטימיים באירוע beforeunload. לדוגמה, כשרוצים להזהיר את המשתמש שיש לו שינויים שלא נשמרו, והם יאבדו אם הוא ייצא מהדף. במקרה כזה, מומלץ להוסיף מאזינים ל-beforeunload רק כשיש למשתמש שינויים שלא נשמרו, ואז להסיר אותם מיד אחרי שמירת השינויים שלא נשמרו.

מה אסור לעשות
window.addEventListener('beforeunload', (event) => {
  if (pageHasUnsavedChanges()) {
    event.preventDefault();
    return event.returnValue = 'Are you sure you want to exit?';
  }
});
הקוד שלמעלה מוסיף מאזין beforeunload ללא תנאים.
מה מותר לעשות
function beforeUnloadListener(event) {
  event.preventDefault();
  return event.returnValue = 'Are you sure you want to exit?';
};

// A function that invokes a callback when the page has unsaved changes.
onPageHasUnsavedChanges(() => {
  window.addEventListener('beforeunload', beforeUnloadListener);
});

// A function that invokes a callback when the page's unsaved changes are resolved.
onAllChangesSaved(() => {
  window.removeEventListener('beforeunload', beforeUnloadListener);
});
הקוד שלמעלה מוסיף את המאזין beforeunload רק כשצריך (ומסיר אותו כשלא צריך).

צמצום השימוש ב-Cache-Control: no-store

Cache-Control: no-store היא כותרת HTTP ששרתי אינטרנט יכולים להגדיר בתגובות, ומורה לדפדפן לא לאחסן את התגובה במטמון HTTP כלשהו. צריך להשתמש באפשרות הזו במשאבים שמכילים מידע רגיש של משתמשים, למשל דפים שמוגנים באמצעות התחברות.

הרכיב fieldset, שמכיל כל קבוצת קלט (.fieldset-item), משתמש ב-gap: 1px כדי ליצור את הקווים הדקים בין הרכיבים. אין פתרון מסובך לגבי גבולות!

מילוי פער
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
טריק לגבי גבולות
.grid {
  display: grid;

  & > .fieldset-item {
    background: var(--bg-surface-2);

    &:not(:last-child) {
      border-bottom: 1px solid var(--bg-surface-1);
    }
  }
}

עיטוף טבעי של רשת

הפריסה המורכבת ביותר הייתה הפריסה ברמת המאקרו, מערכת הפריסה הלוגית בין <main> ל-<form>.

קלט
<input
  type="checkbox"
  id="text-notifications"
  name="text-notifications"
>
תווית
<label for="text-notifications">
  <h3>Text Messages</h3>
  <small>Get notified about all text messages sent to your device</small>
</label>

הרכיב fieldset, שמכיל כל קבוצת קלט (.fieldset-item), משתמש ב-gap: 1px כדי ליצור את גבולות הקו הדק בין הרכיבים. אין פתרון מסובך לשוליים!

מילוי פער
.grid {
  display: grid;
  gap: 1px;
  background: var(--bg-surface-1);

  & > .fieldset-item {
    background: var(--bg-surface-2);
  }
}
טריק לגבי גבולות
.grid {
  display: grid;

  & > .fieldset-item {
    background: var(--bg-surface-2);

    &:not(:last-child) {
      border-bottom: 1px solid var(--bg-surface-1);
    }
  }
}

פריסת הכרטיסיות <header>

הפריסה הבאה דומה מאוד: אני משתמש ב-flex כדי ליצור סדר אנכי.

HTML
<snap-tabs>
  <header>
    <nav></nav>
    <span class="snap-indicator"></span>
  </header>
  <section></section>
</snap-tabs>
CSS
header {
  display: flex;
  flex-direction: column;
}

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

אסטרטגיית Gentle Flex היא אסטרטגיה שמתמקדת רק במרכז המסך. התהליך רך ועדין, כי בניגוד ל-place-content: center, גודל התיבות של הצאצאים לא משתנה במהלך ההצמדה למרכז. כל הפריטים נערמים, מתמקדים ומרווחים בצורה עדינה ככל האפשר.

.gentle-flex {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1ch;
}
יתרונות
  • מטפל רק בהתאמה, בכיוון ובהפצה
  • עריכה ותחזוקה במקום אחד
  • Gap מבטיח מרחקים שווים בין n צאצאים
חסרונות
  • מספר שורות הקוד הגדול ביותר

מתאימות גם לפריסות מאקרו וגם לפריסות מיקרו.

שימוש

אפשר להשתמש ב-gap כערך של כל אורך או אחוז ב-CSS.

.gap-example {
  display: grid;
  gap: 10px;
  gap: 2ch;
  gap: 5%;
  gap: 1em;
  gap: 3vmax;
}


אפשר להעביר למאפיין Gap אורך של 1, והוא ישמש גם לשורה וגם לעמודה.

כתב מקוצר
.grid {
  display: grid;
  gap: 10px;
}
הגדרה של שורות ועמודות יחד בו-זמנית
בוצעה הרחבה
.grid {
  display: grid;
  row-gap: 10px;
  column-gap: 10px;
}


אפשר להעביר ל-Gap 2 אורכי מחרוזות, שישמשו לשורה ולעמודה.

כתב מקוצר
.grid {
  display: grid;
  gap: 10px 5%;
}
הגדרה של שורות ועמודות בנפרד בו-זמנית
בוצעה הרחבה
.grid {
  display: grid;
  row-gap: 10px;
  column-gap: 5%;
}