إنشاء صور متحركة قابلة للتوسيع والتصغير

TL;DR

استخدِم أدوات تحويل الحجم عند تحريك المقاطع. يمكنك منع تمدد الأطفال وانحرافهم أثناء الرسوم المتحركة عن طريق تحجيمها بشكل عكسي.

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

على سبيل المثال، القائمة التي يتم توسيعها:

تحقّق بعض خيارات إنشاء هذه الميزة أداءً أفضل من غيرها.

استخدام غير صحيح: إضافة تأثيرات متحركة للعرض والارتفاع في عنصر حاوية

يمكنك استخدام بعض علامات CSS لإضافة حركة إلى العرض والارتفاع في عنصر الحاوية.

.menu {
  overflow: hidden;
  width: 350px;
  height: 600px;
  transition: width 600ms ease-out, height 600ms ease-out;
}

.menu--collapsed {
  width: 200px;
  height: 60px;
}

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

سيئ: استخدِم خصائص مقطع CSS أو مسار المقطع

يمكن استخدام السمة clip (التي تم إيقافها نهائيًا الآن) كبديل لإضافة تأثيرات متحركة إلى width وheight لإنشاء تأثير التوسيع والتقليص. يمكنك استخدام clip-path بدلاً من ذلك. ومع ذلك، فإنّ استخدام clip-path أقل توافقًا من clip. لكن تم إيقاف clip نهائيًا. نحو اليمين لا داعي للقلق، فهذا الحلّ ليس على مستوى توقعاتك على أي حال.

.menu {
  position: absolute;
  clip: rect(0px 112px 175px 0px);
  transition: clip 600ms ease-out;
}

.menu--collapsed {
  clip: rect(0px 70px 34px 0px);
}

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

جيد: تصاميم الرسوم المتحركة

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

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

الخطوة 1: احتساب حالتَي البدء والانتهاء

باستخدام نهج يستخدم الرسوم المتحركة للمقياس، فإن الخطوة الأولى هي قراءة العناصر التي تخبرك بالحجم الذي يجب أن تكون القائمة عند تصغيرها وعند توسيعها. قد لا تتمكّن في بعض الحالات من الحصول على كلتا المقطعتَين من المعلومات دفعة واحدة، وقد تحتاج إلى تبديل بعض الفئات لكي تتمكّن من قراءة الحالات المختلفة للمكوّن. إذا كنت بحاجة إلى إجراء ذلك، عليك الحذر: يفرض getBoundingClientRect() (أو offsetWidth وoffsetHeight) على المتصفّح تنفيذ عمليات تطبيق الأنماط والتنسيقات إذا تغيّرت الأنماط منذ آخر مرة تم فيها تنفيذها.

function calculateCollapsedScale () {
    // The menu title can act as the marker for the collapsed state.
    const collapsed = menuTitle.getBoundingClientRect();

    // Whereas the menu as a whole (title plus items) can act as
    // a proxy for the expanded state.
    const expanded = menu.getBoundingClientRect();
    return {
    x: collapsed.width / expanded.width,
    y: collapsed.height / expanded.height
    };
}

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

ولكن، مهلاً! من المؤكد أن هذا من شأنه أن يؤدي إلى توسيع نطاق محتويات القائمة أيضًا، أليس كذلك؟ نعم، كما هو موضّح أدناه.

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

  1. إنّ التحويل المضاد هو أيضًا عملية توسيع. وهذا جيد لأنّه يمكن أيضًا تسريعه، تمامًا مثل الصورة المتحركة في الحاوية. قد تحتاج إلى التأكّد من أنّ العناصر التي يتمّ فيها استخدام الصور المتحركة تحصل على طبقة تركيب خاصة بها (لتتمكّن وحدة معالجة الرسومات من المساعدة)، ويمكنك لذلك إضافة will-change: transform إلى العنصر أو backface-visiblity: hidden إذا كنت بحاجة إلى إتاحة المحتوى لمتصفّحات قديمة.

  2. يجب حساب التحويل المضاد لكل إطار. هذا هو المكان الذي يمكن أن تصبح فيه الأمور أكثر تعقيدًا، لأنه بافتراض أن الرسوم المتحركة في CSS وتستخدم وظيفة التخفيف، يجب معارضة التخفيف نفسه عند تحريك التحويل المضاد. ومع ذلك، ليس من الواضح تمامًا احتساب منحنى الانحدار العكسي cubic-bezier(0, 0, 0.3, 1) على سبيل المثال.

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

