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

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

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

TL;DR

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

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

Problem parallaxers

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

سيئ: استخدام أحداث الانتقال ضمن الصفحات

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

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

غير صالح: تعديل background-position

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

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

CSS في 3D

لقد قدّم كل من سكوت كيلوم وكيث كلارك مساهمات كبيرة في مجال استخدام CSS ثلاثي الأبعاد لتحقيق تأثير المنظر المجسم، والأسلوب الذي يستخدمانه هو في الأساس ما يلي:

  • اضبط عنصرًا حاويًا للتمرير باستخدام 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>

ضبط المقياس للمنظور

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

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

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

طريقة عمل هذا النهج

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

ومع ذلك، يؤدي تطبيق قيمة المنظور على عنصر التمرير إلى إرباك هذه العملية، إذ يغيّر المصفوفات التي تستند إليها عملية تحويل التمرير. قد يؤدي التمرير بمقدار 300 بكسل الآن إلى تحريك العناصر الثانوية بمقدار 150 بكسل فقط، وذلك استنادًا إلى قيمتَي perspective وtranslateZ اللتين اخترتهما. إذا كان العنصر يتضمّن القيمة 0 في translateZ، سيتم تحريكه بمعدّل 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);
}

يؤدي ذلك إلى استعادة تأثير المنظر المجسم في Mobile 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 لعينات عناصر واجهة المستخدم.

جرِّبها وأطلِعنا على تجربتك.