ניתוח מעמיק של אירוע JavaScript

preventDefault ו-stopPropagation: מתי להשתמש בכל שיטה ומה בדיוק עושה כל שיטה.

טיפול באירועים ב-JavaScript הוא לרוב פשוט. הדבר נכון במיוחד כאשר עוסקים במבנה HTML פשוט (שטוח יחסית). עם זאת, כשאירועים עוברים (או מתפשטים) דרך היררכיה של רכיבים, הדברים נעשים קצת יותר מורכבים. בדרך כלל, המפתחים פונים אל stopPropagation() ו/או preventDefault() כדי לפתור את הבעיות שהם נתקלים בהן. אם חשבתי אי פעם "אני פשוט אנסה את preventDefault(), ואם זה לא עובד, אנסה את stopPropagation() ואם זה לא עובד, אנסה את שניהם", המאמר הזה מיועד לך! אסביר בדיוק מה עושה כל שיטה, מתי להשתמש בכל אחת מהן, ואספק לכם דוגמאות מגוונות שתוכלו ליישם. המטרה שלי היא לסיים את הבלבול שלך אחת ולתמיד.

לפני שנמשיך, חשוב להתייחס בקצרה לשני הסוגים של טיפול באירועים שאפשר לבצע ב-JavaScript (כלומר בכל הדפדפנים המודרניים – Internet Explorer לפני גרסה 9 לא תמך בכלל בתיעוד אירועים).

סגנונות של אירועים (תיעוד ותהליך ההעברה של אירועים)

כל הדפדפנים המודרניים תומכים בתיעוד אירועים, אבל מפתחים משתמשים בתיעוד אירועים רק לעיתים רחוקות מאוד. באופן מעניין, זו הייתה הדרך היחידה לאירועים ש-Netscape תמכה בה במקור. היריב הגדול ביותר של Netscape, Microsoft Internet Explorer, לא תמך בכלל בתיעוד אירועים, אלא רק בסגנון אחר של אירועים שנקרא 'העברת אירועים'. כשה-W3C הוקם, הם מצאו יתרונות בשני הסגנונות של אירועים והצהירו שדפדפנים צריכים לתמוך בשניהם, באמצעות פרמטר שלישי ל-method‏ addEventListener. במקור, הפרמטר הזה היה רק בוליאני, אבל כל הדפדפנים המודרניים תומכים באובייקט options בתור הפרמטר השלישי. אפשר להשתמש באובייקט הזה כדי לציין (בין היתר) אם רוצים להשתמש בתיעוד אירועים או לא:

someElement.addEventListener('click', myClickHandler, { capture: true | false });

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

תיעוד אירועים

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

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

<html>
  <body>
    <div id="A">
      <div id="B">
        <div id="C"></div>
      </div>
    </div>
  </body>
</html>
document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('#C was clicked');
  },
  true,
);

כשמשתמש לוחץ על הרכיב #C, אירוע שמקורו ב-window נשלח. לאחר מכן, האירוע הזה יעבור אל הצאצאים שלו באופן הבא:

window => document => <html> => <body> => וכן הלאה, עד שהיא מגיעה ליעד.

זה לא משנה אם שום דבר לא מאזין לאירוע קליק ברכיב window או document או <html> או <body> (או כל רכיב אחר בדרך ליעד). האירוע עדיין מתחיל ב-window ומתחיל את המסע שלו כפי שמתואר למעלה.

