الوظيفة المصغّرة للصور المتحركة في Houdini's

تحسين الصور المتحركة في تطبيقك على الويب

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

هل هناك واجهة برمجة تطبيقات أخرى لإنشاء الرسوم المتحركة؟

في الواقع، لا، بل هو امتداد لما نقدّمه حاليًا، وذلك لسبب وجيه. لنبدأ من البداية. إذا أردت إضافة تأثيرات متحركة إلى أي عنصر DOM على الويب، لديك خياران ونصف: عمليات النقل في CSS لتطبيق عمليات النقل البسيطة من "أ" إلى "ب"، والتأثيرات المتحركة في CSS لتطبيق التأثيرات المتحركة المستندة إلى الوقت والتي قد تكون دورية ومعقدة أكثر، وWeb Animations API (WAAPI) لتطبيق تأثيرات متحركة معقدة بشكل تعسّفي تقريبًا. تبدو مصفوفة دعم WAAPI قاتمة للغاية، لكنّها في طريقها إلى تحقيق المزيد. وحتى ذلك الحين، يمكنك استخدام polyfill.

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

ماذا عن فاقدي الجنسية؟ على سبيل المثال، شريط العناوين في Chrome على Android. إذا اطّلعت على المحتوى في الأسفل، سيختفي من الشاشة. ولكن في اللحظة التي تنتقل فيها إلى أعلى الصفحة، يعود المحتوى إلى مكانه الأصلي، حتى لو كنت في منتصف الصفحة. لا تعتمد الرسوم المتحركة على موضع التمرير فقط، ولكن أيضًا على اتجاه التمرير السابق. وهو يعتمد على الحالة.

هناك مشكلة أخرى متعلّقة بتصميم أشرطة التمرير. ومن المعروف أنها غير أسلوبية - أو على الأقل غير قابلة للأسلوب بما فيه الكفاية. ماذا لو أردتُ أن يكون قط النيان شريط التمرير؟ مهما كانت الطريقة التي تختارها، لا يُعدّ إنشاء شريط تمرير مخصّص سهلاً ولا يحقّق أداءً جيدًا.

والنقطة هي أنّ كل هذه الإجراءات غير ملائمة ويصعب تنفيذها بكفاءة. ويعتمد معظمها على الأحداث و/أو requestAnimationFrame، ما قد يحافظ على معدل 60 لقطة في الثانية، حتى عندما تكون الشاشة قادرة على عرض المحتوى بمعدّل 90 لقطة في الثانية أو 120 لقطة في الثانية أو أعلى، ويستخدم جزءًا من الميزانية المخصّصة للإطارات في سلسلة المهام الرئيسية.

توفّر Animation Worklet إمكانات إضافية لحزمة الرسوم المتحركة على الويب لتسهيل استخدام هذا النوع من التأثيرات. قبل البدء، لنتأكّد من أنّنا متعلّمون بأحدث المعلومات حول أساسيات الرسوم المتحرّكة.

لمحة تمهيدية حول الرسوم المتحركة والمخططات الزمنية

يستخدم كلّ من WAAPI وAnimation Worklet المخططات الزمنية على نطاق واسع للسماح لك بتنسيق الرسومات المتحرّكة والتأثيرات بالطريقة التي تريدها. هذا القسم عبارة عن تنشيط سريع للذاكرة أو مقدمة للجداول الزمنية وكيفية عملها مع الرسوم المتحركة.

يحتوي كل مستند على document.timeline. يبدأ من 0 عند إنشاء المستند ويحتسِب المللي ثانية منذ بدء المستند. تعمل كل الرسوم المتحرّكة في المستند وفقًا لهذا المخطط الزمني.

لتوضيح الأمور أكثر، لنلقِ نظرة على مقتطف WAAPI هذا.

const animation = new Animation(
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
      {
        transform: 'translateY(500px)',
      },
    ],
    {
      delay: 3000,
      duration: 2000,
      iterations: 3,
    }
  ),
  document.timeline
);

animation.play();

عند استدعاء animation.play()، يستخدم الرسم المتحرك currentTime في المخطط الزمني كوقت بدء. هناك تأخير في الصورة المتحركة يبلغ 3000 ملي ثانية، ما يعني أنّه سيبدأ عرض الصورة المتحركة (أو ستصبح "نشطة") عندما يصل المخطط الزمني إلى startTime

  • ‎3000. After that time, the animation engine will animate the given element from the first keyframe (translateX(0)), through all intermediate keyframes (translateX(500px)) all the way to the last keyframe (translateY(500px)) in exactly 2000ms, as prescribed by thedurationoptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline'scurrentTimeisstartTime + 3000 + 1000and the last keyframe atstartTime + 3000 + 2000`. والنقطة المهمة هي أنّ المخطط الزمني يتحكّم في النقطة التي وصلنا إليها في الصورة المتحركة.

