מבט מבפנים על דפדפן אינטרנט מודרני (חלק 4)

Mariko Kosaka

הקלט מגיע למצב Compositor

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

הזנת אירועים מנקודת המבט של הדפדפן

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

כאשר מתבצעת תנועת משתמש כמו נגיעה במסך, תהליך הדפדפן הוא זה שמקבל את בתור התחלה. עם זאת, תהליך הדפדפן מודע רק למקום שבו התנועה התרחשה מאז תוכן שנמצא בתוך כרטיסייה מטופל על ידי תהליך הרינדור. התהליך בדפדפן שולח את האירוע, סוג (למשל touchstart) והקואורדינטות שלו לתהליך כלי הרינדור. תהליך הרינדור מטפל את האירוע המתאים באמצעות מציאת יעד האירוע והפעלת פונקציות event listener המצורפים.

אירוע של קלט
איור 1: אירוע קלט שמנותב דרך תהליך הדפדפן לתהליך הרינדור

המרכיב מקבל אירועי קלט

איור 2: אזור התצוגה מעביר את העכבר מעל שכבות הדף

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

הסבר על אזורים שאינם ניתנים לגלילה מהירה

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

אזור מוגבל שלא ניתן לגלול במהירות
איור 3: תרשים של קלט מתואר לאזור של מודעות שלא ניתן לגלול במהירות

חשוב לשים לב כשכותבים גורמים מטפלים באירועים.

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

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault();
    }
});

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

אזור של דף מלא שלא ניתן לגלול במהירות
איור 4: תרשים של קלט מתואר לאזור של מודעות שלא ניתן לגלול במהירות, שמשתרע על פני דף שלם

כדי לצמצם את הסיכון, אפשר להעביר את האפשרויות של passive: true באירוע ל-הקשיב. כך תרמז לדפדפן שאתם עדיין רוצים להאזין לאירוע בשרשור הראשי. אבל compositor יכול להמשיך ליצור גם פריים חדש מורכב.

document.body.addEventListener('touchstart', event => {
    if (event.target === area) {
        event.preventDefault()
    }
 }, {passive: true});

איך בודקים אם אפשר לבטל את האירוע

גלילה בדף
איור 5: דף אינטרנט שבו חלק מהדף קבוע לגלילה אופקית

נניח שיש בדף תיבה שבה אתם רוצים להגביל את כיוון הגלילה לגלילה אופקית בלבד.

שימוש באפשרות passive: true באירוע הסמן משמעותו שהגלילה בדף יכולה להיות חלקה, אבל כדי להגביל את מספר הגלילה האנכית, יכול להיות שהגלילה האנכית תתחיל עד השעה שבה את רוצה preventDefault כיוון הגלילה. אפשר לבדוק את זה באמצעות השיטה event.cancelable.

document.body.addEventListener('pointermove', event => {
    if (event.cancelable) {
        event.preventDefault(); // block the native scroll
        /*
        *  do what you want the application to do here
        */
    }
}, {passive: true});

לחלופין, אפשר להשתמש בכלל CSS כמו touch-action כדי להסיר לחלוטין את הגורם המטפל באירועים.

#area {
  touch-action: pan-x;
}

איך לאתר את יעד האירוע

בדיקת הצלחה
איור 6: ה-thread הראשי שמביט ברשומות הצבע ושואל מה משורטט בנקודת x.y

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

צמצום מספר שליחת האירועים ל-thread הראשי

בפוסט הקודם, הסברנו איך התצוגה הטיפוסית שלנו מרעננת את המסך 60 פעמים בשנייה איך אנחנו צריכים לעמוד בקצב של אנימציה חלקה. כדי להזין את הקלט, מסך מגע רגיל מכשיר מספק אירוע מגע 60-120 פעמים בשנייה, ועכבר טיפוסי מבצע אירועים 100 פעמים שנייה. רמת הדיוק של אירוע הקלט גבוהה יותר מזו שהמסך שלנו יכול לרענן.

אם אירוע רציף כמו touchmove נשלח ל-thread הראשי 120 פעמים בשנייה, עלול להפעיל כמות מופרזת של בדיקות התאמה וביצוע JavaScript, בהשוואה למידת האיטיות ניתן לרענן את המסך.

אירועים ללא סינון
איור 7: אירועים שמציפים את ציר הזמן של המסגרת וגורמים לבעיות בממשק הדף

כדי לצמצם קריאות מיותרות ל-thread הראשי, Chrome מקבץ אירועים רציפים (כמו wheel, mousewheel, mousemove, pointermove, touchmove ) ועיכובים במשלוחים עד ממש לפני requestAnimationFrame הבא.

אירועים מאוחדים
איור 8: אותו ציר זמן כמו קודם, אבל יש איחוד ועיכוב של האירוע

אירועים נפרדים כמו keydown, keyup, mouseup, mousedown, touchstart ו-touchend נשלחים באופן מיידי.

שימוש ב-getCoalescedEvents כדי לקבל אירועים בתוך הפריים

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

getCoalescedEvents
איור 9: נתיב תנועת מגע חלק בצד שמאל, נתיב מוגבל משולב בצד ימין
window.addEventListener('pointermove', event => {
    const events = event.getCoalescedEvents();
    for (let event of events) {
        const x = event.pageX;
        const y = event.pageY;
        // draw a line using x and y coordinates.
    }
});

השלבים הבאים

בסדרה הזו עסקנו בתפעול הפנימי של דפדפן אינטרנט. אם מעולם לא חשבתם למה ההמלצה של כלי הפיתוח היא להוסיף את {passive: true} לגורם שמטפל באירועים, או להסביר למה כדאי לכתוב async בתג הסקריפט שלך, אני מקווה שהסדרה הזו הצליחה להסביר למה דפדפן צריך כדי לספק חוויית אינטרנט מהירה וחלקה יותר.

שימוש ב-Lighthouse

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

איך מודדים ביצועים

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

הוספת מדיניות של תכונות לאתר

אם ברצונך לעשות צעד נוסף, מדיניות התכונות היא מדיניות חדשה תכונה של פלטפורמת אינטרנט שיכולה לשמש אתכם כשכבת הגנה בזמן בניית הפרויקט. מתבצעת הפעלה המדיניות בנושא תכונות מבטיחה התנהגות מסוימת של האפליקציה ומונעת טעויות. לדוגמה, אם אתם רוצים לוודא שהאפליקציה אף פעם לא תחסום את הניתוח, אתם יכולים להריץ את האפליקציה על המדיניות בנושא סקריפטים סינכרוניים. כש-sync-script: 'none' מופעל, JavaScript שחוסם את הניתוח יגרום לא ניתן יהיה להפעיל אותו. הפעולה הזו מונעת מהקוד לחסום את המנתח, הדפדפן לא צריך לדאוג לגבי השהיית המנתח.

סיכום

תודה

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

תודה רבה לכל מי שבחן את הטיוטות המוקדמות של הסדרה, כולל (בין היתר) to): אלכס ראסל, פול אירלנד, מגן קירני, אריק ביילמן, מתיאס ביינס, Addy Osmani, קינוקו יאסודה, נאסקו אוסקוב, וגם צ'רלי רייס.

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