בדוגמה שלנו, אירוע הקליק יופץ (זו מילה חשובה כי היא קשורה באופן ישיר לאופן הפעולה של ה-method stopPropagation() ונסביר בהמשך המסמך) מה-window אל רכיב היעד שלו (במקרה הזה, #C) דרך כל רכיב בין window ל-#C.

פירוש הדבר הוא שאירוע הקליק יתחיל ב-window, והדפדפן יציג את השאלות הבאות:

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

לאחר מכן, האירוע יתפשט אל document והדפדפן ישאל: "האם יש משהו שמקשיב לאירוע לחיצה ב-document בשלב הצילום?" אם כן, גורמי הטיפול באירועים המתאימים יופעלו.

בשלב הבא, האירוע יופץ אל הרכיב <html>, והדפדפן ישאל: "האם יש משהו שמאזינים לקליק על הרכיב <html> בשלב הצילום?" במקרה כזה, המטפלים באירועים המתאימים יופעלו.

לאחר מכן, האירוע יתפשט לאלמנט <body> והדפדפן ישאל: "האם יש משהו שמקשיב לאירוע קליק על אלמנט <body> בשלב הצילום?" במקרה כזה, הגורמים המטפלים באירועים המתאימים יופעלו.

לאחר מכן, האירוע יעבור לרכיב #A. שוב, הדפדפן ישאל: "האם משהו מאזין לאירוע קליק ב-#A בשלב הצילום, ואם כן, הגורמים המטפלים באירועים המתאימים יופעלו.

לאחר מכן, האירוע יתפשט לרכיב #B (ותוצג אותה שאלה).

לבסוף, האירוע יגיע ליעד שלו והדפדפן ישאל: "האם יש משהו שמקשיב לאירוע קליק על האלמנט #C בשלב הצילום?" הפעם התשובה היא 'כן!' פרק הזמן הקצר הזה שבו האירוע נמצא ביעד נקרא 'שלב היעד'. בשלב הזה, פונקציית הטיפול באירוע תופעל, הדפדפן יפרסם את הקוד console.log‏ "#C was clicked" ואז נהיה מוכנים, נכון? לא נכון! אנחנו לא סיימנו בכלל. התהליך ממשיך, אבל עכשיו הוא עובר לשלב הבועות.

אירועים שמתרחשים ברמה העליונה

יוצג בדפדפן השאלה הבאה:

"האם יש משהו שמקשיב לאירוע לחיצה ב-#C בשלב ההעברה (bubbling)?" חשוב לשים לב כאן. אפשר להאזין לקליקים (או לכל סוג אירוע אחר) גם בשלב הצילום וגם בשלב הבועה. אם מחברים בוררים לאירועים בשני השלבים (למשל, על ידי קריאה ל-.addEventListener() פעמיים, פעם עם capture = true ופעם עם capture = false), אז כן, שני הבוררים לאירועים יופעלו באותו רכיב. עם זאת, חשוב גם לציין שהם מופעלים בשלבים שונים (אחד בשלב התיעוד ועוד אחד בשלב ההעברה לטיפול ברמה הבאה).

לאחר מכן, האירוע יתפשט (המונח הנפוץ יותר הוא 'התרחבות' כי נראה שהאירוע נע "מעלה" בעץ ה-DOM) לרכיב ההורה שלו, #B, והדפדפן ישאל: "האם יש משהו שמקשיב לאירועי קליק ב-#B בשלב ההתרחבות?" בדוגמה שלנו, אף אחד מהם לא מוגדר, ולכן אף בורר לא יופעל.

לאחר מכן, האירוע יעלה אל #A והדפדפן ישאל: "האם יש משהו שמקשיב לאירועי לחיצה ב-#A בשלב ההעברה (bubbling)?"

לאחר מכן, האירוע יעלה אל <body>: "האם יש משהו שמקשיב לאירועי קליקים על הרכיב <body> בשלב ההעברה (bubbling)?"

לאחר מכן, האלמנט <html>: "האם יש משהו שמקשיב לאירועי קליקים באלמנט <html> בשלב הבועה?

לאחר מכן, document: "האם יש משהו שמאזינים לאירועים מסוג קליק ב-document בשלב המבעבע?"

לבסוף, ה-window: "האם יש משהו שמקשיב לאירועי לחיצה בחלון בשלב הבועה?"

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

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

<html>
  <body>
    <div id="A">
      <div id="B">
        <div id="C"></div>
      </div>
    </div>
  </body>
</html>
document.addEventListener(
  'click',
  function (e) {
    console.log('click on document in capturing phase');
  },
  true,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
  'click',
  function (e) {
    console.log('click on <html> in capturing phase');
  },
  true,
);
document.body.addEventListener(
  'click',
  function (e) {
    console.log('click on <body> in capturing phase');
  },
  true,
);
document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('click on #A in capturing phase');
  },
  true,
);
document.getElementById('B').addEventListener(
  'click',
  function (e) {
    console.log('click on #B in capturing phase');
  },
  true,
);
document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('click on #C in capturing phase');
  },
  true,
);

document.addEventListener(
  'click',
  function (e) {
    console.log('click on document in bubbling phase');
  },
  false,
);
// document.documentElement == <html>
document.documentElement.addEventListener(
  'click',
  function (e) {
    console.log('click on <html> in bubbling phase');
  },
  false,
);
document.body.addEventListener(
  'click',
  function (e) {
    console.log('click on <body> in bubbling phase');
  },
  false,
);
document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('click on #A in bubbling phase');
  },
  false,
);
document.getElementById('B').addEventListener(
  'click',
  function (e) {
    console.log('click on #B in bubbling phase');
  },
  false,
);
document.getElementById('C').addEventListener(
  'click',
  function (e) {
    console.log('click on #C in bubbling phase');
  },
  false,
);

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

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"
"click on <body> in bubbling phase"
"click on <html> in bubbling phase"
"click on document in bubbling phase"

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

event.stopPropagation()