بعد وصول الصورة المتحركة إلى الإطار الرئيسي الأخير، ستعود إلى الإطار الرئيسي الأول وستبدأ الخطوة التالية من الصورة المتحركة. وتتكرّر هذه العملية 3 مرات إجمالاً منذ إعداد iterations: 3. إذا أردنا أن يستمر التأثير المتحرك بدون توقف، سنكتب iterations: Number.POSITIVE_INFINITY. في ما يلي النتيجة للرمز المعروض أعلاه.

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

كتابة رمز مهام متحركة

بعد أن تعرّفنا على مفهوم المخططات الزمنية، يمكننا البدء بالاطّلاع على Animation Worklet وكيفية السماح لك باللعب بالمخططات الزمنية. لا تستند واجهة برمجة التطبيقات Animation Worklet API إلى واجهة برمجة التطبيقات WAAPI فقط، بل هي واجهة برمجة تطبيقات أساسية من المستوى الأدنى توضّح كيفية عمل واجهة برمجة التطبيقات WAAPI، وذلك وفقًا لمفهوم الويب القابل للتوسيع. من حيث بنية الجملة، يتشابهان بشكل كبير:

Animation Worklet WAAPI
new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)'
      },
      {
        transform: 'translateX(500px)'
      }
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY
    }
  ),
  document.timeline
).play();
      
        new Animation(

        new KeyframeEffect(
        document.querySelector('#a'),
        [
        {
        transform: 'translateX(0)'
        },
        {
        transform: 'translateX(500px)'
        }
        ],
        {
        duration: 2000,
        iterations: Number.POSITIVE_INFINITY
        }
        ),
        document.timeline
        ).play();
        

يكمن الاختلاف في المَعلمة الأولى، وهي اسم وحدة العمل التي تحرِّك هذا المؤثر المتحرك.

رصد الميزات

Chrome هو أول متصفّح يقدّم هذه الميزة، لذا عليك التأكّد من أنّ الرمز البرمجي لا يتوقّع ظهور AnimationWorklet فقط. لذلك، قبل تحميل worklet، يجب أن نتحقّق ممّا إذا كان متصفح المستخدم متوافقًا مع AnimationWorklet من خلال إجراء عملية تحقق بسيطة:

if ('animationWorklet' in CSS) {
  // AnimationWorklet is supported!
}

جارٍ تحميل الوظيفة المصغّرة

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

يجب التأكّد من تحميل وحدة عمل باسم "passthrough"، قبل تحديد الصور المتحركة:

// index.html
await CSS.animationWorklet.addModule('passthrough-aw.js');
// ... WorkletAnimation initialization from above ...

// passthrough-aw.js
registerAnimator(
  'passthrough',
  class {
    animate(currentTime, effect) {
      effect.localTime = currentTime;
    }
  }
);

ما الذي يحدث هنا؟ نحن بصدد تسجيل فئة كعنصر رسوم متحركة باستخدام registerAnimator() في AnimationWorklet، ونمنحها الاسم "passthrough". وهو الاسم نفسه الذي استخدمناه في أداة الإنشاء WorkletAnimation() أعلاه. بعد اكتمال التسجيل، سيتم حلّ الوعد الذي يعرضه addModule() وسنبدأ في إنشاء صور متحركة باستخدام هذه الوظيفة المصغّرة.

سيتمّ استدعاء طريقة animate() لدينا لكل إطار يريد المتصفّح عرضه، مع تمرير currentTime للإطار الزمني للحركة المتحرّكة بالإضافة إلى التأثير الذي تتم معالجته حاليًا. لدينا تأثير واحد فقط، وهو KeyframeEffect ونستخدم currentTime لضبط التأثير localTime، ولهذا السبب يُطلق على أداة الصور المتحركة هذه اسم "مرور". باستخدام هذا الرمز البرمجي لملف ‎worklet، يعمل كلّ من واجهة برمجة التطبيقات وAnimationWorklet أعلاه بالطريقة نفسها تمامًا، كما يمكنك الاطّلاع عليه في العرض التجريبي.

الوقت

المعلمة currentTime لطريقة animate() هي currentTime للجدول الزمني الذي مررنا به إلى الدالة الإنشائية WorkletAnimation(). في المثال السابق، لقد مررنا هذا الوقت إلى التأثير. ولكن بما أنّ هذا هو رمز JavaScript، يمكننا تشويش الوقت 💫

