מבוא
HTML5 מספק לנו כלים מצוינים לשיפור המראה החזותי של אפליקציות אינטרנט. זה נכון במיוחד בתחום האנימציות. עם זאת, היכולת החדשה הזו מביאה גם אתגרים חדשים. למעשה, האתגרים האלה לא ממש חדשים, ולפעמים כדאי לשאול את השכנה הידידותית שלכם ליד השולחן, מתכננת ה-Flash, איך היא התגברה על בעיות דומות בעבר.
בכל מקרה, כשעובדים עם אנימציה, חשוב מאוד שהמשתמשים יחושו שהאנימציות חלקות. חשוב להבין שלא ניתן ליצור אנימציות חלקות פשוט על ידי הגדלת מספר הפריימים לשנייה מעבר לסף קוגניטיבי כלשהו. לצערנו, המוח שלנו חכם יותר. מה תלמדו? ש-30 פריימים אמיתיים של אנימציה לשנייה (fps) טובים הרבה יותר מ-60fps עם כמה פריימים חסרים באמצע. אנשים שונאים קצוות משוננים.
במאמר הזה ננסה לספק לכם את הכלים והטכניקות שיעזרו לכם לשפר את חוויית השימוש באפליקציה שלכם.
האסטרטגיה
אנחנו בהחלט לא רוצים להרתיע אתכם מלפתח אפליקציות מדהימות עם רכיבים חזותיים מדהימים באמצעות HTML5.
לאחר מכן, אם תבחינו שהביצועים יכולים להיות טובים יותר, תוכלו לחזור לכאן ולקרוא איך לשפר את הרכיבים של האפליקציה. כמובן שעדיף לעשות דברים בצורה נכונה מלכתחילה, אבל אל תתנו לזה להפריע לכם להיות פרודוקטיביים.
איכות חזותית גבוהה יותר באמצעות HTML5
שיפור המהירות באמצעות חומרה
עיבוד מואץ של חומרה הוא ציון דרך חשוב בביצועי העיבוד הכוללים בדפדפן. התכנון הכללי הוא להעביר משימות שהמעבד הראשי היה מחשב אחרת ליחידת העיבוד הגרפי (GPU) במתאם הגרפיקה של המחשב. כך אפשר לשפר משמעותית את הביצועים וגם לצמצם את צריכת המשאבים במכשירים ניידים.
ה-GPU יכול להאיץ את ההיבטים האלה של המסמך
- עיבוד קומפוזיציה כללי של פריסה
- מעברים ב-CSS3
- טרנספורמציות תלת-ממדיות ב-CSS3
- ציור על קנבס
- ציור תלת-ממד ב-WebGL
האצת הקנבס ו-WebGL הן תכונות ייעודיות שעשויות שלא לחול על האפליקציה הספציפית שלכם, אבל שלושת ההיבטים הראשונים יכולים לעזור לכל אפליקציה להאיץ את הביצועים שלה.
מה אפשר להאיץ?
כדי להאיץ את ה-GPU, מעבירים משימות ספציפיות ומוגדרות היטב לחומרה ייעודית. התכנון הכללי הוא שהמסמך מחולק למספר 'שכבות' שלא משתנות בהתאם לחלקים בדף שמואצים. הרינדור של השכבות האלה מתבצע באמצעות צינור עיבוד הנתונים הרגיל לעיבוד תמונה. לאחר מכן, מעבד ה-GPU משמש כדי לשלב את השכבות בדף אחד ולהחיל את 'האפקטים' שאפשר להאיץ בזמן אמת. תוצאה אפשרית היא שאובייקט עם אנימציה במסך לא מחייב 'פריסה מחדש' אחת של הדף בזמן האנימציה.
מה שצריך להבין מכך הוא שצריך להקל על מנוע הרינדור לזהות מתי הוא יכול להחיל את הקסם של האצת ה-GPU. דוגמה:
הבעיה היא שהדפדפן לא יודע שאתם מבצעים פעולה שאמורה להיראות כאנימציה חלקה לאדם. מה קורה כשמקבלים את אותו מראה חזותי באמצעות מעברים ב-CSS3 במקום זאת:
האופן שבו הדפדפן מטמיע את האנימציה הזו מוסתר לחלוטין מהמפתח. המשמעות היא שהדפדפן יכול להשתמש בטריקים כמו האצת GPU כדי להשיג את היעד שהוגדר.
יש שני דגלים שימושיים בשורת הפקודה של Chrome שיעזרו בניפוי באגים בהאצת GPU:
--show-composited-layer-borders
מציג מסגרת אדומה סביב רכיבים שמתבצעת בהם מניפולציה ברמת ה-GPU. שימושי כדי לוודא שהמניפולציות מתרחשות בשכבת ה-GPU.--show-paint-rects
כל השינויים שאינם ב-GPU נצבעים, וכתוצאה מכך מופיע גבול בהיר סביב כל האזורים שצוירו מחדש. אפשר לראות את הדפדפן מבצע אופטימיזציה של אזורי הצביעה בפעולה.
ל-Safari יש דגלים דומים בסביבת זמן הריצה שמתוארים כאן.
מעברים ב-CSS3
טרנזיציות CSS מאפשרות לכל אחד ליצור אנימציה של סגנון בקלות, אבל הן גם תכונה חכמה לשיפור הביצועים. מכיוון שהדפדפן מנהל את המעבר ב-CSS, אפשר לשפר משמעותית את רמת הנאמנות של האנימציה, ובמקרים רבים להאיץ אותה באמצעות חומרה. בשלב הזה, ב-WebKit (Chrome, Safari, iOS) יש טרנספורמציות CSS שמואצות בחומרה, אבל בקרוב הן יהיו זמינות בדפדפנים ובפלטפורמות אחרים.
אפשר להשתמש באירועים מסוג transitionEnd
כדי לכתוב סקריפט עם שילובים חזקים, אבל כרגע, כדי לתעד את כל אירועי הסיום של המעברים הנתמכים צריך לעקוב אחרי webkitTransitionEnd transitionend oTransitionEnd
.
ספריות רבות הוסיפו עכשיו ממשקי API לאנימציה שמנצלים מעברים אם הם קיימים, ובמקרה אחר חוזרים לאנימציה רגילה בסגנון DOM. scripty2, YUI transition, jQuery animate enhanced.
CSS3 Translate
בטח יצא לכם להוסיף אנימציה למיקום x/y של רכיב בדף. סביר להניח ששיניתם את המאפיינים left ו-top של סגנון השורה. באמצעות טרנספורמציות דו-ממדיות, אפשר להשתמש בפונקציונליות של translate()
כדי לשחזר את ההתנהגות הזו.
אנחנו יכולים לשלב את זה עם אנימציה של DOM כדי להשתמש באפשרות הטובה ביותר
<div style="position:relative; height:120px;" class="hwaccel">
<div style="padding:5px; width:100px; height:100px; background:papayaWhip;
position:absolute;" id="box">
</div>
</div>
<script>
document.querySelector('#box').addEventListener('click', moveIt, false);
function moveIt(evt) {
var elem = evt.target;
if (Modernizr.csstransforms && Modernizr.csstransitions) {
// vendor prefixes omitted here for brevity
elem.style.transition = 'all 3s ease-out';
elem.style.transform = 'translateX(600px)';
} else {
// if an older browser, fall back to jQuery animate
jQuery(elem).animate({ 'left': '600px'}, 3000);
}
}
</script>
אנחנו משתמשים ב-Modernizr כדי לבדוק את התכונות של טרנספורמציות 2D ב-CSS ושל מעברים ב-CSS. אם התכונות האלה נתמכות, נשתמש ב-translate כדי לשנות את המיקום. אם האנימציה הזו מתבצעת באמצעות מעבר, סביר להניח שהדפדפן יוכל להאיץ אותה באמצעות חומרה. כדי לדחוף את הדפדפן לכיוון הנכון, נשתמש ב'כדור הקליע הקסום של CSS' שמופיע למעלה.
אם הדפדפן שלנו לא תומך ביכולות מתקדמות, נשתמש ב-jQuery כדי להעביר את הרכיב. כדי להפוך את התהליך הזה לאוטומטי, אפשר להשתמש בPlugin של jQuery Transform polyfill של Louis-Remi Babe.
window.requestAnimationFrame
requestAnimationFrame
הוצג על ידי Mozilla ושופר על ידי WebKit במטרה לספק ממשק API מקומי להפעלת אנימציות, בין שהן מבוססות על DOM/CSS ובין שהן מבוססות על <canvas>
או על WebGL. הדפדפן יכול לבצע אופטימיזציה של אנימציות בו-זמניות יחד במחזור אחד של עיבוד מחדש וצביעת מחדש, וכך לשפר את איכות האנימציה. לדוגמה, אנימציות מבוססות-JS שמסונכרנות עם מעברים של CSS או SVG SMIL. בנוסף, אם מפעילים את לולאת האנימציה בכרטיסייה שלא מוצגת, הדפדפן לא ימשיך להפעיל אותה. המשמעות היא פחות שימוש ב-CPU, ב-GPU ובזיכרון, וכתוצאה מכך חיי סוללה ארוכים יותר.
מידע נוסף על האופן שבו משתמשים ב-requestAnimationFrame
ועל הסיבות לכך זמין במאמר של Paul Irish, requestAnimationFrame for smart animating.
יצירת פרופילים
אם גיליתם שאפשר לשפר את מהירות האפליקציה, הגיע הזמן להתחיל לנתח את הפרופיל כדי לבדוק איפה האופטימיזציה יכולה להניב את התועלת הרבה ביותר. לרוב, לשיפורים יש השפעה שלילית על יכולת התחזוקה של קוד המקור, ולכן צריך להחיל אותם רק במקרה הצורך. ניתוח פרופיל מאפשר לכם לדעת אילו חלקים בקוד יניבו את התועלת הרבה ביותר אם הביצועים שלהם ישתפרו.
ניתוח ביצועים של JavaScript
פרופילרים של JavaScript מספקים סקירה כללית של ביצועי האפליקציה ברמת הפונקציות של JavaScript, על ידי מדידת הזמן שנדרש להפעלת כל פונקציה בנפרד, מההתחלה ועד הסוף.
זמן הביצוע הכולל של פונקציה הוא משך הזמן הכולל שלוקח להפעיל אותה מלמעלה למטה. זמן הביצוע נטו הוא זמן הביצוע ברוטו בניכוי הזמן שנדרש להפעלת הפונקציות שהופעלו מהפונקציה.
חלק מהפונקציות נקראות בתדירות גבוהה יותר מאחרות. בדרך כלל, כלי הניתוח מספקים את הזמן שחלף עד להרצה של כל ההפעלות, וגם את משך הזמן הממוצע, המינימלי והמקסימלי להרצה.
מידע נוסף זמין במסמכי התיעוד של הכלים למפתחים ב-Chrome בנושא יצירת פרופילים.
ה-DOM
הביצועים של JavaScript משפיעים מאוד על מידת הנוחות והמהירות של האפליקציה. חשוב להבין שכלי לניתוחי ביצועים של JavaScript מודדים את זמן הביצוע של ה-JavaScript, אבל הם מודדים גם באופן עקיף את הזמן שמושקע בפעולות DOM. פעולות ה-DOM האלה הן לרוב הגורם העיקרי לבעיות בביצועים.
function drawArray(array) {
for(var i = 0; i < array.length; i++) {
document.getElementById('test').innerHTML += array[i]; // No good :(
}
}
לדוגמה, בקוד שלמעלה, כמעט לא חלף זמן על הפעלת JavaScript בפועל. עדיין סביר מאוד שהפונקציה drawArray תופיע בפרופילים שלכם, כי היא יוצרת אינטראקציה עם ה-DOM בצורה בזבזנית מאוד.
טיפים וטריקים
פונקציות אנונימיות
קשה ליצור פרופיל של פונקציות אנונימיות כי אין להן שם שבו הן יכולות להופיע בפרופילר. יש שתי דרכים לעקוף את הבעיה הזו:
$('.stuff').each(function() { ... });
שכתוב אל:
$('.stuff').each(function workOnStuff() { ... });
לא רבים יודעים ש-JavaScript תומכת במתן שמות לביטויים של פונקציות. כך הם יופיעו בצורה מושלמת בניתוח הפרופיל. יש בעיה אחת בפתרון הזה: הביטוי בעל השם בעצם מעביר את שם הפונקציה להיקף הלקסיקלי הנוכחי. פעולה כזו עלולה למחוק סמלים אחרים, לכן חשוב להיזהר.
יצירת פרופילים של פונקציות ארוכות
נניח שיש לכם פונקציה ארוכה ואתם חושדים שחלק קטן ממנה עלול להיות הסיבה לבעיות הביצועים. יש שתי דרכים לבדוק איזה חלק גורם לבעיה:
- השיטה הנכונה: צריך לשנות את הקוד כך שלא יכיל פונקציות ארוכות.
- השיטה הרעה לביצוע משימות: מוסיפים לקוד הצהרות בצורת פונקציות בעלות שם שמבצעות קריאה עצמית. אם תהיו זהירים, הפעולה הזו לא תשנה את הסמנטיקה, וחלקים מהפונקציה יופיעו כפונקציות נפרדות בפרופיל:
js function myLongFunction() { ... (function doAPartOfTheWork() { ... })(); ... }
חשוב לא לשכוח להסיר את הפונקציות הנוספות האלה אחרי סיום הניתוח, או אפילו להשתמש בהן כנקודת התחלה לשיפור הקוד.
יצירת פרופיל של DOM
הכלים העדכניים ביותר לפיתוח ב-Chrome Web Inspector מכילים את התצוגה החדשה 'ציר זמן', שבה מוצג ציר זמן של הפעולות ברמה הנמוכה שמבוצעות על ידי הדפדפן. אפשר להשתמש במידע הזה כדי לבצע אופטימיזציה של פעולות DOM. כדאי לצמצם את מספר ה'פעולות' שהדפדפן צריך לבצע בזמן שהקוד פועל.
תצוגת ציר הזמן יכולה ליצור כמות עצומה של מידע. לכן, כדאי לנסות ליצור תרחישי בדיקה מינימליים שאפשר להריץ בנפרד.
בתמונה שלמעלה מוצג הפלט של תצוגת ציר הזמן לסקריפט פשוט מאוד. בחלונית הימנית מוצגות הפעולות שהדפדפן ביצע בסדר כרונולוגי, ובציר הזמן בחלונית השמאלית מוצג הזמן בפועל שהפעולה הספציפית נמשכה.
מידע נוסף על תצוגת ציר הזמן כלי חלופי ליצירת פרופילים ב-Internet Explorer הוא DynaTrace Ajax Edition.
שיטות ליצירת פרופילים
הבחנה בין היבטים
כשאתם רוצים ליצור פרופיל של האפליקציה, נסו לזהות את ההיבטים של הפונקציונליות שלה שעשויים לגרום לזמן אחזור ארוך ככל האפשר. לאחר מכן, נסו להריץ פרופיל שבו יופעלו רק חלקים מהקוד שרלוונטיים להיבטים האלה של האפליקציה. כך יהיה קל יותר לפרש את נתוני הפרופיל כי הם לא מעורבבים עם נתיבים של קוד שלא קשורים לבעיה בפועל. דוגמאות טובות לפרטים ספציפיים באפליקציה יכולות להיות:
- זמן ההפעלה (הפעלת הכלי לניתוחי פרופיל, טעינת האפליקציה מחדש, המתנה עד שהאתחול יושלם, הפסקת הכלי לניתוחי פרופיל).
- לחיצה על לחצן ואז על אנימציה (הפעלת הכלי למעקב ביצועים, לחיצה על לחצן, המתנה לסיום האנימציה, הפסקת הכלי למעקב ביצועים).
יצירת פרופילים של ממשק משתמש גרפי
ביצוע רק של החלק הנכון של האפליקציה יכול להיות קשה יותר בתוכנית עם ממשק משתמש גרפי מאשר כשמבצעים אופטימיזציה, למשל, של כלי המעקב אחר קרניים במנוע ה-3D. לדוגמה, אם רוצים ליצור פרופיל של הדברים שקורים כשמקישים על לחצן, יכול להיות שתפעילו אירועים לא קשורים של מעבר עכבר מעליהם לאורך הדרך, שיגרמו לתוצאות פחות חד-משמעיות. כדאי להימנע מכך :)
ממשק פרוגרמטי
יש גם ממשק פרוגרמטי להפעלת מנתח הבאגים. כך תוכלו לקבוע במדויק מתי תתחיל היצירה של הפרופיל ומתי היא תסתיים.
מתחילים ליצור פרופיל באמצעות:
console.profile()
כדי להפסיק את היצירה של הפרופילים:
console.profileEnd()
יכולת חזרה
כשאתם מבצעים ניתוח פרופיל, חשוב לוודא שאתם יכולים לשחזר את התוצאות בפועל. רק אז תוכלו לדעת אם האופטימיזציות אכן שיפרו את הביצועים. בנוסף, יצירת הפרופיל ברמת הפונקציה מתבצעת בהקשר של המחשב כולו. זו לא מדע מדויק. גורמים רבים אחרים במחשב עשויים להשפיע על הפעלות ספציפיות של הפרופיל:
- טיימר לא קשור באפליקציה שלכם שמופעל בזמן שאתם מודדים משהו אחר.
- האוסף האשפה מבצע את העבודה שלו.
- כרטיסייה אחרת בדפדפן שמבצעת עבודה קשה באותו שרשור הפעלה.
- תוכנית אחרת במחשב שמנצלת את המעבד, וכתוצאה מכך האפליקציה איטית יותר.
- שינויים פתאומיים בשדה הכבידה של כדור הארץ.
מומלץ גם להריץ את אותו נתיב קוד כמה פעמים בסשן אחד של יצירת פרופיל. כך תוכלו לצמצם את ההשפעה של הגורמים שלמעלה, והחלקים האיטיים עשויים לבלוט עוד יותר.
מדידה, שיפור, מדידה
כשאתם מזהים נקודה איטית בתוכנית, נסו לחשוב על דרכים לשיפור התנהגות הביצוע. אחרי שמשנים את הקוד, צריך לבצע שוב את תהליך היצירה של הפרופיל. אם אתם מרוצים מהתוצאה, אפשר להמשיך. אם לא רואים שיפור, מומלץ לבטל את השינוי ולא להשאיר אותו "כי זה לא יכול להזיק".
אסטרטגיות לביצוע אופטימיזציה
צמצום האינטראקציה עם DOM
שיטה נפוצה לשיפור המהירות של אפליקציות לקוח אינטרנט היא לצמצם את האינטראקציה עם DOM. המהירות של מנועי JavaScript עלתה פי כמה, אבל הגישה ל-DOM לא התקצרה באותו קצב. זה גם לא יקרה מסיבות מעשיות מאוד (דברים כמו עיצוב גרפי וציור דברים במסך פשוט דורשים זמן).
שמירת צמתים של DOM במטמון
בכל פעם שאתם מאחזרים צומת או רשימת צמתים מ-DOM, נסו לחשוב אם תוכלו לעשות בהם שימוש חוזר בחישוב מאוחר יותר (או אפילו רק בחזרה הבאה של הלולאה). בדרך כלל זה המצב, כל עוד לא מוסיפים או מוחקים צמתים באזור הרלוונטי.
לפני:
function getElements() {
return $('.my-class');
}
אחרי:
var cachedElements;
function getElements() {
if (cachedElements) {
return cachedElements;
}
cachedElements = $('.my-class');
return cachedElements;
}
שמירת ערכי מאפיינים במטמון
באותו אופן שבו אפשר לשמור צמתים של DOM במטמון, אפשר גם לשמור במטמון את הערכים של המאפיינים. נניח שאתם רוצים ליצור אנימציה למאפיין של סגנון של צומת. אם אתם יודעים שאתם (כלומר, החלק הזה בקוד) היחידים שתמיד תיגע במאפיין הזה, תוכלו לשמור את הערך האחרון במטמון בכל חזרה על הפעולה כדי שלא תצטרכו לקרוא אותו שוב ושוב.
לפני:
setInterval(function() {
var ele = $('#element');
var left = parseInt(ele.css('left'), 10);
ele.css('left', (left + 5) + 'px');
}, 1000 / 30);
אחרי:
js
var ele = $('#element');
var left = parseInt(ele.css('left'), 10);
setInterval(function() {
left += 5;
ele.css('left', left + 'px');
}, 1000 / 30);
העברת מניפולציות DOM מחוץ ללופים
לולאות הן לרוב נקודות חמות לאופטימיזציה. נסו לחשוב על דרכים לנתק את עיבוד המספרים בפועל מהעבודה עם ה-DOM. לעיתים קרובות אפשר לבצע חישוב ואז, אחרי שהוא מסתיים, להחיל את כל התוצאות בבת אחת.
לפני:
document.getElementById('target').innerHTML = '';
for(var i = 0; i < array.length; i++) {
var val = doSomething(array[i]);
document.getElementById('target').innerHTML += val;
}
אחרי:
var stringBuilder = [];
for(var i = 0; i < array.length; i++) {
var val = doSomething(array[i]);
stringBuilder.push(val);
}
document.getElementById('target').innerHTML = stringBuilder.join('');
ציורים מחדש וזרימות חוזרות
כפי שצוין קודם, הגישה ל-DOM איטית יחסית. הבדיקה הזו הופכת להיות איטית מאוד כשהקוד קורא ערך שצריך לחשב מחדש כי הקוד שינה לאחרונה משהו שקשור ב-DOM. לכן, צריך להימנע משילוב של גישת קריאה וכתיבה ל-DOM. מומלץ תמיד לקבץ את הקוד בשני שלבים:
- שלב 1: קריאת ערכי DOM הנחוצים לקוד
- שלב 2: שינוי ה-DOM
נסו לא לתכנת דפוס כמו:
- שלב 1: קריאת ערכי DOM
- שלב 2: שינוי ה-DOM
- שלב 3: קריאה נוספת
- שלב 4: משנים את DOM במקום אחר.
לפני:
function paintSlow() {
var left1 = $('#thing1').css('left');
$('#otherThing1').css('left', left);
var left2 = $('#thing2').css('left');
$('#otherThing2').css('left', left);
}
אחרי:
function paintFast() {
var left1 = $('#thing1').css('left');
var left2 = $('#thing2').css('left');
$('#otherThing1').css('left', left);
$('#otherThing2').css('left', left);
}
כדאי להביא את ההמלצה הזו בחשבון לגבי פעולות שמתרחשות בהקשר אחד של ביצוע JavaScript. (למשל, בתוך גורם מטפל באירועים, בתוך גורם מטפל במרווח זמן או בזמן טיפול בתגובה של ajax).
הפעלת הפונקציה paintSlow()
מהקוד שלמעלה יוצרת את התמונה הבאה:
אם עוברים להטמעה המהירה יותר, התמונה שתוצג תהיה:
התמונות האלה מראות ששינוי הסדר שבו הקוד שלכם ניגש ל-DOM יכול לשפר משמעותית את ביצועי הרינדור. במקרה כזה, הקוד המקורי צריך לחשב מחדש את הסגנונות ולפרוס את הדף פעמיים כדי ליצור את אותה תוצאה. אפשר ליישם אופטימיזציה דומה כמעט בכל קוד 'בעולם האמיתי', ולקבל תוצאות דרמטיות מאוד.
מידע נוסף: עיבוד (rendering): צביעה מחדש, עיבוד מחדש של זרימה/פריסה, עיצוב מחדש מאת סטויאנו סטפנוב
ציורים מחדש ו-Event Loop
ההרצה של JavaScript בדפדפן מבוססת על מודל 'מחזור אירועים'. כברירת מחדל, הדפדפן נמצא במצב 'לא פעיל'. המצב הזה יכול להיות מופרע על ידי אירועים מאינטראקציות של משתמשים או על ידי דברים כמו שעוני JavaScript או קריאות חזרה של Ajax. בכל פעם שקוד JavaScript פועל בנקודת הפרעה כזו, הדפדפן בדרך כלל ממתין עד שהוא מסיים את הפעולה וחוזר לצייר את המסך (יכולות להיות חריגות לקוד JavaScript שמריץ זמן רב מאוד או במקרים כמו תיבות אזהרה שמפריעות בפועל להרצת ה-JavaScript).
השלכות
- אם מחזור האנימציה של JavaScript נמשך יותר מ-1/30 שניות, לא תוכלו ליצור אנימציות חלקות כי הדפדפן לא ירענן את התמונה במהלך ביצוע ה-JS. אם אתם מתכוונים לטפל גם באירועי משתמשים, תצטרכו להיות מהירים הרבה יותר.
- לפעמים כדאי לעכב פעולות מסוימות ב-JavaScript עד קצת מאוחר יותר.
לדוגמה:
setTimeout(function() { ... }, 0)
הקוד הזה בעצם אומר לדפדפן להריץ את פונקציית ה-callback ברגע שמחזור האירועים יהיה שוב במצב חוסר פעילות (חלק מהדפדפנים ימתינו לפחות 10 אלפיות השנייה). חשוב לדעת שהפעולה הזו תיצור שני מחזורי הפעלה של JavaScript שיקרו בהפרש זמן קצר מאוד. שתי האפשרויות האלה עלולות לגרום לצביעה מחדש של המסך, וכתוצאה מכך להכפיל את משך הזמן הכולל של הצביעה. אם הפעולה הזו תגרום להצגה של שתי פעולות צביעה בפועל תלויה בהיוריסטיקה בדפדפן.
גרסה רגילה:
function paintFast() {
var height1 = $('#thing1').css('height');
var height2 = $('#thing2').css('height');
$('#otherThing1').css('height', '20px');
$('#otherThing2').css('height', '20px');
}
נוסיף עיכוב:
function paintALittleLater() {
var height1 = $('#thing1').css('height');
var height2 = $('#thing2').css('height');
$('#otherThing1').css('height', '20px');
setTimeout(function() {
$('#otherThing2').css('height', '20px');
}, 10)
}
בגרסה עם העיכוב רואים שהדפדפן מצייר פעמיים, למרות ששני השינויים בדף מתרחשים במרווח של 1/100 שנייה בלבד.
אתחול עצל (Lazy Initialization)
המשתמשים רוצים אפליקציות אינטרנט שנטענות במהירות ומגיבות במהירות. עם זאת, למשתמשים יש ערכי סף שונים לגבי מה שהם מגדירים כ'איטי', בהתאם לפעולה שהם מבצעים. לדוגמה, אף פעם לא כדאי לבצע באפליקציה חישובים רבים באירוע של מעבר העכבר מעל, כי זה עלול לגרום לחוויית משתמש גרועה בזמן שהמשתמש ממשיך להזיז את העכבר. עם זאת, המשתמשים רגילים להמתין קצת אחרי שהם לוחצים על לחצן.
לכן, מומלץ להעביר את קוד האיפוס כך שיתבצע כמה שיותר מאוחר (למשל, כשהמשתמש לוחץ על לחצן שמפעיל רכיב מסוים באפליקציה).
לפני:
js
var things = $('.ele > .other * div.className');
$('#button').click(function() { things.show() });
אחרי:
js
$('#button').click(function() { $('.ele > .other * div.className').show() });
הענקת גישה לאירועים
הפצת פונקציות טיפול באירועים בדף עשויה להימשך זמן רב יחסית, וגם יכולה להיות משימה מייגעת אחרי שמחליפים אלמנטים באופן דינמי, כי צריך לצרף מחדש את פונקציות הטיפול באירועים לאלמנטים החדשים.
במקרה כזה, הפתרון הוא להשתמש בשיטה שנקראת הענקת גישה לאירועים. במקום לצרף לטבלת האירועים פונקציות בודדות לטיפול באירועים, אפשר להשתמש בטבע הבועה של אירועים רבים בדפדפן. לשם כך, צריך לצרף את פונקציית הטיפול באירוע לצומת הורה ולבדוק את צומת היעד של האירוע כדי לראות אם האירוע מעניין.
ב-jQuery אפשר לבטא את זה בקלות כך:
$('#parentNode').delegate('.button', 'click', function() { ... });
מתי לא כדאי להשתמש בהענקת גישה לאירועים
לפעמים המצב יכול להיות הפוך: אתם משתמשים בהענקת גישה לאירועים ויש לכם בעיה בביצועים. בעיקרון, הענקת גישה לאירועים מאפשרת זמן אתחול עם מורכבות קבועה. עם זאת, צריך לשלם את המחיר של בדיקת העניין באירוע בכל קריאה לאותו אירוע. הפעולה הזו עשויה להיות יקרה, במיוחד לגבי אירועים שמתרחשים לעיתים קרובות, כמו 'mouseover' או אפילו 'mousemove'.
בעיות נפוצות ופתרונות
הדברים שאני עושה ב-$(document).ready
נמשכים הרבה זמן
העצה האישית של Malte: אף פעם אל תעשו שום דבר ב-$(document).ready
. נסו לשלוח את המסמך בגרסה הסופית שלו. בסדר, מותר לרשום מאזינים לאירועים, אבל רק באמצעות בורר מזהי (id-selector) ו/או באמצעות הענקת גישה לאירועים. באירועים יקרים כמו 'mousemove', כדאי לעכב את הרישום עד שהם נדרשים (אירוע 'mouseover' על הרכיב הרלוונטי).
אם אתם באמת צריכים לעשות דברים, כמו שליחת בקשת Ajax כדי לקבל נתונים בפועל, כדאי להציג אנימציה יפה. אם מדובר בקובץ GIF מונפש או בפורמט דומה, כדאי לכלול את האנימציה כ-URI של נתונים.
מאז שהוספתי סרטון Flash לדף, הכל מאוד איטי
הוספת Flash לדף תמיד תגרום להאטה קלה ברינדור, כי יש צורך 'לנהל משא ומתן' על הפריסה הסופית של החלון בין הדפדפן לבין הפלאגין של Flash. אם אי אפשר להימנע לחלוטין משימוש ב-Flash בדפים, חשוב להגדיר את הפרמטר 'wmode' ב-Flash לערך 'window' (זהו ערך ברירת המחדל). הפעולה הזו תשבית את היכולת ליצור קומפוזיציה של רכיבי HTML ו-Flash (לא תוכלו לראות רכיב HTML שנמצא מעל סרטון ה-Flash, וסרטון ה-Flash לא יכול להיות שקוף). יכול להיות שזה יהיה לא נוח, אבל זה ישפר באופן משמעותי את הביצועים. לדוגמה, כדאי לבדוק איך באתר youtube.com נמנעים בקפידה מלמקם שכבות מעל נגן הסרט הראשי.
שומרים דברים ב-localStorage, והאפליקציה מתחילה להאט
כתיבת ב-localStorage היא פעולה סינכרונית שכוללת הפעלה של דיסק הקשיח. אף פעם לא כדאי לבצע פעולות סינכרוניות 'ארוכות' בזמן ביצוע אנימציות. מעבירים את הגישה ל-localStorage לנקודה בקוד שבה אתם בטוחים שהמשתמש לא פעיל ואין אנימציות פעילות.
ניתוח הפרופיל מציין שסלקטור jQuery איטי מאוד
קודם כול, צריך לוודא שאפשר להריץ את הבורר באמצעות document.querySelectorAll. אפשר לבדוק את זה במסוף JavaScript. אם יש חריג, צריך לכתוב מחדש את הבורר כך שלא ישתמש בתוסף מיוחד של מסגרת ה-JavaScript. כך תוכלו להאיץ את הבורר בדפדפנים מודרניים פי כמה.
אם הבעיה לא נפתרה או אם אתם רוצים שהדפדפנים המודרניים יפעלו במהירות גבוהה, תוכלו לפעול לפי ההנחיות הבאות:
- חשוב להיות ספציפיים ככל האפשר בצד שמאל של הבורר.
- כדאי להשתמש בשם תג שאתם לא משתמשים בו לעיתים קרובות כחלק הימני ביותר של הבורר.
- אם אף פתרון לא עוזר, כדאי לנסות לכתוב מחדש את הקוד כדי שתוכלו להשתמש בבורר מזהה.
כל פעולות ה-DOM האלה נמשכות זמן רב
הוספה, הסרה ועדכון של מספר רב של צמתים ב-DOM יכולים להיות איטיים מאוד. בדרך כלל אפשר לבצע אופטימיזציה של הקוד הזה על ידי יצירת מחרוזת גדולה של HTML ושימוש ב-domNode.innerHTML = newHTML
כדי להחליף את התוכן הישן. חשוב לזכור ששיטה כזו עלולה להקשות מאוד על תחזוקה, ויכול להיות שהיא תיצור קישורי זיכרון ב-IE, לכן חשוב להיזהר.
בעיה נפוצה נוספת היא שקוד האיפוס עשוי ליצור הרבה HTML. לדוגמה, פלאגין של jQuery שממיר תיבת בחירה למקבץ של divs כי זה מה שאנשים מהמחלקה לעיצוב רצו, בלי לדעת מהן השיטות המומלצות לממשק המשתמש. אם אתם רוצים שהדף יהיה מהיר, אל תעשו זאת אף פעם. במקום זאת, צריך לשלוח את כל הרכיבים של ה-Markup מצד השרת בצורתם הסופית. גם כאן יש הרבה בעיות, לכן כדאי לחשוב היטב אם המהירות שווה את התמורה.
כלים
- JSPerf – בדיקת ביצועים של קטעי JavaScript קצרים
- Firebug – ליצירת פרופילים ב-Firefox
- כלים למפתחים ב-Google Chrome (זמינים כ-WebInspector ב-Safari)
- DOM Monster – לאופטימיזציה של ביצועי DOM
- DynaTrace Ajax Edition – ליצירת פרופילים ולביצוע אופטימיזציה של ציור ב-Internet Explorer