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

روبرت فلاك
روبرت فلاك

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

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

النص المختصر (TL:DR)

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

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

حلول المناظرات للمشاكل

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

سيئ: استخدام أحداث التمرير

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

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

سيئ: جارٍ تحديث background-position

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

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

CSS بتنسيق ثلاثي الأبعاد

حقّق كل من سكوت كيلوم وكيث كلارك عملاً كبيرًا في مجال استخدام لغة 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>

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

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

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

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

آلية عمل هذا الأسلوب

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

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

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

الخلاصة

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

استمتع بوقتك وأخبرنا كيف يمكنك اللعب.