מה יקרה אם אספר לכם שיש יותר מחלון תצוגה אחד?
BRRRRAAAAAAAMMMMMMMMMM
אזור התצוגה שבו אתם משתמשים כרגע הוא למעשה אזור תצוגה בתוך אזור תצוגה.
BRRRRAAAAAAAMMMMMMMMMM
לפעמים הנתונים שמתקבלים מ-DOM מתייחסים לאחד מחלונות התצוגה האלה ולא לשני.
BRRRRAAAAM… רגע, מה?
זה נכון, אפשר לראות:
אזור התצוגה של הפריסה לעומת אזור התצוגה החזותי
בסרטון שלמעלה מוצג דף אינטרנט שאנחנו גוללים בו ומגדילים אותו באמצעות הצמדת האצבעות, לצד מפה בגודל מיני בצד שמאל שבה מוצג המיקום של חלונות התצוגה בדף.
כשגוללים באופן רגיל, הדברים די פשוטים. האזור הירוק מייצג את אזור התצוגה של הפריסה, שאליו מוצמדים הפריטים ב-position: fixed
.
העניינים מסתבכים כשמתחילים להשתמש בהגדלת התצוגה באמצעות צביטה. התיבה האדומה מייצגת את אזור התצוגה החזותית, שהוא החלק בדף שאנחנו יכולים לראות בפועל. אזור התצוגה הזה יכול לנוע בזמן שרכיבי position: fixed
נשארים במקום שבו הם היו, מחוברים לאזור התצוגה של הפריסה. אם נעביר את התצוגה לגבול של אזור התצוגה של הפריסה, אזור התצוגה של הפריסה ייגרר אחרי התצוגה.
שיפור התאימות
לצערנו, ממשקי ה-API לא עקביים לגבי חלון התצוגה שאליו הם מפנים, וגם לא עקביים בין הדפדפנים השונים.
לדוגמה, element.getBoundingClientRect().y
מחזירה את ההיסט בתוך אזור התצוגה של הפריסה. זה מגניב, אבל לרוב אנחנו רוצים לדעת את המיקום בדף, ולכן כותבים:
element.getBoundingClientRect().y + window.scrollY
עם זאת, בדפדפנים רבים נעשה שימוש באזור התצוגה החזותית עבור window.scrollY
, כלומר הקוד שלמעלה יתקלקל כשהמשתמש יבצע זום באמצעות צביטה.
ב-Chrome 61, השדה window.scrollY
משתנה כך שיתייחס לחלון התצוגה של הפריסה, כלומר הקוד שלמעלה פועל גם כשמשנים את הזום באמצעות צביטה. למעשה, הדפדפנים משנים בהדרגה את כל המאפיינים המיקומיים כך שיתייחסו לאזור התצוגה בפריסה.
מלבד נכס חדש אחד…
חשיפת אזור התצוגה החזותי לסקריפט
ממשק API חדש חושף את שדה הראייה החזותי בתור window.visualViewport
. זוהי טיוטת מפרט, עם אישור בדפדפנים שונים, והיא תגיע ל-Chrome 61.
console.log(window.visualViewport.width);
זה מה ש-window.visualViewport
נותן לנו:
visualViewport מלונות |
|
---|---|
offsetLeft
|
המרחק בין הקצה הימני של אזור התצוגה החזותי לבין אזור התצוגה של הפריסה, בפיקסלים של CSS. |
offsetTop
|
המרחק בין הקצה העליון של אזור התצוגה החזותי לאזור התצוגה של הפריסה, בפיקסלים של CSS. |
pageLeft
|
המרחק בין הקצה השמאלי של אזור התצוגה החזותית לבין הגבול השמאלי של המסמך, בפיקסלים של CSS. |
pageTop
|
המרחק בין הקצה העליון של אזור התצוגה החזותית לבין הגבול העליון של המסמך, בפיקסלים של CSS. |
width
|
רוחב אזור התצוגה החזותית בפיקסלים של CSS. |
height
|
הגובה של אזור התצוגה החזותית בפיקסלים של CSS. |
scale
|
קנה המידה שהוחל על ידי תנועת צביטה בזום. אם התוכן גדול פי שניים בגלל התכווננות, הפונקציה תחזיר את הערך 2 . הערך הזה לא מושפע מ-devicePixelRatio .
|
יש גם כמה אירועים:
window.visualViewport.addEventListener('resize', listener);
visualViewport אירועים |
|
---|---|
resize
|
הפעלה כשהערך של width , height או scale משתנה.
|
scroll
|
הפונקציה מופעלת כשהערך של offsetLeft או offsetTop משתנה.
|
הדגמה (דמו)
הסרטון שמופיע בתחילת המאמר נוצר באמצעות visualViewport
. אפשר לצפות בו ב-Chrome מגרסה 61 ואילך. בסרטון נעשה שימוש ב-visualViewport
כדי שהמפה המינימלית תיצמד לפינה השמאלית העליונה של אזור התצוגה החזותית, והיא מותאמת כך שתמיד תופיע באותו גודל, למרות שינוי מרחק התצוגה באמצעות צביטה.
דברים שחשוב לדעת
האירועים מופעלים רק כשחלון התצוגה החזותית משתנה
זה נשמע כמו עובדה מובנת מאליה, אבל זה הפתיע אותי כששיחקתי בפעם הראשונה עם visualViewport
.
אם גודל אזור התצוגה של הפריסה משתנה אבל אזור התצוגה החזותית לא משתנה, לא מתקבל אירוע resize
. עם זאת, לא סביר שאזור התצוגה בפריסה ישתנה ללא שינוי של רוחב או גובה אזור התצוגה החזותי.
הבעיה האמיתית היא גלילה. אם מתבצע גלילה, אבל אזור התצוגה החזותית נשאר סטטי ביחס לאזור התצוגה של הפריסה, לא מתקבל אירוע scroll
ב-visualViewport
, וזו תופעה נפוצה מאוד. במהלך גלילה רגילה במסמך, אזור התצוגה החזותית נשאר נעול בפינה הימנית העליונה של אזור התצוגה של הפריסה, כך ש-scroll
לא מופעל ב-visualViewport
.
אם רוצים לקבל הודעות על כל השינויים בחלון התצוגה החזותית, כולל pageTop
ו-pageLeft
, צריך להאזין גם לאירוע הגלילה של החלון:
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);
הימנעות מכפילות עבודה עם מספר מאזינים
בדומה להאזנה ל-scroll
ו-resize
בחלון, סביר להניח שתפעילו כתוצאה מכך פונקציית 'עדכון' כלשהי. עם זאת, לרוב רבים מהאירועים האלה מתרחשים בו-זמנית. אם המשתמש ישנה את גודל החלון, האירוע resize
יופעל, אבל לעיתים קרובות גם האירוע scroll
. כדי לשפר את הביצועים, כדאי להימנע משינוי אותו פריט כמה פעמים:
// Add listeners
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
addEventListener('scroll', update);
let pendingUpdate = false;
function update() {
// If we're already going to handle an update, return
if (pendingUpdate) return;
pendingUpdate = true;
// Use requestAnimationFrame so the update happens before next render
requestAnimationFrame(() => {
pendingUpdate = false;
// Handle update here
});
}
הגשתי דיווח על בעיה בנושא הזה, כי לדעתי יכול להיות שיש דרך טובה יותר, כמו אירוע update
יחיד.
פונקציות שמטפלות באירועים לא פועלות
בגלל באג ב-Chrome, הפעולה הזו לא פועלת:
פגום – נעשה שימוש בגורם שמטפל באירועים
visualViewport.onscroll = () => console.log('scroll!');
במקום זאת:
פועל – משתמש ב-event listener
visualViewport.addEventListener('scroll', () => console.log('scroll'));
ערכי ההיסט מעוגלים
לדעתי (טוב, אני מקווה) מדובר בבאג נוסף ב-Chrome.
הערכים של offsetLeft
ו-offsetTop
עגולים, והם לא מדויקים במיוחד אחרי שהמשתמש מגדיל את התצוגה. אפשר לראות את הבעיות האלה במהלך הדגמה – אם המשתמש מגדיל את התצוגה ומזיז את המפה לאט, המפה המינימלית קופצת בין פיקסלים ללא זום.
קצב האירועים איטי
כמו אירועים אחרים מסוג resize
ו-scroll
, האירועים האלה לא מופעלים בכל פריים, במיוחד בנייד. אפשר לראות את זה במהלך הדגמה – אחרי שמבצעים התכווננות באמצעות צביטה, המפה המיניאטורית מתקשה להישאר מוצמדת למסך.
נגישות
בהדגמה השתמשתי ב-visualViewport
כדי לבטל את התכונה של המשתמש להגדלת התצוגה באמצעות צביטה. זה הגיוני בדמו הזה, אבל כדאי לחשוב היטב לפני שמבצעים פעולה שמבטלת את הרצון של המשתמש להתקרב.
אפשר להשתמש ב-visualViewport
כדי לשפר את הנגישות. לדוגמה, אם המשתמש מגדיל את התצוגה, תוכלו להסתיר פריטים position: fixed
דקורטיביים כדי שהם לא יפריעו לו. עם זאת, חשוב להיזהר שלא תסתירו משהו שהמשתמש מנסה לבחון לעומק.
כדאי לשקול לפרסם נתונים בשירות ניתוח נתונים כשהמשתמש מגדיל את התצוגה. כך תוכלו לזהות דפים שבהם המשתמשים מתקשים לראות את התוכן ברמת הזום שמוגדרת כברירת מחדל.
visualViewport.addEventListener('resize', () => {
if (visualViewport.scale > 1) {
// Post data to analytics service
}
});
זה הכול! visualViewport
הוא ממשק API קטן ונוח שמאפשר לכם לפתור בעיות תאימות לאורך הדרך.