CSS Deep-Dive - trim3d() للحصول على شريط تمرير مخصّص ومثالي للإطارات

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

TL;DR

ألا تهتم بالتفاصيل الصغيرة؟ أنت تريد فقط إلقاء نظرة على عرض توضيحي لقطط ناين والحصول على المكتبة؟ يمكنك العثور على رمز العرض التوضيحي في مستودع GitHub.

LAM؛WRA (طويل ورياضي؛ تتم القراءة على أي حال)

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

ملخّص

لنبدأ بملخّص طريقة عمل شريط تمرير اختلاف المنظر.

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

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

الخطوة 0: ماذا نريد أن نفعل؟

أشرطة التمرير. هذا ما سنقوم بإنشائه. ولكن هل فكرت حقًا حول ما يفعلونه؟ أنا بالتأكيد لم أفعل ذلك. تُعد أشرطة التمرير مؤشرًا مقدار المحتوى المتاح المعروض حاليًا ومقدار التقدّم التي حققها القارئ. إذا قمت بالتمرير لأسفل، فإن شريط التمرير يشير إلى أنك تحرز تقدمًا نحو النهاية. إذا كان كل المحتوى مناسبًا في إطار العرض، يكون شريط التمرير عادةً مخفيًا. إذا زاد ارتفاع المحتوى بمقدار مرتين عن إطار العرض، فإن شريط التمرير يملأ 1⁄2 ارتفاع إطار العرض محتوى يساوي 3 أضعاف ارتفاع يعمل إطار العرض على ضبط شريط التمرير بحيث يتناسب مع 1⁄3 إطار العرض، وما إلى ذلك. حيث ترى النمط. بدلاً من التمرير، يمكنك أيضًا النقر على شريط التمرير وسحبه للتنقل الموقع بشكل أسرع. وهذا قدر مدهش من السلوك الذي يُظهر عنصر من هذا القبيل. دعونا نخوض معركة واحدة في كل مرة.

الخطوة 1: وضعه بشكل عكسي

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

للحصول على أي نوع من إسقاط المنظور من الناحية الرياضية، عليك من المحتمل أن ينتهي بك الأمر باستخدام الإحداثيات المتجانسة. لن أتحدث بالتفصيل عن ماهيتها وسبب عملها، ولكن يمكنك التفكير في مثل الإحداثيات الثلاثية الأبعاد مع إحداثي رابع إضافي يسمى w. هذا النمط التنسيق 1 إلا إذا كنت تريد تشويه المنظور. أر لا داعي للقلق بشأن تفاصيل w لأننا لن نستخدم أي قيمة أخرى غير 1. وبالتالي تكون جميع النقاط من الآن فصاعدًا متجهات رباعية الأبعاد [x, y, z, w=1] وبالتالي تحتاج المصفوفات أن يكون 4x4 أيضًا.

وفي إحدى الحالات التي يمكنك فيها أن ترى أن CSS تستخدم إحداثيات متجانسة أسفل هو تحديد مصفوفات 4×4 الخاصة بك في خاصية التحويل باستخدام matrix3d(). تأخذ matrix3d 16 وسيطة (لأن المصفوفة 4×4)، مع تحديد عمود بعد الآخر. لذلك يمكننا استخدام هذه الدالة تحديد عمليات التدوير والترجمات يدويًا وما إلى ذلك. ولكن ما يسمح لنا أيضًا بفعله عبثًا في هذا التنسيق w!

قبل أن نتمكن من الاستفادة من matrix3d()، نحتاج إلى سياق ثلاثي الأبعاد لأنه بدون لن يكون هناك تشويه في المنظور ثلاثي الأبعاد ولن تكون هناك حاجة إلى الإحداثيات المتجانسة. لإنشاء سياق ثلاثي الأبعاد، نحتاج إلى حاوية تحتوي على perspective وبعض العناصر الداخلية التي يمكننا تحويلها في قمت بإنشاء مساحة ثلاثية الأبعاد. بالنسبة مثال:

يشير هذا المصطلح إلى رمز CSS الذي يشوه علامة div باستخدام رمز CSS.
    سمة المنظور.

يعالج محرّك CSS العناصر داخل حاوية المنظور. على النحو التالي:

  • تحويل كل زاوية (رأس) لعنصر إلى إحداثيات متجانسة [x,y,z,w]، نسبةً إلى حاوية المنظور.
  • طبِّق كل معاملات العنصر كمصفوفات من اليمين إلى اليسار.
  • إذا كان عنصر المنظور قابلاً للتمرير، فقم بتطبيق مصفوفة تمرير.
  • تطبيق مصفوفة المنظور.

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

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

