اختلاف الأداء

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

صورة توضيحية لتأثير اختلاف المنظر

الملخّص

  • لا تستخدِم أحداث الانتقال للأعلى أو للأسفل أو background-position لإنشاء صور متحركة للتأثير البصري.
  • استخدِم عمليات التحويل الثلاثية الأبعاد في CSS لإنشاء تأثير تمويه أكثر دقة.
  • بالنسبة إلى Safari للأجهزة الجوّالة، استخدِم position: sticky لضمان انتشار تأثير التمويه.

إذا كنت تريد استخدام حلّ جاهز، انتقِل إلى مستودع GitHub الخاص بـ "عيّنات عناصر واجهة المستخدم" واحصل على ملف Parallax helper JS. يمكنك الاطّلاع على عرض تجريبي مباشر لشريط التمرير المتغير المدى في مستودع GitHub.

أدوات حلّ المشاكل

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

استخدام أحداث الانتقال ضمن الصفحة

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

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

خطأ: جارٍ تعديل background-position

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

إذا أردنا تحقيق تأثير التماثل البصري، نحتاج إلى عنصر يمكن تطبيقه كعنصر متسارع (يعني ذلك اليوم الالتزام بعمليات التحويل والشفافية)، ولا يعتمد على أحداث الانتقال.

CSS في 3D

أجرى كلّ من سكوت كليمون وكيث كلارك عملاً هامًا في مجال استخدام CSS 3D لتحقيق الحركة التماثلية، والتقنية التي يستخدمانها هي على النحو التالي:

  • يمكنك إعداد عنصر يحتوي على عنصر آخر للتمرير باستخدام overflow-y: scroll (وربما overflow-x: hidden).
  • طبِّق على هذا العنصر نفسه قيمة perspective، واضبط perspective-origin على top left أو 0 0.
  • على عناصر هذا العنصر، طبِّق ترجمة في محور Z، وكبِّرها مجددًا لتوفير حركة تمويه بدون التأثير في حجمها على الشاشة.

يظهر ملف CSS لهذا النهج على النحو التالي:

.container {
  width: 100%;
  height: 100%;
  overflow-x: hidden;
  overflow-y: scroll;
  perspective: 1px;
  perspective-origin: 0 0;
}

.parallax-child {
  transform-origin: 0 0;
  transform: translateZ(-2px) scale(3);
}

ويفترض ذلك استخدام مقتطف HTML على النحو التالي:

<div class="container">
    <div class="parallax-child"></div>
</div>

تعديل المقياس للمنظور

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

في حالة الرمز أعلاه، تكون المنظور 1 بكسل، وتكون المسافة Z لملف parallax-child هي -2 بكسل. وهذا يعني أنّه يجب تكبير حجم العنصر بمقدار 3 أضعاف، وهي القيمة التي تم إدخالها في الرمز: scale(3).

بالنسبة إلى أي محتوى لم يتم تطبيق قيمة translateZ عليه، يمكنك استبدال القيمة بصفر. وهذا يعني أنّ المقياس هو (perspective - 0) / perspective، ما يؤدي إلى الحصول على قيمة 1، ما يعني أنّه لم يتم تعديله للتكبير أو التصغير. هذا مفيد جدًا.

آلية عمل هذا النهج

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

ومع ذلك، يؤدي تطبيق قيمة منظور على عنصر التمرير إلى عرقلة هذه العملية، إذ يغيّر المصفوفات التي تستند إليها عملية التحويل. الآن، قد يؤدي التمرير بمسافة 300 بكسل إلى تحريك العناصر الثانوية بمقدار 150 بكسل فقط، استنادًا إلى قيم perspective وtranslateZ التي اخترتها. إذا كانت قيمة translateZ للعنصر هي 0، سيتم التمرير فيها بنسبة 1:1 (كما كان من قبل)، ولكن سيتم التمرير في عنصر تابع تم دفعه في محور Z بعيدًا عن نقطة الأصل في المنظور بمعدل مختلف. النتيجة النهائية: تأثير التمويه. ومن المهم جدًا أن يتم التعامل مع ذلك كجزء من آلية الانتقال الداخلي للمتصفّح تلقائيًا، ما يعني أنّه ليس هناك حاجة إلى الاستماع إلى أحداث scroll أو تغيير background-position.

عيوب Safari للأجهزة الجوّالة

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

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>

في رمز HTML أعلاه، العنصر .parallax-container جديد، وسيؤدي إلى تسطيح قيمة perspective بشكل فعّال، ما يؤدي إلى فقدان تأثير التمويه. في معظم الحالات، يكون الحلّ بسيطًا جدًا: يمكنك إضافة transform-style: preserve-3d إلى العنصر، ما يؤدي إلى نشر أي تأثيرات ثلاثية الأبعاد (مثل قيمة المنظور) التي تم تطبيقها في أعلى الشجرة.

.parallax-container {
  transform-style: preserve-3d;
}

