تحسين الصور المتحركة في تطبيقك على الويب
الملخّص: تتيح لك أداة Animation Worklet إنشاء صور متحركة إلزامية يتم تشغيلها بمعدل عرض اللقطات الأصلي للجهاز للحصول على سلاسة إضافية بدون أي تقطُّع، وجعل صورك المتحركة أكثر مرونة في مواجهة التقطُّع في سلسلة المهام الرئيسية، ويمكن ربطها بالانتقال بدلاً من الوقت. تتوفّر ميزة Animation Worklet في Chrome Canary (بعد تفعيل الخيار "ميزات النظام الأساسي التجريبي للويب") ونخطّط لمرحلة تجربة وتقييم في الإصدار 71 من Chrome. يمكنك البدء في استخدامه كأحد التحسينات التدريجية اليوم.
هل هناك واجهة برمجة تطبيقات أخرى لإنشاء الرسوم المتحركة؟
في الواقع، لا، بل هو امتداد لما نقدّمه حاليًا، وذلك لسبب وجيه. لنبدأ من البداية. إذا أردت إضافة تأثيرات متحركة إلى أي عنصر DOM على الويب، لديك خياران ونصف: عمليات النقل في CSS لتطبيق عمليات النقل البسيطة من "أ" إلى "ب"، والتأثيرات المتحركة في CSS لتطبيق التأثيرات المتحركة المستندة إلى الوقت والتي قد تكون دورية ومعقدة بشكل أكبر، وWeb Animations API (WAAPI) لتطبيق تأثيرات متحركة معقدة بشكل تعسّفي تقريبًا. تبدو مصفّفة توافق WAAPI قاتمة جدًا، ولكن إنّها في طريقها إلى التحسن. وحتى ذلك الحين، يمكنك استخدام polyfill.
إنّ القاسم المشترك بين كل هذه الطرق هو أنّها لا تعتمد على حالة وتكون مستندة إلى الزمن. ولكن بعض التأثيرات التي يحاول المطوّرون استخدامها ليست استنادًا إلى الوقت أو غير مرتبطة بحالة. على سبيل المثال، فإنّ شريط التمرير المائل الشهير يعتمد على التمرير، كما يشير الاسم. إنّ تنفيذ أداة التمرير بزاوية متباينة عالية الأداء على الويب أمر صعب بشكلٍ غير متوقَّع اليوم.
ماذا عن ميزة "عدم ربط بحساب على Google"؟ على سبيل المثال، شريط العناوين في 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 the
durationoptions. Since we have a duration of 2000ms, we will reach the middle keyframe when the timeline's
currentTimeis
startTime + 3000 + 1000and the last keyframe at
startTime + 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,
};
}
}
);
في كل مرة تُعيد فيها تحميل هذا العرض التجريبي، يكون لديك احتمال متساوٍ
لتحديد اتجاه دوران المربع. إذا أراد المتصفّح إلغاء
العمل ونقل بياناته إلى سلسلة محادثات مختلفة، سيكون هناك 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 التي يتمّ تطبيق تأثيرات متحركة عليها، قد يحتاج المتصفّح إلى أن ينفّذ ملفّ الترميز المرئي عملية التركيب فقط، بينما تحتاج الملفّات الأخرى إلى تنفيذ التنسيق، وهي عملية يمكن أن ينفّذها فقط الخيط الرئيسي. استنادًا إلى الملفّات التي تريد تطبيق تأثيرات متحركة عليها، سيتم ربط وحدة العمل الخاصة بالتأثيرات المتحركة بالخيط الرئيسي أو تشغيلها في سلسلة محادثات منفصلة متزامنة مع ملف الترميز المرئي.
Slap on the wrist
عادةً ما تكون هناك عملية تركيب واحدة فقط يمكن مشاركتها على مستوى علامات تبويب متعددة، لأنّ وحدة معالجة الرسومات هي مورد يُطلب استخدامه بشكل كبير. إذا تم حظر أداة الدمج بطريقة ما، يتوقّف المتصفّح بأكمله عن العمل ولا يستجيب لإدخالات المستخدم. ويجب تجنُّب ذلك بأي ثمن. ماذا يحدث إذا تعذّر على وحدة العمل تقديم البيانات التي يحتاجها المُركِّب في الوقت المناسب لمعالجة الإطار؟
وفي هذه الحالة، يُسمح بـ "تأخّر" وحدة العمل وفقًا للمواصفات. ويتأخّر عن المركّب، ويُسمح للمركّب بإعادة استخدام بيانات اللقطة الأخيرة للحفاظ على معدّل اللقطات في الثانية. سيبدو هذا الأمر من الناحية المرئية وكأنه تقطُّع، ولكن الاختلاف الكبير هو أنّ المتصفّح سيظل يستجيب لإدخالات المستخدم.
الخاتمة
هناك العديد من الجوانب في AnimationWorklet والفوائد التي تقدّمها للويب. وتشمل المزايا الواضحة إمكانية التحكّم بشكل أكبر في الصور المتحركة وطرقًا جديدة لعرضها بهدف تحقيق مستوى جديد من الدقّة المرئية على الويب. يسمح لك تصميم واجهات برمجة التطبيقات أيضًا بجعل تطبيقك أكثر مرونة في مواجهة الانقطاعات أثناء الأداء، مع الاستفادة من كل الميزات الجديدة في الوقت نفسه.
تتوفّر ميزة Animation Worklet في الإصدار Canary، ونهدف إلى طرحها في إصدار تجريبي أوّلي مع Chrome 71. نحن في انتظار تجاربك الجديدة الرائعة على الويب ومعرفة ما يمكننا تحسينه. هناك أيضًا مكتبة polyfill تمنحك واجهة برمجة التطبيقات نفسها، ولكنّها لا توفّر عزل الأداء.
يُرجى العِلم أنّ انتقالات CSS وحركات CSS لا تزال خيارات válida ويمكن أن تكون أبسط بكثير للحركات الأساسية. ولكن إذا أردت استخدام رسوم متحركة مميّزة، يمكنك الاستعانة بـ AnimationWorklet.