يقع الصندوق داخل حاوية منظور تتضمّن القيمة p للحقل "perspective". ولنفترض أن الحاوية قابلة للتمرير ويتم تمريرها لأسفل بواسطة ن بكسل.

مصفوفة مراتب مصفوفة مرات التمرير في مصفوفة تحويل العنصر
  يساوي أربعة في أربعة مصفوفة هوية مع سالب واحد على p في الصف الرابع
  العمود الثالث ضرب أربعة في أربعة مصفوفة هوية بـ سالب ن في الثانية
  والعمود الرابع وضرب العنصر في مصفوفة تحويل.

المصفوفة الأولى هي مصفوفة المنظور، والمصفوفة الثانية هي مصفوفة التمرير ومصفوفة. خلاصة القول: تتمثل مهمة مصفوفة التمرير في جعل أحد العناصر يتحرك لأعلى عندما ينتقل إلى أسفل، وبالتالي علامة السالب.

أما بالنسبة لشريط التمرير، فإننا نريد المقابل - نريد أن الانتقال للأسفل عندما ننتقل إلى لأسفل. إليك بعض الأشياء التي يمكننا فيها استخدام إحدى الحيل: عكس إحداثي w لزاوية المربع. إذا كان الإحداثي w 1-، ستسري جميع الترجمات في الاتجاه المعاكس. إذن كيف يمكننا ذلك؟ حيث يقوم محرك CSS بتحويل زوايا الصندوق إلى إحداثيات متجانسة، وتعيين w على 1. حان وقت تألُّق "matrix3d()".

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    );
}

ولن تفعل هذه المصفوفة سوى إلغاء w. لذلك عندما يكون لدى محرك CSS حولت كل زاوية إلى متجه للشكل [x,y,z,1]، فستتحول المصفوفة نحولها إلى [x,y,z,-1].

مصفوفة من أربعة على أربعة متطابقة مع سالب واحد على p في الصف الرابع
  العمود الثالث ضرب أربعة في أربعة مصفوفة هوية بـ سالب ن في الثانية
  الصف الرابع ضرب أربعة في أربعة مصفوفة هوية بسالب واحد في
  الصف الرابع والعمود الرابع مضروبًا في أربعة متجهات الأبعاد x، y، z، 1 يساوي أربعة
  في أربع مصفوفة هوية مع سالب واحد على p في العمود الثالث للصف الرابع،
  مطروحًا منه n في العمود الرابع وسالب واحد في الصف الرابع
  العمود الرابع يساوي أربعة متجهات الأبعاد x، y جمع n، z، سالب z فوق
  p ناقص 1.

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

ومع ذلك، إذا وضعنا هذه المصفوفة للتو في مثال، فلن يتم عرض العنصر. وذلك لأن مواصفات CSS تتطلب رأس الصفحة مع w < 0 يمنع عرض العنصر. ونظرًا لأن z قيمة الإحداثي هي 0 حاليًا، وقيمة p 1، وتساوي w -1.

لحسن الحظ، يمكننا اختيار قيمة z! للتأكد من أننا انتهى بنا الأمر بـ w=1، نحتاج لتعيين z = -2.

.box {
  transform:
    matrix3d(
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 1, 0,
      0, 0, 0, -1
    )
    translateZ(-2px);
}

حسنًا، عودة المربع

الخطوة 2: تنفيذ الحركة

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

<div class="container">
    <div class="box"></div>
    <span class="spacer"></span>
</div>

<style>
/* … all the styles from the previous example … */
.container {
    overflow: scroll;
}
.spacer {
    display: block;
    height: 500px;
}
</style>

والآن مرِّر المربّع. ينتقل المربع الأحمر لأسفل.

الخطوة 3: تحديد مقاس

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

عادةً ما يتكون شريط التمرير من "عجلة" و"مسار"، بينما لا يكون المسار دائمًا مرئية. يتناسب ارتفاع الإبهام بشكل مباشر مع مقدار المحتوى مرئيًا.

<script>
    const scroller = document.querySelector('.container');
    const thumb = document.querySelector('.box');
    const scrollerHeight = scroller.getBoundingClientRect().height;
    thumb.style.height = /* ??? */;
</script>

يمثّل scrollerHeight ارتفاع العنصر القابل للتمرير، بينما وscroller.scrollHeight هو إجمالي ارتفاع المحتوى القابل للتمرير. scrollerHeight/scroller.scrollHeight هو الجزء الذي يمثل الجزء مرئية. يجب أن تكون نسبة المساحة الرأسية التي تغطيها الإبهام مساوية نسبة المحتوى المرئي:

