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

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

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

לפרטים המלאים, אפשר לעיין במסמכי Intersection Reporter ב-MDN, אבל חשוב לזכור שכך נראה ה-API של Intersection Exploreer 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'));

מה הבעיה בגרסה 1 של Intersection Viewer?

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

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

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

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

הטעיית משתמש כדי שילחץ על מודעה על ידי עיצובה שקופה והוספתה כשכבת-על מעל משהו מושך.

איך לפתור את הבעיה הזאת בגרסה 2 של Intersection Viewer?

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

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

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

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

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

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

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

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

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

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

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

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

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

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

גרסה 2 של כלי הצפייה הבין-לאומי מונעת קליק לא מכוון על מודעה.

<!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'));

אישורים

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