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

عزِّز الصور المتحركة في تطبيق الويب

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

هل تريد واجهة برمجة تطبيقات Animation API أخرى؟

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

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

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

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

النقطة المهمة هي أن كل هذه الأشياء مربكة ويصعب تنفيذها بكفاءة. يعتمد معظمها على الأحداث و/أو requestAnimationFrame، ما يعني أنّ معدّل 60 لقطة في الثانية قد يجعلك تعمل بسرعة 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 ملي ثانية، مما يعني أن الرسوم المتحركة ستبدأ (أو ستصبح "نشطة") عندما يصل المخطط الزمني إلى "وقت البدء"

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

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

جارٍ تحميل عمل صغير

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

نحتاج إلى التأكد من أننا حملنا عملاً مصغّرًا باسم "العبور"، قبل إعلان الرسوم المتحركة:

// 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، لنسمّيها "عبور". هذا هو الاسم نفسه الذي استخدمناه في الدالة الإنشائية WorkletAnimation() أعلاه. بعد اكتمال التسجيل، سيتم تنفيذ الوعد الذي يعرضه addModule()، ويمكننا البدء في إنشاء الصور المتحركة باستخدام هذا العمل المصغّر.

سيتم استدعاء طريقة animate() للمثيل لكل إطار يريد المتصفّح عرضه، ويمرر currentTime من المخطط الزمني للصورة المتحركة بالإضافة إلى التأثير الذي تجري معالجته حاليًا. ولدينا تأثير واحد فقط، KeyframeEffect ونحن نستخدم currentTime لضبط localTime للتأثير، ولذلك يطلق على أداة الرسوم المتحرّكة هذه اسم "مرور". باستخدام هذا الرمز الخاص بالوظيفة المصغّرة، تتصرف واجهة WAAPI و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 بتمرير كائن خيارات إلى 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() أخرى عند الإنشاء، ما قد يؤدي إلى تغيير الاتجاه بشكل مفاجئ. ولضمان عدم حدوث ذلك، نعيد الرسوم المتحركة الاتجاه الذي تم اختياره عشوائيًا على أنه state ونستخدمه في الدالة الإنشائية، إن تم توفيرها.

التعمق في التسلسل الزمني لوقت الفضاء: 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 بتنفيذ هذه التأثيرات بطريقة مباشرة مع تمتعها بأداء عالي. على سبيل المثال: يُظهر تأثير التمرير المنعكس مثل هذا العرض التوضيحي أنّ الأمر يستغرق الآن سطرين فقط لتحديد الرسوم المتحركة التي تعتمد على التمرير.

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

وظيفتان مصغّرتان

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

المكوِّن NSync

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

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

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

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

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

الخلاصة

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

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

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