ارتفاع نقطة نمط الإبهام أعلى شريط التمرير يساوي ارتفاع شريط التمرير
  أعلى شريط تمرير نقطة ارتفاع التمرير إذا وفقط إذا كان ارتفاع نقطة نمط نقطة الإبهام
  يساوي ارتفاع شريط التمرير مضروبًا ارتفاع شريط التمرير فوق نقطة تمرير نقطة التمرير
  الارتفاع.
<script>
    // …
    thumb.style.height =
    scrollerHeight * scrollerHeight / scroller.scrollHeight + 'px';
    // Accommodate for native scrollbars
    thumb.style.right =
    (scroller.clientWidth - scroller.getBoundingClientRect().width) + 'px';
</script>

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

وتكمن المعلومات الحاسمة في أننا نريد من الحافة السفلية تتماشى مع الحافة السفلية للعنصر القابل للتمرير عند التمرير وصولاً إلى أقصى إلى الأسفل. بمعنى آخر: إذا انتقلنا scroller.scrollHeight - scroller.height بكسل، نريد أن يكون إبهامنا تمت الترجمة بواسطة scroller.height - thumb.height. مقابل كل بكسل من أشرطة التمرير، نريد أن يحرك إبهامنا جزءًا من البكسل:

العامل يساوي ارتفاع نقطة شريط التمرير مطروحًا منه ارتفاع نقطة الإبهام فوق شريط التمرير
  ارتفاع نقطة التمرير مطروحًا منه ارتفاع نقطة شريط التمرير

هذا هو عامل التوسع لدينا. نحن الآن بحاجة إلى تحويل عامل القياس إلى على طول المحور z، وهو ما قمنا به بالفعل في تمرير اختلاف المنظر . وفقًا للخريطة، القسم ذو الصلة في المواصفات: عامل التحجيم يساوي p/(p − z). يمكننا حل هذه المعادلة من z إلى اكتشاف مقدار ما نحتاج إليه لترجمة رمز الإبهام على طول المحور z. لكن اجعل مع الأخذ في الاعتبار أنه بسبب التلاعب في الإحداثيات، نحتاج إلى ترجمة -2px إضافية على طول z. لاحظ أيضًا أنه يتم تطبيق تحويلات أحد العناصر من اليمين إلى اليسار، بمعنى أن جميع الترجمات قبل المصفوفة الخاصة لن تكون مقلوبة، فإن كل الترجمات بعد المصفوفة الخاصة ستكون كذلك! هيا نرى تنظيم هذا!

<script>
    // ... code from above...
    const factor =
    (scrollerHeight - thumbHeight)/(scroller.scrollHeight - scrollerHeight);
    thumb.style.transform = `
    matrix3d(
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, -1
    )
    scale(${1/factor})
    translateZ(${1 - 1/factor}px)
    translateZ(-2px)
    `;
</script>

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

ماذا عن iOS؟

مرحبًا بك يا صديقي القديم iOS Safari. وكما هو الحال مع تمرير اختلاف المنظر، المشكلة هنا. نظرًا لأننا نمرر على عنصر، نحتاج إلى تحديد -webkit-overflow-scrolling: touch، ولكن هذا يتسبب في حدوث تسطيح ثلاثي الأبعاد وكامل يتوقف تأثير التمرير عن العمل. لقد توصّلنا إلى حلّ هذه المشكلة في شريط تمرير اختلاف المنظر من خلال اكتشاف متصفّح Safari لنظام التشغيل iOS والاعتماد على position: sticky كحل بديل، سنفعل الشيء نفسه هنا بالضبط. يمكنك إلقاء نظرة على مقالة عن المقارنة لتنشيط ذاكرتك.

ماذا عن شريط التمرير في المتصفّح؟

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

Fin

من خلال وضع كل ذلك معًا، يمكننا الآن إنشاء صورة مخصصة مثالية شريط التمرير - مثل الشريط الموجود في عرض توضيحي لقطط ناين.

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

وهذا كل ما في الأمر. كان هذا الكثير من العمل. أحييك على قراءة مجمل قراءة شيء. هذا بعض خداعًا حقيقيًا للحصول على هذا الأمر وربما لا يستحق بذل هذا الجهد، إلا عندما يكون شريط التمرير المخصّص جزءًا أساسيًا من التجربة. لَكِنْ من الجيد معرفة إمكانية حدوث ذلك، أليس كذلك؟ حقيقة أنه من الصعب القيام شريط التمرير المخصص يوضح أن هناك عمل يجب إنجازه من جانب CSS. لكن لا تخف! في المستقبل، Houdini ستجري AnimationWorklet مثل هذه التأثيرات المرتبطة بالتمرير بسهولة أكبر،