עכשיו, אחרי שהבנו מאיפה מגיעים האירועים ואיך הם עוברים (כלומר מתפשטים) דרך ה-DOM גם בשלב התיעוד וגם בשלב ההעברה, אפשר להפנות את תשומת הלב אל event.stopPropagation().

אפשר להפעיל את השיטה stopPropagation() באירועי DOM מקומיים (ברובם). כתבתי "רוב" כי יש כמה אירועים שבהם קריאה לשיטה הזו לא תעשה כלום (כי האירוע לא מופץ מלכתחילה). אירועים כמו focus,‏ blur,‏ load,‏ scroll ועוד כמה אירועים נכללים בקטגוריה הזו. אפשר לקרוא ל-stopPropagation() אבל לא יקרה שום דבר מעניין, כי האירועים האלה לא מופצים.

אבל מה stopPropagation עושה?

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

נחזור לאותם תגי העיצוב לדוגמה. מה לדעתכם יקרה אם נקרא stopPropagation() בשלב הצילום ברכיב #B?

הפלט הבא אמור להתקבל:

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"

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

מה דעתך על הפסקת ההעברה ב-#A בשלב הבועות? הפלט שיוצג יהיה:

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"
"click on #C in bubbling phase"
"click on #B in bubbling phase"
"click on #A in bubbling phase"

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

עוד אחת, רק בשביל הכיף. מה קורה אם קוראים ל-stopPropagation() בשלב היעד של #C? חשוב לזכור ש'שלב היעד' הוא השם שניתן לתקופה שבה האירוע ב היעד. הפלט שיוצג יהיה:

"click on document in capturing phase"
"click on <html> in capturing phase"
"click on <body> in capturing phase"
"click on #A in capturing phase"
"click on #B in capturing phase"
"click on #C in capturing phase"

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

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

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

event.stopImmediatePropagation()

מהי השיטה המוזרה הזו, שלא נעשה בה שימוש לעיתים קרובות? היא דומה ל-stopPropagation, אבל במקום למנוע מאירוע לעבור לצאצאים (תיעוד) או לאבות (העברה דרך שכבות), השיטה הזו חלה רק כשיש יותר ממנגנון טיפול אחד באירועים שמקושר לרכיב יחיד. מכיוון ש-addEventListener() תומך בסגנון אירועים של multicast, אפשר לקשר בורר אירועים לרכיב יחיד יותר מפעם אחת. במקרה כזה (ברוב הדפדפנים), מנהלי האירועים מופעל בסדר שבו הם חוברו. קריאה ל-stopImmediatePropagation() מונעת הפעלה של מנהלים נוספים. עיינו בדוגמה הבאה:

<html>
  <body>
    <div id="A">I am the #A element</div>
  </body>
</html>
document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('When #A is clicked, I shall run first!');
  },
  false,
);

document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('When #A is clicked, I shall run second!');
    e.stopImmediatePropagation();
  },
  false,
);

document.getElementById('A').addEventListener(
  'click',
  function (e) {
    console.log('When #A is clicked, I would have run third, if not for stopImmediatePropagation');
  },
  false,
);

הדוגמה שלמעלה תוביל לפלט הבא של המסוף:

"When #A is clicked, I shall run first!"
"When #A is clicked, I shall run second!"

שימו לב שבמקרה הזה פונקציית הטיפול באירוע השלישית אף פעם לא פועלת כי פונקציית הטיפול באירוע השנייה קוראת ל-e.stopImmediatePropagation(). אם במקום זאת נקרא e.stopPropagation(), ה-handler השלישי עדיין היה רץ.

event.preventDefault()

אם stopPropagation() מונע מאירוע לעבור "למטה" (תיעוד) או "למעלה" (העברה ברמה גבוהה יותר), מה preventDefault() עושה? נראה שהוא עושה משהו דומה. האם כן?

לא ממש. בדרך כלל מתבלבלים בין השניים, אבל למעשה אין ביניהם הרבה קשר. כשמופיעה המילה preventDefault(), מוסיפים בראשה את המילה 'פעולה'. חשבו על 'מניעת פעולת ברירת המחדל'.

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

נתחיל בדוגמה פשוטה מאוד. מה אתם מצפים שיקרה כשאתם לוחצים על קישור בדף אינטרנט? ברור שציפיתם שהדפדפן ינתב לכתובת ה-URL שצוינה בקישור הזה. במקרה כזה, האלמנט הוא תג עוגן והאירוע הוא אירוע קליק. לשילוב הזה (<a> + click) יש 'פעולת ברירת מחדל' של ניווט אל הערך של href של הקישור. מה קורה אם רוצים למנוע מהדפדפן לבצע את פעולת ברירת המחדל הזו? כלומר, נניח שרוצים למנוע מהדפדפן לנווט לכתובת ה-URL שצוינה במאפיין href של האלמנט <a>. זה מה ש-preventDefault() תעשה בשבילכם. עיין בדוגמה זו:

