إزالة البيانات غير الضرورية لتحسين أداء العرض

Tom Wiltzius
Tom Wiltzius

مقدمة

تريد أن يصبح تطبيق الويب سريع الاستجابة وسلسًا عند إنشاء الرسوم المتحركة والانتقالات وغيرها من تأثيرات واجهة المستخدم الصغيرة. إنّ التأكّد من أنّ هذه التأثيرات خالية من البيانات غير الضرورية يمكن أن يعني الفرق بين الشعور "الأصلي" والطابع القبيح وغير المتقن.

وهذه المقالة هي الأولى من سلسلة مقالات تتناول تحسين أداء العرض في المتصفّح. لبدء الإجراءات، سنتناول سبب صعوبة الرسوم المتحركة السلس وما يجب أن يحدث لتحقيقها، بالإضافة إلى بعض أفضل الممارسات السهلة. تم تقديم العديد من هذه الأفكار في الأصل في لقاء بعنوان "Jank Busters"، وهو محاضرة شاركتها مع نات دوكا في مؤتمر Google I/O (فيديو) خلال هذا العام.

إطلاق ميزة "مزامنة الفيديو"

قد يكون لاعبو أجهزة الكمبيوتر على دراية بهذا المصطلح، إلا أنّ هذا المصطلح غير شائع على الويب، فما هي ميزة مزامنة v؟

ضع في اعتبارك شاشة هاتفك: يتم تحديثها على فترات منتظمة، عادةً (وليس دائمًا) حوالي 60 مرة في الثانية. تشير المزامنة الرأسية (أو المزامنة العمودية) إلى ممارسة إنشاء إطارات جديدة بين عمليات إعادة تحميل الشاشة فقط. قد تفكر في هذا كحالة سباق بين العملية التي تكتب البيانات في المخزن المؤقت للشاشة ونظام التشغيل الذي يقرأ تلك البيانات لوضعها على الشاشة. نريد أن يتغيّر محتوى الإطار المخزن مؤقّتًا بين عمليات التحديث هذه، وليس أثناء عرضها، وإلا ستعرض شاشة العرض نصف إطار ونصف آخر، ما سيؤدي إلى "تمزيق".

للحصول على صورة متحركة سلسة، يجب أن يكون إطارًا جديدًا جاهزًا في كل مرة يتم فيها إعادة تحميل الشاشة. وينتج عن ذلك أثران كبيران: توقيت عرض اللقطة (أي الوقت الذي يجب أن يكون فيه الإطار جاهزًا بحلول) وميزانية الإطار (أي المدة التي يجب أن ينتج خلالها المتصفّح إطارًا). لديك فقط الوقت بين عمليات إعادة تحميل الشاشة لإكمال لقطة (16 ملي ثانية تقريبًا على شاشة 60 هرتز)، وتريد البدء في إنتاج الإطار التالي فور وضع الإطار الأخير على الشاشة.

التوقيت هو كل شيء: requestAnimationFrame

يستخدم العديد من مطوّري البرامج على الويب setInterval أو setTimeout كل 16 ملّي ثانية لإنشاء الصور المتحركة. هذه مشكلة لعدة أسباب (وسنناقش المزيد منها خلال دقيقة)، ولكن ما يهم على وجه الخصوص هو:

  • تكون درجة دقّة الموقّت الواردة من JavaScript مطلوبة من عدة ملّي ثانية فقط.
  • تختلف معدلات التحديث في الأجهزة المختلفة.

استرجع مشكلة توقيت عرض اللقطة المذكورة أعلاه: أنت بحاجة إلى إطار رسوم متحركة مكتمل وإنهاء أي معالجة بلغة JavaScript ومعالجة DOM وتخطيط ورسم وما إلى ذلك، لكي تكون جاهزًا قبل تحديث الشاشة التالي. يمكن أن تؤدي الدقة المنخفضة للموقتات إلى صعوبة إكمال الصور المتحركة قبل إعادة تحميل الشاشة التالية، ولكن التباين في معدلات تحديث الشاشة يجعل من المستحيل استخدام مؤقت ثابت. بغضّ النظر عن الفاصل الزمني للموقت، ستنتقل ببطء من نافذة التوقيت للإطار وسينتهي بك الأمر بإسقاط إطار. ويحدث ذلك حتى لو كان المؤقت يتم تنشيطه بدقة بالملي ثانية، وهو أمر لن يتم اكتشافه (كما اكتشفه) -- تختلف درجة دقة المؤقت بناءً على ما إذا كان الجهاز يعمل بالبطارية أم أن الجهاز متصل بالكهرباء، ويمكن أن تتأثر بموارد علامات التبويب التي تستخدم في الخلفية أو غير ذلك. عليك أيضًا تنفيذ الإجراءات اللازمة لإنشاء إطارات لا يتم عرضها مطلقًا، ما يؤدي إلى إهدار الطاقة ووقت وحدة المعالجة المركزية الذي قد تقضيه في تنفيذ مهام أخرى في تطبيقك.

