האמון הוא טוב, התצפית טובה יותר: 'הצטלבות' בגרסה 2

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

Intersection Observer v1 הוא אחד מממשקי ה-API שכנראה אוהבים ברחבי העולם, ועכשיו גם Safari תומך בכך, בסופו של דבר אפשר גם להשתמש בו בכל הדפדפנים המובילים. כדי לרענן את הידע שלכם במהירות בנושא ה-API: אני ממליץ לצפות בסרטון של Surma Supercharged MicroTip במקטע Observer v1 שמוטמע למטה. אפשר גם לקרוא את ההסבר המפורט של Surma מאמר. אנשים השתמשו ב-Intersection Observer v1 למגוון רחב של תרחישים לדוגמה, טעינה מדורגת של תמונות וסרטונים, קבלת התראות כשרכיבים מגיעים אל position: sticky, הפעלת אירועים של ניתוח נתונים, ועוד הרבה יותר.

לקבלת הפרטים המלאים, אפשר לעיין Intersection Observer docs ב-MDN, אבל כתזכורת קצרה, כך נראה ממשק ה-API של Intersection Observer v1 מקרה בסיסי:

const onIntersection = (entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      console.log(entry);
    }
  }
};

const observer = new IntersectionObserver(onIntersection);
observer.observe(document.querySelector('#some-target'));

מה מאתגר ב-Intersection Observer v1?

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

לגבי רכיב במסמך ברמה העליונה, אפשר לקבוע את המידע הזה באמצעות ניתוח את ה-DOM דרך JavaScript, לדוגמה דרך DocumentOrShadowRoot.elementFromPoint() ואז לחקור לעומק. לעומת זאת, לא ניתן לקבל את אותו מידע אם הרכיב המדובר ב-iframe של צד שלישי.

מדוע החשיפה בפועל חשובה כל כך?

לצערנו, האינטרנט הוא מקום שמושך גורמים זדוניים עם כוונות גרועות יותר. לדוגמה, בעל אתר מוצלח שמציג מודעות בתשלום לפי קליק (PPC) באתר תוכן עשוי לקבל תמריצים כדי לגרום לאנשים ללחוץ על המודעות שלהם במטרה להגדיל את התשלום שיועבר לבעלי התוכן הדיגיטלי (לפחות למשך תקופה קצרה, עד שרשת המודעות תאתר אותם). בדרך כלל, מודעות כאלה מוצגות במסגרות iframe. אם בעל האתר ירצה לגרום למשתמשים ללחוץ על מודעות כאלה, הוא יוכל ליצור iframes של המודעה. שקופים לחלוטין על ידי החלת כלל CSS iframe { opacity: 0; } ושכבת-על של מסגרות ה-iframe בנוסף למשהו מושך, כמו סרטון חתולים חמוד שהמשתמשים באמת ירצו ללחוץ עליו. פעולה כזו נקראת חטיפת קליקים. אפשר לראות מתקפה כזו מסוג 'חטיפת קליקים' בפעולה בחלק העליון demo (נסו "לצפות" בסרטון החתולים) ולהפעיל את 'מצב טריק'). תוכלו לראות שהמודעה ב-iframe "חושב" הוא קיבל קליקים לגיטימיים, גם אם שקוף לגמרי כשאתה (מעמיד פנים שלא מרצון) לחצת עליו.

הטעיית משתמשים כדי לגרום להם ללחוץ על מודעה באמצעות עיצוב שקוף ושכבת-על מעל משהו מושך.

איך הכלי Intersection Observer v2 פותר את הבעיה?

Intersection Observer v2 מציג את המושג מעקב אחר ה'חשיפה' בפועל של יעד שבני אדם יגדירו אותו. על ידי הגדרת אפשרות IntersectionObserver constructor, מצטלב IntersectionObserverEntry והמכונות יכילו שדה בוליאני חדש בשם isVisible. הערך של true במאפיין isVisible הוא אחריות משמעותית מההטמעה הבסיסית שרכיב היעד לא מוסתר לגמרי על ידי תוכן אחר ושאין בו אפקטים חזותיים שיכולים לשנות או לעוות את התצוגה שלו על המסך. לעומת זאת, ערך false פירושו שההטמעה לא יכולה להבטיח זאת.