<a id="avett" href="https://www.theavettbrothers.com/welcome">The Avett Brothers</a>
document.getElementById('avett').addEventListener(
  'click',
  function (e) {
    e.preventDefault();
    console.log('Maybe we should just play some of their music right here instead?');
  },
  false,
);

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

בדרך כלל, לחיצה על הקישור עם התווית The Avett Brothers תוביל לגלישה אל www.theavettbrothers.com. במקרה הזה, חיברנו רכיב לטיפול באירועי קליקים לרכיב <a> והצבענו על כך שצריך למנוע את פעולת ברירת המחדל. לכן, כשמשתמש לוחץ על הקישור הזה, הוא לא מועבר לאף מקום. במקום זאת, ביומן של המסוף מתועדת ההודעה "Maybe we should just play some of their music right here instead?"

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

  • רכיב <form> + אירוע 'שליחה': preventDefault() בשילוב הזה ימנע שליחת טופס. האפשרות הזו שימושית אם רוצים לבצע אימות, ואם משהו נכשל, אפשר לבצע קריאה מותנית ל-preventDefault כדי למנוע שליחת הטופס.

  • אלמנט <a> + אירוע 'קליק': preventDefault() בשילוב הזה, הדפדפן מונע מעבר לכתובת ה-URL שצוינה במאפיין href של האלמנט <a>.

  • document + אירוע 'גלגלת העכבר': preventDefault() בשילוב הזה, גלילה בדף באמצעות גלגלת העכבר לא תתבצע (אבל גלילה באמצעות המקלדת עדיין תעבוד).
    ↜ לשם כך צריך להפעיל את addEventListener() עם { passive: false }.

  • document + אירוע 'keydown': preventDefault() השילוב הזה קטלני. היא הופכת את הדף ללא שימושי במידה רבה, ומונע גלילה במקלדת, מעבר בין כרטיסיות והדגשה במקלדת.

  • document + אירוע "mousedown": preventDefault() בשילוב הזה ימנע הדגשת טקסט באמצעות העכבר וכל פעולת "ברירת מחדל" אחרת שאפשר יהיה להפעיל באמצעות העכבר למטה.

  • רכיב <input> + אירוע 'keypress': preventDefault() בשילוב הזה מונעים מאותיות שהמשתמש הקליד להגיע לרכיב הקלט (אבל אל תעשו זאת, כי יש לכך סיבה תקפה רק לעתים נדירות, אם בכלל).

  • document + אירוע 'contextmenu': preventDefault() השילוב הזה מונע את הצגת תפריט ההקשר המקורי של הדפדפן כשהמשתמש לוחץ לחיצה ימנית או לוחץ לחיצה ארוכה (או בכל דרך אחרת שבה תפריט ההקשר עשוי להופיע).

זו רשימה חלקית בלבד, אבל אנחנו מקווים שהיא נותנת לך מושג טוב על אופן השימוש ב-preventDefault().

מתיחה משעשעת?

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

function preventEverything(e) {
  e.preventDefault();
  e.stopPropagation();
  e.stopImmediatePropagation();
}

document.addEventListener('click', preventEverything, true);
document.addEventListener('keydown', preventEverything, true);
document.addEventListener('mousedown', preventEverything, true);
document.addEventListener('contextmenu', preventEverything, true);
document.addEventListener('mousewheel', preventEverything, { capture: true, passive: false });

לא ברור לי למה בכלל תרצו לעשות את זה (חוץ מאשר כדי להצחיק מישהו), אבל כדאי לחשוב על מה שקורה כאן ולהבין למה זה יוצר את המצב הזה.

כל האירועים מגיעים מ-window, כך שקטע הקוד הזה: אנחנו עוצרים, מפסיקים לפעול, את כל האירועים של click, keydown, mousedown, contextmenu ו-mousewheel כך שלא יגיעו אי פעם לרכיבים שעשויים להאזין להם. אנחנו גם קוראים ל-stopImmediatePropagation כדי שכל מנהלים שמחוברים למסמך אחרי זה ייחסמו גם הם.

חשוב לדעת ש-stopPropagation() ו-stopImmediatePropagation() הם לא (לפחות לא בעיקר) הגורמים שהופכים את הדף ללא שימושי. הן פשוט מונעות מאירועים להגיע למקום שאליו הם היו מגיעים אחרת.

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

הדגמות בזמן אמת

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

תודות

התמונה הראשית (Hero) של Tom Wilson ב-Unsplash.