function remap(minIn, maxIn, minOut, maxOut, v) {
  return ((v - minIn) / (maxIn - minIn)) * (maxOut - minOut) + minOut;
}
registerAnimator(
  'sin',
  class {
    animate(currentTime, effect) {
      effect.localTime = remap(
        -1,
        1,
        0,
        2000,
        Math.sin((currentTime * 2 * Math.PI) / 2000)
      );
    }
  }
);

نأخذ Math.sin() من currentTime، ونعيد ربط هذه القيمة بالفاصل الزمني [0؛ 2000]، وهو النطاق الزمني الذي تم تحديد تأثيرنا له. الآن، تبدو الصورة المتحركة مختلفة جدًا، بدون تغيير اللقطات الرئيسية أو خيارات الصورة المتحركة. يمكن أن يكون رمز الوحدات المخصّصة معقدًا بشكل عشوائي، ويسمح لك بتحديد التأثيرات التي يتم تشغيلها بالترتيب والدرجة المحدّدَين آليًا.

الخيارات فوق الخيارات

قد تحتاج إلى إعادة استخدام وحدات عمل وتغيير أرقامها. لهذا السبب، يتيح لك ملف بدء WorkletAnimation تمرير عنصر خيارات إلى القطعة العاملة:

registerAnimator(
  'factor',
  class {
    constructor(options = {}) {
      this.factor = options.factor || 1;
    }
    animate(currentTime, effect) {
      effect.localTime = currentTime * this.factor;
    }
  }
);

new WorkletAnimation(
  'factor',
  new KeyframeEffect(
    document.querySelector('#b'),
    [
      /* ... same keyframes as before ... */
    ],
    {
      duration: 2000,
      iterations: Number.POSITIVE_INFINITY,
    }
  ),
  document.timeline,
  {factor: 0.5}
).play();

في هذا المثال، يتم تشغيل كلتا الصورتين المتحرّكتين باستخدام الرمز البرمجي نفسه، ولكن بخيارات مختلفة.

يُرجى إخبارنا بالولاية المحلية.

كما سبق أن ذكرت، إنّ أحد المشاكل الرئيسية التي تهدف أداة "شريحة عرض متحركة" إلى حلّها هي الرسوم المتحركة التي تعتمد على الحالة. يُسمح للعاملين في الرسوم المتحركة بالحفاظ على الحالة. ومع ذلك، فإنّ إحدى الميزات الأساسية لوحدات العمل هي أنّه يمكن نقلها إلى سلسلة رسائل مختلفة أو حتى إتلافها لتوفير الموارد، ما يؤدي أيضًا إلى إتلاف حالتها. لمنع فقدان الحالة، تقدّم أداة "عملية عرض متحركة" عنصر ربط يتمّ استدعاؤه قبل إتلاف أداة "عملية عرض متحركة"، ويمكنك استخدامه لعرض عنصر الحالة. سيتم تمرير هذا الكائن إلى الدالة الإنشائية عند إعادة إنشاء الوظيفة الصغيرة. وعند الإنشاء الأوّلي، ستكون هذه المَعلمة undefined.

registerAnimator(
  'randomspin',
  class {
    constructor(options = {}, state = {}) {
      this.direction = state.direction || (Math.random() > 0.5 ? 1 : -1);
    }
    animate(currentTime, effect) {
      // Some math to make sure that `localTime` is always > 0.
      effect.localTime = 2000 + this.direction * (currentTime % 2000);
    }
    destroy() {
      return {
        direction: this.direction,
      };
    }
  }
);

في كل مرة تُحدِّث فيها هذا العرض التوضيحي، تكون لديك فرصة 50/50 في اتجاه دوران المربع. إذا أراد المتصفّح إلغاء العمل ونقل بياناته إلى سلسلة محادثات مختلفة، سيكون هناك Math.random() آخر عند الإنشاء، ما قد يؤدي إلى تغيير مفاجئ في الاتجاه. لضمان عدم حدوث ذلك، نعرض الرسوم المتحرّكة الاتجاه الذي تم اختياره عشوائيًا على أنّه الحالة ونستخدمه في أداة الإنشاء، إذا تم توفيرها.

الربط بسلسلة الأحداث الفضائية الزمنية: ScrollTimeline

كما أوضح القسم السابق، تتيح لنا AnimationWorklet تحديد كيفية تأثير تقديم المخطط الزمني في تأثيرات المتحرّك برمجيًا. ولكن حتى الآن، كان المخطط الزمني دائمًا document.timeline، والذي يتتبّع الوقت.

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

