مقدمة
ينبغي أن يكون تطبيق الويب الخاص بك مستجيبًا وسلسًا عند إنشاء الرسوم المتحركة والانتقالات وتأثيرات واجهة المستخدم الصغيرة الأخرى. لذا فإن التأكد من أن هذه التأثيرات خالية من أي أعطال قد تُحدث فرقًا بين "التأثيرات الأصلية" أو طابعًا مزعجًا أو غير منقول.
هذه هي المقالة الأولى من سلسلة مقالات تتناول تحسين أداء العرض في المتصفّح. وكبداية، سنتناول بعض أسباب صعوبة إنشاء الصور المتحركة السلسة وما يجب القيام به لتحقيقها، بالإضافة إلى بعض أفضل الممارسات السهلة. تم تقديم العديد من هذه الأفكار في الأصل في "Jank Busters"، "نات دوكا" التي ألقيتها في مؤتمر Google I/O (فيديو) هذا العام.
لمحة حول ميزة "مزامنة V
قد يكون هذا المصطلح مألوفًا لدى لاعبي ألعاب الكمبيوتر، ولكنه لا شائع على الويب: ما هو v-sync؟
يُرجى مراعاة أنّ شاشة هاتفك: يتم تحديثها على فترات منتظمة، عادةً (وليس دائمًا!) حوالي 60 مرة في الثانية. تشير المزامنة الرأسية (أو المزامنة الرأسية) إلى ممارسة إنشاء إطارات جديدة بين عمليات إعادة تحميل الشاشة فقط. قد تفكر في هذا وكأنه حالة سباق بين العملية التي تكتب البيانات في المخزن المؤقت للشاشة وقراءة نظام التشغيل لتلك البيانات لوضعها على الشاشة. ويهمّنا أن تتغير محتويات اللقطات التي تم تخزينها مؤقتًا بين عمليات إعادة التحميل هذه، وليس أثناء إجرائها. وبخلاف ذلك، ستعرض الشاشة نصف إطار ونصف آخر، ما يؤدي إلى "تمزيق".
للحصول على صورة متحركة سلسة، تحتاج إلى إطار جديد ليكون جاهزًا في كل مرة يتم فيها تحديث الشاشة. وينتج عن ذلك تأثيران كبيران: توقيت عرض اللقطة (أي الوقت الذي يجب أن يكون فيه الإطار جاهزًا)، وميزانية الإطار (أي المدة التي يجب أن يعرض خلالها المتصفّح إطارًا). لديك فقط الوقت بين عمليات تحديث الشاشة لإكمال إطار (16 ملي ثانية تقريبًا على شاشة 60 هرتز)، وتريد بدء إنتاج الإطار التالي بمجرد عرض الإطار الأخير على الشاشة.
التوقيت هو كل شيء: requestAnimationFrame
يستخدم العديد من مطوّري الويب setInterval
أو setTimeout
كل 16 مللي ثانية لإنشاء صور متحركة. هذه مشكلة لعدة أسباب (وسنناقش المزيد من التفاصيل بعد دقيقة)، ولكن بشكل خاص، ما يلي:
- تكون درجة دقة الموقّت من JavaScript في حدود عدة ملي ثانية فقط.
- معدلات إعادة التحميل المختلفة لكل جهاز آخر
تذكر مشكلة توقيت الإطار المذكورة أعلاه: تحتاج إلى إطار رسم متحرك مكتمل، مع الانتهاء من أي معالجة JavaScript أو معالجة DOM أو التخطيط أو الرسم وما إلى ذلك، لتكون جاهزًا قبل تحديث الشاشة التالي. قد تؤدي درجة دقة الموقّت المنخفضة إلى صعوبة إكمال إطارات الصور المتحركة قبل إعادة تحميل الشاشة التالية، إلا أنّ الاختلاف في معدّلات إعادة تحميل الشاشة يجعل من المستحيل إكمال هذا الإجراء باستخدام موقّت ثابت. مهما كان الفاصل الزمني بين الموقّت، ستنتقل ببطء إلى وضعية التوقيت الخاصة بإطار معيّن وينتهي بك الأمر بإسقاط أحد الإطارات. ويحدث ذلك حتى إذا كان الموقّت يعمل بدقة بالمللي ثانية، وهي غير متاحة (كما اكتشفها المطورون)، حيث تختلف درجة دقة الموقّت حسب ما إذا كان الجهاز يعمل بالبطارية أم عند توصيله بمصدر طاقة، وقد يتأثر ذلك بعلامات تبويب الخلفية التي تستنزف الموارد، وغير ذلك. وحتى إذا كان هذا الفاصل نادر الحدوث (على سبيل المثال، كل 16 إطارًا بسبب إيقاف تشغيل الفيديو بمقدار ملي ثانية)، ستلاحظ: هبوط عدة لقطات في الثانية. وتقوم أيضًا بالعمل لإنشاء إطارات لا يتم عرضها مطلقًا، ما يؤدي إلى إهدار الطاقة ووقت وحدة المعالجة المركزية الذي قد تقضيه في تنفيذ أشياء أخرى في تطبيقك.
يكون للشاشات المختلفة معدلات تحديث مختلفة: 60 هرتز شائع، لكن بعض الهواتف 59 هرتز، وبعض أجهزة الكمبيوتر المحمولة تنخفض إلى 50 هرتز في وضع الطاقة المنخفضة، وبعض شاشات سطح المكتب بتردد 70 هرتز.
نميل بالتركيز على اللقطات في الثانية عند مناقشة أداء العرض، ولكن قد يشكّل التباين مشكلة أكبر. وتلاحظ أعيننا العقبات الصغيرة وغير المنتظمة في الرسوم المتحركة التي يمكن أن تنتج عن الرسوم المتحركة ذات الوقت السيئ.
طريقة الحصول على إطارات الصور المتحركة في الوقت المناسب هي باستخدام requestAnimationFrame
. عند استخدام واجهة برمجة التطبيقات هذه، أنت تطلب من المتصفح إطارًا للصور المتحركة. يتم استدعاء إجراء معاودة الاتصال عندما يُنتج المتصفِّح إطارًا جديدًا قريبًا. ويحدث ذلك بغض النظر عن معدّل التحديث.
لدى requestAnimationFrame
خصائص رائعة أخرى أيضًا:
- ويتم إيقاف الصور المتحركة مؤقتًا في علامات التبويب في الخلفية، ما يؤدي إلى الحفاظ على موارد النظام وعمر البطارية.
- إذا لم يتمكّن النظام من معالجة العرض مع معدّل تحديث الشاشة، قد يحدّ من جودة الصور المتحركة ويقل عدد مرّات معاودة الاتصال (على سبيل المثال، 30 مرّة في الثانية على شاشة 60 هرتز). يؤدي هذا إلى انخفاض معدل عرض الإطارات بمقدار النصف، ولكنه يحافظ على اتساق الصورة المتحركة، وكما أسلفنا أعلاه، فإن أعيننا تدرك التباين أكثر من معدل عرض الإطارات. وتبدو 30 هرتز أفضل من 60 هرتز تفوتها بضع لقطات في الثانية.
لقد ناقشنا مواضيع مختلفة حول "requestAnimationFrame
"، لذا يمكنك الاطّلاع على مقالات مثل هذه المقالة من محتوى JavaScript للحصول على مزيد من المعلومات حول هذا الموضوع، مع أنّها خطوة أولى مهمة لضمان سلاسة الصور المتحركة.
ميزانية الإطار
ونظرًا لأننا نريد إطارًا جديدًا جاهزًا عند كل تحديث للشاشة، فلا يوجد سوى الوقت بين عمليات التحديث للقيام بكل العمل لإنشاء إطار جديد. فعند استخدام شاشة بسرعة 60 هرتز، تتوفّر مدة 16 ملي ثانية تقريبًا لتشغيل كل JavaScript وتنفيذ التنسيق والرسم وغير ذلك من الإجراءات التي يجب أن يتّخذها المتصفّح لإزالة الإطار. ويعني هذا أنّه إذا استغرق تشغيل رمز JavaScript داخل استدعاء requestAnimationFrame
أكثر من 16 ملي ثانية، لن يكون لديك أي أمل في إنشاء إطار في الوقت المناسب للمزامنة v.
16 ملي ثانية ليست وقتًا طويلاً. لحسن الحظ، يمكن أن تساعدك أدوات المطور في Chrome في تتبع ما إذا كنت تنفد ميزانية الإطار أثناء استدعاء requestAnimationFrame.
عند فتح المخطط الزمني لأدوات مطوّري البرامج وتسجيل هذه الصورة المتحركة بشكل سريع، يتبيّن لنا بسرعة أنّنا نتجاوز الميزانية عند استخدام اللقطات المتحرّكة. في المخطط الزمني، قم بالتبديل إلى "الإطارات" وألق نظرة:
![عرض توضيحي بتنسيق كبير للغاية](https://web.dev/static/articles/speed-rendering/image/a-demo-far-too-much-layo-d53c622ced846.png?authuser=19&hl=ar)
تستغرق عمليات استدعاء requestAnimationFrame (rAF) هذه مدة تزيد عن 200 ملي ثانية. هذه القيمة طويلة جدًا لتحديد إطار كل 16 ملي ثانية. يكشف فتح إحدى استدعاءات rAF الطويلة تلك عما يحدث بالداخل: في هذه الحالة، هناك الكثير من التخطيط.
يقدّم سامي في الفيديو مزيدًا من التفاصيل حول السبب المحدّد لعملية الانتقال (هذه الصفحة هي scrollTop
) وكيفية تجنّبه. ولكن المغزى هنا هو أنه يمكنك التعمق في رد الاتصال والتحقيق في ما يستغرق وقتًا طويلاً.
![إصدار تجريبي محدَّث بتنسيق أصغر بكثير](https://web.dev/static/articles/speed-rendering/image/an-updated-demo-much-red-4918377a3889.png?authuser=19&hl=ar)
لاحِظ أنّ وقت عرض اللقطة هو 16 ملي ثانية. تلك المساحة الفارغة في الإطارات هي هامش النمو الذي يتعين عليك القيام بمزيد من العمل (أو جعل المتصفح يقوم بالعمل اللازم في الخلفية). تلك المساحة الفارغة شيء جيد.
مصدر آخر للمياه
أكبر سبب لحدوث المشاكل عند محاولة تشغيل الصور المتحركة المستندة إلى JavaScript هو أن أشياء أخرى قد تعيق معاودة اتصال rAF، بل وتمنعها من التشغيل على الإطلاق. حتى إذا كانت معاودة الاتصال بـ rAF بسيطة وتعمل خلال بضع سنوات بالمللي ثانية أو أنشطة أخرى (مثل معالجة طلب XHR الذي جاء للتو، أو تشغيل معالِجات أحداث الإدخال، أو تشغيل التحديثات المجدولة على مؤقِّت) يظهر ويعمل فجأة لأي فترة من الزمن دون أن يتراجع. على الجهاز الجوّال فقد تستغرق أحيانًا الأجهزة التي تعالج هذه الأحداث المئات من المللي ثانية، سيتم إيقاف الرسوم المتحركة تمامًا خلال هذه الفترة. نحن نسمي تلك أعطال الصور المتحركة jank.
لا يوجد حل سحرية لتجنب هذه المواقف، ولكن هناك بعض أفضل الممارسات المتعلقة بالبنية التي يمكنك من خلالها تحقيق النجاح:
- لا تُجرِ الكثير من المعالجة في معالِجات الإدخال. إجراء الكثير من نصوص JavaScript أو محاولة إعادة ترتيب الصفحة بأكملها على سبيل المثال فإن معالج التحميل هو سبب شائع جدًا للضعف الشديد.
- إرسال أكبر قدر ممكن من المعالجة (أي ما يمكن أن يستغرق تشغيله وقتًا طويلاً) في معاودة الاتصال بـ rAF أو عاملي الويب.
- إذا كنت تدفع العمل إلى استدعاء rAF، فحاول تقسيمه بحيث لا تتم معالجة كل إطار إلا قليلاً أو تأخيره إلى أن ينتهي الرسم المتحرك المهم - بهذه الطريقة يمكنك الاستمرار في تشغيل استدعاءات rAF قصيرة وتحريكه بسلاسة.
للحصول على برنامج تعليمي رائع يتناول كيفية الدفع بالمعالجة إلى استدعاءات requestAnimationFrame بدلاً من معالِجات الإدخال، يمكنك الاطّلاع على مقالة "بول لويس" بعنوان Leaner, Meaner, Faster Animations with requestAnimationFrame.
الصور المتحركة بتنسيق CSS
ما هو أفضل من نص JavaScript خفيف في الحدث وعمليات معاودة الاتصال في rAF؟ لا يقبل JS.
قلنا سابقًا أنه ما من حل بديل لتجنب مقاطعة استدعاءات rAF، ولكن يمكنك استخدام صور CSS المتحركة لتجنب الحاجة إليها بالكامل. في متصفح Chrome لنظام التشغيل Android على وجه الخصوص (وتعمل المتصفحات الأخرى مع ميزات مشابهة)، تتميز صور CSS المتحركة بالخصائص المرغوبة التي يتيح للمتصفّح إمكانية تشغيلها حتى إذا كان JavaScript قيد التشغيل.
هناك عبارة ضمنية في القسم أعلاه بشأن مشكلة في الطلبات غير صالحة: يمكن للمتصفحات تنفيذ إجراء واحد فقط في كل مرة. وهذا المفهوم غير صحيح تمامًا، ولكن يُفترَض أن يكون ذلك جيدًا، إذ يمكن في أي وقت أن يشغّل المتصفّح JavaScript أو يمكنه تنفيذ تنسيق أو تلوين عنصر واحد في كل مرة. يمكن التحقق من ذلك في عرض "المخطط الزمني" في "أدوات مطوّري البرامج". وأحد الاستثناءات لهذه القاعدة هو الصور المتحركة في CSS على Chrome لنظام Android (وقريبًا Chrome لأجهزة سطح المكتب، ولكن ليس بعد).
إذا أمكن، يؤدي استخدام الصور المتحركة في CSS إلى تبسيط التطبيق والسماح بتشغيل الصور المتحركة بسلاسة، حتى أثناء تشغيل JavaScript.
// see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ for info on rAF polyfills
rAF = window.requestAnimationFrame;
var degrees = 0;
function update(timestamp) {
document.querySelector('#foo').style.webkitTransform = "rotate(" + degrees + "deg)";
console.log('updated to degrees ' + degrees);
degrees = degrees + 1;
rAF(update);
}
rAF(update);
في حال النقر على الزر، يتم تشغيل JavaScript لمدة 180 ملي ثانية، ما يؤدي إلى إيقاف مؤقت. ولكن إذا شغّلنا تلك الحركة باستخدام الرسوم المتحركة بتنسيق CSS، فلن تظهر هذه الرسوم المتحركة.
(تذكر في وقت كتابة هذا التقرير، كانت الرسوم المتحركة في CSS مجانية فقط على Chrome لنظام Android، وليس Chrome على سطح المكتب.)
/* tools like Modernizr (http://modernizr.com/) can help with CSS polyfills */
#foo {
+animation-duration: 3s;
+animation-timing-function: linear;
+animation-animation-iteration-count: infinite;
+animation-animation-name: rotate;
}
@+keyframes: rotate; {
from {
+transform: rotate(0deg);
}
to {
+transform: rotate(360deg);
}
}
للحصول على مزيد من المعلومات عن استخدام الصور المتحركة في CSS، يمكنك الاطّلاع على مقالات مثل هذه المقالة على MDN.
الخاتمة
وباختصار، هذه هي:
- عند إضافة تأثيرات حركية، يجب الاهتمام بإنشاء إطارات لكل إعادة تحميل للشاشة. تؤثر الرسوم المتحركة من Vsync بشكل إيجابي كبير في الطريقة التي يشعر بها التطبيق.
- إن أفضل طريقة للحصول على صور متحركة بتنسيق vsync في Chrome والمتصفحات الحديثة الأخرى هي استخدام الرسوم المتحركة بتنسيق CSS. عندما تحتاج إلى مرونة أكبر من الرسوم المتحركة بتنسيق CSS أفضل أسلوب هو الرسوم المتحركة المستندة إلى requestAnimationFrame.
- للحفاظ على رضا الصور المتحركة في rAF، تأكّد من أنّ هناك معالِجات أحداث أخرى. لا تعيق تشغيل معاودة اتصال rAF، وتحافظ على استدعاءات rAF قصير (أقل من 15 ملي ثانية).
أخيرًا، لا تنطبق الرسوم المتحركة في vsync'd فقط على الرسوم المتحركة البسيطة في واجهة المستخدم - بل تنطبق على صور Canvas2D المتحركة والرسوم المتحركة في WebGL، وحتى التمرير على الصفحات الثابتة. في المقالة التالية ضمن هذه السلسلة، سنتناول أداء التمرير مع وضع هذه المفاهيم في الاعتبار.
استمتع بالرسومات المتحركة!