تختلف معدلات التحديث على الشاشات المختلفة: 60 هرتز شائعة، ولكن بعض الهواتف تبلغ 59 هرتز، بينما تنخفض بعض أجهزة الكمبيوتر المحمولة إلى 50 هرتز في وضع الطاقة المنخفضة، بينما تصل درجة حرارة بعض شاشات الكمبيوتر المكتبي إلى 70 هرتز.

نميل إلى التركيز على عدد اللقطات في الثانية (FPS) عند مناقشة أداء العرض، ولكن يمكن أن يمثّل التباين مشكلة أكبر. تلاحظ أعيننا العوائق الصغيرة غير المنتظمة في الرسوم المتحركة التي يمكن أن تنتج عنها الرسوم المتحركة ذات توقيت سيئ.

يمكنك استخدام requestAnimationFrame للحصول على إطارات الصور المتحركة في وقتها الصحيح. عند استخدام واجهة برمجة التطبيقات هذه، فإنك تطلب من المتصفح إضافة إطار رسوم متحركة. يتم استدعاء معاودة الاتصال عندما يعرض المتصفِّح قريبًا إطارًا جديدًا. ويحدث ذلك بغض النظر عن معدّل التحديث.

يحتوي requestAnimationFrame على خصائص أخرى رائعة أيضًا:

  • يتم إيقاف الصور المتحركة في علامات التبويب في الخلفية مؤقتًا، ما يؤدي إلى الحفاظ على موارد النظام وعمر البطارية.
  • وإذا لم يتمكن النظام من معالجة العرض بمعدل تحديث الشاشة، يمكنه تقليل الرسوم المتحركة وإنتاج رد الاتصال بوتيرة أقل (على سبيل المثال، 30 مرة في الثانية على شاشة 60 هرتز). في حين أن هذا يؤدي إلى تخفيض معدل عرض الإطارات إلى النصف، فإنه يحافظ على اتساق الرسوم المتحركة، وكما ذُكر أعلاه، تكون أعيننا أكثر انسجامًا مع التباين من معدل عرض الإطارات. يبدو البث الثابت بتردد 30 هرتز أفضل من 60 هرتز وهو يفوته بضع لقطات في الثانية.

سبق أن تمت مناقشتنا عن "requestAnimationFrame" في كل مكان، لذا ننصحك بالرجوع إلى المقالات مثل هذه المقالة من تصميم JS للحصول على مزيد من المعلومات حولها، إلا أنّها خطوة أولى مهمة لإنشاء صور متحركة سلسة.

ميزانية الإطار

ونظرًا لأننا نريد إطارًا جديدًا جاهزًا في كل تحديث للشاشة، فلا يوجد سوى وقت بين عمليات التحديث للقيام بكل العمل لإنشاء إطار جديد. على شاشة 60 هرتز، يعني ذلك أن لدينا حوالي 16 ملي ثانية لتشغيل جميع JavaScript، وتنفيذ التنسيق، والطلاء، وأي شيء آخر يتعين على المتصفح فعله لإزالة الإطار. ويعني هذا أنّه إذا استغرق تشغيل JavaScript في معاودة الاتصال في requestAnimationFrame أكثر من 16 ملي ثانية، ليس لديك أي أمل في إنشاء إطار في الوقت المناسب لإجراء المزامنة عبر v-in.

16 ملي ثانية ليس وقتًا طويلاً. لحسن الحظّ، يمكن أن تساعدك أدوات المطوّرين في Chrome في تتبُّع ما إذا كنت تبذل مجهودًا كبيرًا في إطار ميزانية الإطار أثناء معاودة الاتصال بـ requestAnimationFrame.

يؤدي فتح المخطط الزمني لأدوات مطوّري البرامج وتسجيل هذا الرسم المتحرك أثناء العمل بسرعة إلى توضيح أننا نتجاوز الميزانية عند إنشاء الرسوم المتحركة. في "المخطط الزمني"، انتقِل إلى "الإطارات" لإلقاء نظرة:

عرض توضيحي بتنسيق كبير جدًا
عرض توضيحي بحجم كبير جدًا

تستغرق عمليات معاودة الاتصال requestAnimationFrame (rAF) هذه أكثر من 200 ملي ثانية. هذا مقدار طويل جدًا بحيث لا يمكن إخراج إطار كل 16 ملي ثانية! فتح إحدى عمليات استدعاء rAF الطويلة هذه يكشف عما يحدث بالداخل: في هذه الحالة، الكثير من التخطيط.

يقدم فيديو سليم مزيدًا من التفاصيل حول السبب المحدد لإعادة التنسيق (هو قراءة scrollTop) وكيفية تجنبه. لكن المغزى هنا هو أنه يمكنك التعمق في معاودة الاتصال والتحقيق فيما يستغرق وقتًا طويلاً.

إصدار تجريبي مُحدَّث بتنسيق أقل بكثير
إصدار تجريبي مُعدَّل بتصميم مخفّض للغاية

لاحِظ أوقات عرض اللقطات التي تبلغ 16 ملي ثانية. تلك المساحة الفارغة في الإطارات هي هامش النمو الذي يجب عليك القيام به بمزيد من العمل (أو السماح للمتصفح بتنفيذ ما يحتاج إليه في الخلفية). تلك المساحة الفارغة هي شيء جيد.