في ما يتعلّق بتطبيق Mobile Safari، تكون الأمور أكثر تعقيدًا. إنّ تطبيق overflow-y: scroll على عنصر الحاوية يعمل من الناحية الفنية، ولكن بتكلفة عدم التمكّن من رمي عنصر التمرير. الحلّ هو إضافة -webkit-overflow-scrolling: touch، ولكنّ ذلك سيؤدي أيضًا إلى تسطيح perspective ولن نحصل على أي تأثير تمويه.

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

position: sticky لإنقاذ الموقف

في الواقع، تتوفّر بعض المساعدة في شكل position: sticky، وهي تهدف إلى السماح للعناصر "بالالتصاق" بأعلى مساحة العرض أو عنصر رئيسي معيّن أثناء الانتقال للأعلى أو للأسفل. إنّ المواصفات، مثل معظمها، ضخمة إلى حدٍ ما، ولكنها تحتوي على معلومات مفيدة:

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

من خلال تطبيق position: -webkit-sticky على عنصر التماثل البصري، يمكننا "عكس" تأثير التسطيح في -webkit-overflow-scrolling: touch بفعالية. يضمن ذلك أن يشير عنصر التمويه البصري إلى أقرب سلف باستخدام مربّع لفائف، وهو في هذه الحالة .container. بعد ذلك، على غرار ما سبق، يطبّق .parallax-container قيمة perspective، ما يؤدي إلى تغيير إزاحة التمرير المحسوبة وإنشاء تأثير التمويه.

<div class="container">
    <div class="parallax-container">
    <div class="parallax-child"></div>
    </div>
</div>
.container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

.parallax-container {
  perspective: 1px;
}

.parallax-child {
  position: -webkit-sticky;
  top: 0px;
  transform: translate(-2px) scale(3);
}

يؤدي ذلك إلى استعادة تأثير التمويه في Safari للأجهزة الجوّالة، ما يشكّل خبرًا رائعًا من جميع النواحي.

تحذيرات بشأن المواضع الثابتة

يوجد اختلاف هنا، مع ذلك: يغيّر position: sticky أسلوب التداخل. يحاول وضع العنصر في موضع ثابت تثبيت العنصر في الحاوية التي يتم التمرير فيها، في حين لا يفعل ذلك الإصدار غير الثابت. وهذا يعني أنّ تأثير التماثل البصري الذي يتضمّن عناصر ثابتة يكون عكس تأثير التماثل البصري الذي لا يتضمّن عناصر ثابتة:

  • مع position: sticky، كلما كان العنصر أقرب إلى z=0، كان التحرك أبطأ.
  • بدون position: sticky، كلما كان العنصر أقرب إلى z=0، زاد تنقله.

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

لقطة شاشة لتأثير اختلاف المنظر

عرض توضيحي من إعداد "روبرت فلاك" يعرض كيفية تأثير position: sticky في التمرير بزاوية متناوبة.

أخطاء متنوعة وحلول بديلة

ومع ذلك، لا تزال هناك بعض المشاكل التي يجب حلّها:

  • لا تتوفّر ميزة "العناصر الثابتة" بشكل متسق. لا يزال يتم تنفيذ الميزة في Chrome، ولا يتوفّر الإصدار المتوافق مع Edge على الإطلاق، ويواجه Firefox أخطاء في الرسم عند دمج العناصر الثابتة مع عمليات التحويل المنظور. في مثل هذه الحالات، من المفيد إضافة رمز صغير لإضافة position: sticky (الإصدار الذي يحتوي على البادئة -webkit-) فقط عند الحاجة، وهو مخصّص لمتصفّح Safari على الأجهزة الجوّالة فقط.
  • لا يعمل التأثير "ببساطة" في Edge. يحاول Edge معالجة الانتقال للأعلى أو للأسفل على مستوى نظام التشغيل، وهو أمر جيد بشكل عام، ولكن في هذه الحالة يمنع ذلك من رصد التغييرات في المنظور أثناء الانتقال للأعلى أو للأسفل. لحلّ هذه المشكلة، يمكنك إضافة عنصر في موضع ثابت، لأنّ هذا الإجراء يبدو أنّه ينقل Edge إلى طريقة لفّ الصفحة لا تعتمد على نظام التشغيل، ويضمن مراعاة التغييرات في المنظور.
  • "أصبح محتوى الصفحة ضخمًا جدًا" تأخذ العديد من المتصفّحات مقياس العرض في الاعتبار عند تحديد حجم محتوى الصفحة، ولكن Chrome وSafari لا يأخذان المنظور في الاعتبار. وبالتالي، إذا تم تطبيق مقياس 3x على عنصر، قد تظهر لك أشرطة التمرير وما شابه ذلك، حتى إذا كان العنصر بمقياس 1x بعد تطبيق perspective. يمكنك حلّ هذه المشكلة من خلال تصغير العناصر من الزاوية اليمنى السفلى (باستخدام transform-origin: bottom right)، ما يؤدي إلى تكبير العناصر الكبيرة جدًا لتظهر في "المنطقة السلبية" (عادةً في أعلى يمين) المنطقة القابلة للتنقّل. ولا تسمح لك مناطق التمرير أبدًا بالاطّلاع على المحتوى في المنطقة السلبية أو الانتقال إليه.

الخاتمة

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

يُرجى تجربة هذه الميزة وإعلامنا برأيك.