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

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

الاستخدام غير الجيد: استخدام سمة clip أو clip-path في 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 فقط. لا تتوفّر ميزة Web Animations بشكل كبير، ولكن يمكنك استخدام ميزة التحسين التدريجي لاستخدامها إذا كانت متاحة.

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

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

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