الخطوة 2: إنشاء صور متحركة باستخدام CSS

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

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

function createKeyframeAnimation () {
    // Figure out the size of the element when collapsed.
    let {x, y} = calculateCollapsedScale();
    let animation = '';
    let inverseAnimation = '';

    for (let step = 0; step <= 100; step++) {
    // Remap the step value to an eased one.
    let easedStep = ease(step / 100);

    // Calculate the scale of the element.
    const xScale = x + (1 - x) * easedStep;
    const yScale = y + (1 - y) * easedStep;

    animation += `${step}% {
        transform: scale(${xScale}, ${yScale});
    }`;

    // And now the inverse for the contents.
    const invXScale = 1 / xScale;
    const invYScale = 1 / yScale;
    inverseAnimation += `${step}% {
        transform: scale(${invXScale}, ${invYScale});
    }`;

    }

    return `
    @keyframes menuAnimation {
    ${animation}
    }

    @keyframes menuContentsAnimation {
    ${inverseAnimation}
    }`;
}

قد يتساءل الأشخاص الفضوليون عن دالة ease() داخل حلقة for. يمكنك استخدام إجراء مماثل لربط القيم من 0 إلى 1 بقيمة مخفَّضة.

function ease (v, pow=4) {
  return 1 - Math.pow(1 - v, pow);
}

يمكنك أيضًا استخدام بحث Google لعرض شكل ذلك. رائع. إذا كنت بحاجة إلى معادلات تسهيل أخرى، يمكنك الاطّلاع على Tween.js من Soledad Penadés، التي تحتوي على مجموعة كبيرة منها.

الخطوة 3: تفعيل الرسومات المتحركة في CSS

بعد إنشاء هذه الرسوم المتحركة وعرضها على الصفحة باستخدام JavaScript، تكون الخطوة الأخيرة هي تبديل الفئات التي تتيح الرسوم المتحركة.

.menu--expanded {
  animation-name: menuAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

.menu__contents--expanded {
  animation-name: menuContentsAnimation;
  animation-duration: 0.2s;
  animation-timing-function: linear;
}

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

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

const xScale = 1 + (x - 1) * easedStep;
const yScale = 1 + (y - 1) * easedStep;

نسخة أكثر تقدمًا: العروض الدوّارة

من الممكن أيضًا استخدام هذه التقنية لإنشاء صور متحركة دائرية للتوسيع والتقليص.

تتشابه المبادئ إلى حد كبير مع الإصدار السابق، حيث يتم تغيير حجم عنصر معيّن و تغيير حجم عناصره الثانوية بشكل معاكس. في هذه الحالة، يحتوي العنصر الذي يتم توسيعه على border-radius بنسبة %50، ما يجعله دائريًا، ويتم لفّه بعنصر آخر يحتوي على overflow: hidden، ما يعني أنّك لن ترى الدائرة تتوسع خارج حدود العنصر.

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

يمكن العثور على الرمز البرمجي لتأثير التوسيع الدائري في مستودع GitHub.

الاستنتاجات

إليك طريقة لإنشاء رسوم متحركة فعّالة للمقاطع باستخدام عمليات التحويل على مستوى الحجم. في العادة، يكون من الرائع تسريع الرسوم المتحركة للمقاطع (هناك خطأ في Chromium أنشأه Jake Archibald)، ولكن إلى أن نتمكن من ذلك، يجب الحذر عند إضافة رسوم متحركة إلى clip أو clip-path، وتجنُّب إضافة رسوم متحركة إلى width أو height.

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

if ('animate' in HTMLElement.prototype) {
    // Animate with Web Animations.
} else {
    // Fall back to generated CSS Animations or JS.
}

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

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