مصدر آخر للجانك

يكمن السبب الأكبر في حدوث مشاكل عند محاولة تشغيل الرسوم المتحركة التي تستخدم JavaScript في أن أشياءً أخرى قد تقف عائقًا أمام استدعاء RAF، بل وحتى تمنع تشغيله على الإطلاق. حتى إذا كان استدعاء RAF يميل إلى العمل ويتم تشغيله خلال بضع ميلي ثانية فقط، يمكن أن تظهر فجأة أنشطة أخرى (مثل معالجة XHR قد وصل للتو، أو تشغيل معالِجات أحداث الإدخال، أو تشغيل تحديثات مجدولة على موقّت)، وذلك لأي فترة زمنية بدون أن يستلزم ذلك استجابة. قد تستغرق أحيانًا معالجة هذه الأحداث على الأجهزة الجوّالة مئات المللي ثانية، وخلالها تتوقف الرسوم المتحركة تمامًا. ونُطلق على عوائق الرسوم المتحركة هذه اسم أعطال.

ما من حل سحري لتجنُّب هذه المواقف، ولكن هناك بعض أفضل الممارسات المتعلّقة بالبنية لمساعدتك على تحقيق النجاح:

  • يجب عدم إجراء الكثير من عمليات المعالجة في معالِجات الإدخال. لذلك، يُعد تنفيذ الكثير من صفحات JavaScript أو محاولة إعادة ترتيب الصفحة بأكملها أثناء استخدام المعالج onscroll على سبيل المثال أحد الأسباب الشائعة للمشاكل الفادحة.
  • حاوِل إدخال أكبر قدر ممكن من المعالجة (قراءة: أي شيء قد يستغرق وقتًا طويلاً حتى يتم تنفيذه) في معاودة اتصال RAF أو عاملي الويب قدر الإمكان.
  • إذا دفعت العمل إلى استدعاء rAF، فحاول تقسيمه بحيث تتم معالجة كل إطار قليلاً فقط أو تؤخره حتى بعد انتهاء رسم متحرك مهم - بهذه الطريقة يمكنك الاستمرار في تشغيل استدعاءات rAF قصيرة وتحريكها بسلاسة.

للاطّلاع على برنامج تعليمي رائع يتناول كيفية إرسال المعالجة إلى طلبات معاودة الاتصال requestAnimationFrame بدلاً من معالِجات الإدخال، يُرجى الاطّلاع على مقالة "بول لويس" حول Leaner, Meaner, Faster Animations with requestAnimationFrame.

الرسوم المتحركة بتنسيق CSS

ما الذي يمكن أن يكون أفضل من JavaScript خفيف في الحدث وعمليات معاودة الاتصال في RAF؟ لا يقبل JavaScript.

قلنا في وقت سابق أنه ليس هناك حل سحري لتجنب إيقاف استدعاءات rAF، ولكن يمكنك استخدام صور CSS المتحركة لتجنب الحاجة إليها تمامًا. في متصفح Chrome لنظام التشغيل Android على وجه الخصوص (وتعمل المتصفحات الأخرى على ميزات مشابهة)، تحتوي الصور المتحركة في CSS على الخاصية المطلوبة والتي يستطيع المتصفّح تشغيلها في كثير من الأحيان حتى ولو كانت JavaScript قيد التشغيل.

هناك عبارة ضمنية في القسم أعلاه عن غير محدد: يمكن للمتصفحات تنفيذ إجراء واحد فقط في كل مرة. وهذا ليس صحيحًا تمامًا، ولكنه افتراض عملي جيد: يمكن في أي وقت تشغيل 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.

الخاتمة

باختصار:

  1. عند إنشاء تأثيرات متحركة، من المهم إنشاء إطارات لكل عملية تحديث للشاشة. تُحدث الرسوم المتحركة مع Vsync'd تأثيرًا إيجابيًا كبيرًا على مظهر التطبيق.
  2. إن أفضل طريقة للحصول على صور متحركة بتنسيق vsync في Chrome والمتصفحات الحديثة الأخرى هي استخدام الرسوم المتحركة في CSS. عندما تحتاج إلى مرونة أكبر مما توفره الصور المتحركة في CSS، فإن أفضل أسلوب هو الصور المتحركة المستندة إلى requestAnimationFrame.
  3. للحفاظ على حالة ورضا رسوم متحركة RAF، تأكد من أن معالِجات الأحداث الأخرى لا تعيق تشغيل معاودة الاتصال RAF، واجعل استدعاءات rAF قصيرة (أقل من 15 ملي ثانية).

وأخيرًا، لا تنطبق الرسوم المتحركة vsync'd فقط على الرسوم المتحركة البسيطة في واجهة المستخدم - بل تنطبق على الرسوم المتحركة Canvas2D، ورسومات WebGL المتحركة، وحتى التمرير على الصفحات الثابتة. في المقالة التالية من هذه السلسلة، سنتعمق في أداء التمرير مع وضع هذه المفاهيم في الاعتبار.

نتمنى لك رسوم متحركة سعيدة.

المراجع