new WorkletAnimation(
  'passthrough',
  new KeyframeEffect(
    document.querySelector('#a'),
    [
      {
        transform: 'translateX(0)',
      },
      {
        transform: 'translateX(500px)',
      },
    ],
    {
      duration: 2000,
      fill: 'both',
    }
  ),
  new ScrollTimeline({
    scrollSource: document.querySelector('main'),
    orientation: 'vertical', // "horizontal" or "vertical".
    timeRange: 2000,
  })
).play();

بدلاً من تمرير document.timeline، سننشئ ScrollTimeline جديدًا. كما قد تتوقّع، لا يستخدم ScrollTimeline الوقت، بل يستخدم موضع التمرير فيscrollSource لضبط currentTime في الوظيفة المصغّرة. عند التمرير إلى أعلى الصفحة (أو اليسار)، يعني ذلك أنّ القيمة هي currentTime = 0، بينما عند التمرير إلى أسفل الصفحة (أو اليمين)، يتم ضبط القيمة currentTime على timeRange. إذا اطّلعت على المربّع في هذا العرض التجريبي، يمكنك التحكّم في موضع المربّع الأحمر.

إذا أنشأت ScrollTimeline يتضمّن عنصرًا لا يمكن التمرير فيه، سيكون currentTime المعروض في المخطط الزمني هو NaN. لذلك، يجب أن تكون مستعدًا دائمًا NaN كcurrentTime، خاصةً مع وضع التصميم السريع الاستجابة في الاعتبار. غالبًا ما يكون من المنطقي ضبط القيمة التلقائية على 0.

لقد كان ربط الرسوم المتحركة بموقع التمرير من الأمور التي تم البحث عنها منذ فترة طويلة، ولكن لم يتم تحقيق ذلك على هذا المستوى من الدقّة (باستثناء الحلول البديلة غير المُحكمة باستخدام CSS3D). تسمح لك وحدات Animation Worklet بتنفيذ هذه التأثيرات بطريقة مباشرة مع تحقيق أداء عالٍ. على سبيل المثال: يوضّح تأثير التمرير المتغير مثل هذا العرض التجريبي أنّه يتطلب الآن بضع سطور فقط لتحديد صورة متحركة يتم تشغيلها من خلال التمرير.

الخيارات المتقدمة

التطبيقات المصغّرة

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

Compositor NSync

قد تعلم أنّ خصائص CSS معيّنة سريعة في عرض الصور المتحركة، في حين أنّ خصائص أخرى لا تكون كذلك. تحتاج بعض الخصائص فقط إلى بعض العمل على وحدة معالجة الرسومات لجعلها متحركة، بينما يفرض البعض الآخر على المتصفح إعادة تخطيط المستند بأكمله.

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

صَفْع على المعصم

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

وفي هذه الحالة، يُسمح بـ "تأخّر" وحدة العمل وفقًا للمواصفات. ويتأخّر عن المركّب، ويُسمح للمركّب بإعادة استخدام بيانات اللقطة الأخيرة للحفاظ على معدّل اللقطات في الثانية. سيبدو هذا الأمر من الناحية المرئية وكأنه تقطُّع، ولكن الاختلاف الكبير هو أنّ المتصفّح سيظل يستجيب لإدخالات المستخدم.

الخاتمة

هناك العديد من الجوانب في AnimationWorklet والفوائد التي تقدّمها للويب. والفوائد الواضحة هي مزيد من التحكم في الرسوم المتحركة وطرق جديدة لتحفيز الرسوم المتحركة لإضفاء مستوى جديد من الدقة المرئية على الويب. يسمح لك تصميم واجهات برمجة التطبيقات أيضًا بجعل تطبيقك أكثر مرونة في مواجهة الانقطاعات أثناء الأداء، مع الاستفادة من كل الميزات الجديدة في الوقت نفسه.

تتوفّر ميزة Animation Worklet في الإصدار Canary، ونهدف إلى طرحها في إصدار تجريبي أوّلي مع Chrome 71. نحن في انتظار تجاربك الجديدة الرائعة على الويب ومعرفة ما يمكننا تحسينه. هناك أيضًا مكتبة polyfill تمنحك واجهة برمجة التطبيقات نفسها، ولكنّها لا توفّر عزل الأداء.

يُرجى العِلم أنّ انتقالات CSS وحركات CSS لا تزال خيارات válida ويمكن أن تكون أبسط بكثير للحركات الأساسية. ولكن إذا أردت استخدام رسوم متحركة مميّزة، يمكنك الاستعانة بـ AnimationWorklet.