פרט חשוב מפרט האם היישום מותר לדווח על תוצאות שליליות כוזבות (כלומר, הגדרת isVisible ל-false גם כשרכיב היעד גלוי במלואו ולא בוצעו בו שינויים). מטעמי ביצועים או מסיבות אחרות, דפדפנים מגבילים את עצמם לעבודה עם תיבות וגיאומטריה ישרית ליניארית; הם לא מנסים להשיג תוצאות מושלמות של פיקסלים שינויים כמו border-radius.

עם זאת, תוצאות חיוביות כוזבות אסורות בשום מקרה (כלומר, isVisible עד true אם רכיב היעד לא גלוי במלואו ולא בוצעו בו שינויים).

איך הקוד החדש נראה בפועל?

ה-constructor של IntersectionObserver משתמש עכשיו בשני מאפיינים נוספים של הגדרות אישיות: delay ו-trackVisibility. delay הוא מספר שמציין את משך ההשהיה המינימלי באלפיות השנייה בין התראות מאת הצופה ביעד נתון. trackVisibility הוא ערך בוליאני שמציין אם הצופה יעקוב אחר שינויים החשיפה.

חשוב לציין כאן שכאשר התאריך trackVisibility הוא true, חובה לציין את 'delay' בכתובת לפחות 100 (כלומר, לא יותר מהתראה אחת כל 100 אלפיות השנייה). כפי שציינו קודם, חישוב החשיפה עולה, והדרישה הזו היא אמצעי זהירות מפני ירידה בביצועים וצריכת הסוללה. המפתח האחראי ישתמש ב הערך הסביל הגדול ביותר לעיכוב.

בהתאם לדיווח הנוכחי המפרט, החשיפה היא מחושב באופן הבא:

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

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

  • אם ליעד, או לכל רכיב בשרשרת הבלוקים שלו, יש אטימוּת יעילה שאינה 1.0, היעד נחשב לבלתי נראה.

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

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

פירוש הדבר הוא שההטמעות הנוכחיות די שמרניות ומבטיחות חשיפה. לדוגמה, החלה של מסנן גווני אפור שכמעט לא ניתן להבחין בהם, כמו filter: grayscale(0.01%) או הגדרת שקיפות כמעט בלתי נראית של opacity: 0.99 תגרום לרינדור של הרכיב בלתי נראה.

בהמשך מופיע קוד קצר שממחיש את תכונות ה-API החדשות. ניתן לראות את המעקב אחר קליקים את הלוגיקה בפעולה בקטע השני של ההדגמה (אבל עכשיו נסו "לצפות" בסרטון של הגור). חשוב לזכור להפעיל את 'מצב טריק' שוב באופן מיידי תהפכו את עצמכם לאתרי חדשות מוצללים, ותראו איך Intersection Observer v2 מונע מעקב אחר קליקים לא לגיטימיים על מודעות. הפעם, גרסה 2 של Intersection Observer בחזרה! 🎉

Intersection Observer v2 מונע קליק לא מכוון על מודעה.

<!DOCTYPE html>
<!-- This is the ad running in the iframe -->
<button id="callToActionButton">Buy now!</button>
// This is code running in the iframe.

// The iframe must be visible for at least 800ms prior to an input event
// for the input event to be considered valid.
const minimumVisibleDuration = 800;

// Keep track of when the button transitioned to a visible state.
let visibleSince = 0;

const button = document.querySelector('#callToActionButton');
button.addEventListener('click', (event) => {
  if ((visibleSince > 0) &&
      (performance.now() - visibleSince >= minimumVisibleDuration)) {
    trackAdClick();
  } else {
    rejectAdClick();
  }
});

const observer = new IntersectionObserver((changes) => {
  for (const change of changes) {
    // ⚠️ Feature detection
    if (typeof change.isVisible === 'undefined') {
      // The browser doesn't support Intersection Observer v2, falling back to v1 behavior.
      change.isVisible = true;
    }
    if (change.isIntersecting && change.isVisible) {
      visibleSince = change.time;
    } else {
      visibleSince = 0;
    }
  }
}, {
  threshold: [1.0],
  // 🆕 Track the actual visibility of the element
  trackVisibility: true,
  // 🆕 Set a minimum delay between notifications
  delay: 100
}));

// Require that the entire iframe be visible.
observer.observe(document.querySelector('#ad'));

אישורים

תודה לסימון וינסנט, יואב וייס ומתיאס ביינס על קריאת המאמר הזה, וגם על Stefan Zager לבדיקה ולהטמעה של התכונה ב-Chrome. התמונה הראשית (Hero) של סרגיי סמין בסרטון Unbounce.