إنّ أشرطة التمرير المخصّصة نادرة جدًا، ويعود ذلك إلى أنّه لا يمكن تطبيق أنماط على أشرطة التمرير بشكل كبير (أقصد أداة اختيار التاريخ). يمكنك استخدام JavaScript لإنشاء نموذج خاص بك، ولكنّ هذا الإجراء مكلف ودقيق قليلاً وقد يبطئ الأداء. في هذه المقالة، سنستفيد من بعض مصفوفات CSS غير التقليدية لإنشاء شريط تمرير مخصّص لا يتطلّب أي JavaScript أثناء التمرير، بل بعض رموز الإعداد فقط.
TL;DR
ألا تهتم بالتفاصيل الصغيرة؟ هل تريد فقط الاطّلاع على العرض الترويجي لـ Nyan cat والحصول على المكتبة؟ يمكنك العثور على رمز العرض الترويجي في مستودع GitHub.
LAM؛WRA (طويل ورياضي؛ تتم القراءة على أي حال)
منذ فترة، صمّمنا أداة لالتمرير المتباين (هل قرأت هذه المقالة؟ إنّه جيد جدًا ويستحق وقتك. من خلال دفع العناصر للخلف باستخدام عمليات التحوّل المجسمة في CSS، تم نقل العناصر بسرعة أقل من سرعة الانتقال الفعلية.
ملخّص
لنبدأ بملخص عن آلية عمل شريط التمرير المتغير الإيحاء.
كما هو موضّح في الصورة المتحركة، حقّقنا تأثير التماثل البصري من خلال دفع العناصر "إلى الخلف" في الفضاء الثلاثي الأبعاد، على طول محور Z. إنّ الانتقال للأعلى أو للأسفل في المستند هو عملية ترجمة على طول محور Y. إذا تحركنا للأسفل بمقدار 100 بكسل مثلاً، سيتم نقل كل عنصر للأعلى بمقدار 100 بكسل. وينطبق ذلك على جميع العناصر، حتى تلك التي تكون "أبعد". ولكن لأنّها تكون أبعد عن الكاميرا، سيكون الحركة التي تظهر على الشاشة أقل من 100 بكسل، ما يؤدي إلى تأثير المجسم المطلوب.
بالطبع، سيؤدي نقل عنصر إلى الخلف في المساحة إلى ظهوره أصغر حجمًا أيضًا، وسيتم تصحيح ذلك من خلال تكبير العنصر مرة أخرى. لقد توصّلنا إلى المعادلات الحسابية الدقيقة عند إنشاء أداة التمرير باستخدام تأثير المرئيات المتحركة، لذلك لن أكرّر كل التفاصيل.
الخطوة 0: ماذا نريد أن نفعل؟
أشرطة التمرير. وهذا ما سننشئه. لكن هل فكرت حقًا في ما يفعلونه؟ لم أفعل ذلك بالتأكيد. إنّ أشرطة التمرير هي مؤشر على مقدار المحتوى المتاح المعروض حاليًا ومقدار التقدّم الذي أحرزته بصفتك قارئًا. إذا تنقّلت للأسفل، سينتقل شريط التمرير للأسفل أيضًا لتحديد مستوى تقدّمك نحو النهاية. إذا كان كل المحتوى يلائم إطار العرض، يتم عادةً إخفاء شريط التمرير. إذا كان ارتفاع المحتوى ضعف ارتفاع إطار العرض، سيشغل شريط التمرير نصف ارتفاع إطار العرض. إذا كان المحتوى يمثّل 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 العناصر داخل حاوية المنظور على النحو التالي:
- حوِّل كل زاوية (نقطة) من العنصر إلى إحداثيات متجانسة
[x,y,z,w]
، بالنسبة إلى حاوية المنظور. - طبِّق جميع عمليات التحويل للعنصر كمصفوفات من اليمين إلى اليسار.
- إذا كان عنصر المنظور قابلاً للتقديم أو الإيقاف، طبِّق مصفوفة للتقديم أو الإيقاف.
- طبِّق مصفوفة المنظور.
مصفوفة التمرير هي عملية ترجمة على طول المحور ص. إذا مررنا لأسفل بمقدار 400 بكسل، يجب تحريك كل العناصر لأعلى بمقدار 400 بكسل. مصفوفة المنظور عبارة عن مصفوفة "تسحب" النقاط أقرب إلى نقطة التلاشي مرة أخرى في الفضاء الثلاثي الأبعاد. ويحقق ذلك تأثيرات جعل الأشياء تبدو أصغر عندما تكون بعيدًا ويجعلها "تتحرك بشكل أبطأ" عند الترجمة. وبالتالي، إذا تم دفع عنصر للخلف، سيؤدي التحويل بمقدار 400 بكسل إلى تحرّك العنصر بمقدار 300 بكسل فقط على الشاشة.
إذا أردت معرفة كل التفاصيل، يجب قراءة المواصفات المتعلقة بنموذج عرض التحويل في CSS، ولكن لأغراض هذه المقالة، بسّطتُ الخوارزمية المذكورة أعلاه.
يقع المربّع داخل حاوية منظورية ذات قيمة p لسمة perspective
، ولنفترض أنّ الحاوية قابلة للتقديم أو الإيقاف ويتم خفضها بقيمة
n بكسل.
المصفوفة الأولى هي مصفوفة المنظور، والمصفوفة الثانية هي مصفوفة التمرير. للتلخيص: وظيفة مصفوفة الانتقال هي جعل العنصر يتحرك للأعلى عندما نكون ننتقل للأسفل، وبالتالي العلامة السالبة.
في المقابل، بالنسبة إلى شريط التمرير، نريد المقابل، نريد أن يتحرك العنصر للأسفل عندما ننتقل إلى أسفل. في ما يلي حيلة يمكننا استخدامها:
قلب الإحداثي 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]
.
لقد أدرجت خطوة وسيطة لعرض تأثير مصفوفة تحويل العنصر. إذا لم تكن مرتاحًا لاستخدام الرياضيات في المصفوفات، لا بأس بذلك. يكمن سرّ الاكتشاف في أنّنا في السطر الأخير ننتهي من إضافة إزاحة التمرير n إلى إحداثي y بدلاً من طرحه. سيتم نقل العنصر للأسفل إذا انتقلنا للأسفل.
ومع ذلك، إذا وضعنا هذه المصفوفة في المثال، لن يتم عرض العنصر. ويعود السبب في ذلك إلى أنّ مواصفات CSS تتطلّب أن يمنع أي نقطة عندها w < 0 عرض العنصر. وبما أن إحداثي z تساوي حاليًا 0 وص 1 تساوي 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. يُرجى مراعاة
أنّه بسبب التلاعب في إحداثيات w، علينا ترجمة -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؟
مرحبًا، لقد تم تثبيت متصفح Safari على أجهزة iOS. كما هو الحال مع التمرير بزاوية متناوبة، نواجه
مشكلة هنا. بما أنّنا ننتقل للأعلى أو للأسفل في عنصر، يجب تحديد
-webkit-overflow-scrolling: touch
، ولكنّ ذلك يؤدي إلى تسطيح العناصر الثلاثية الأبعاد ويتوقف تأثير التمرير بالكامل. لقد حللنا هذه المشكلة في شريط تمرير اختلاف المنظر
من خلال اكتشاف iOS Safari والاعتماد على position: sticky
كحل بديل،
وسنفعل الشيء نفسه تمامًا هنا. يمكنك الاطّلاع على مقالة التماثل لتجديد ذاكرتك.
ماذا عن شريط التمرير في المتصفّح؟
في بعض الأنظمة، سيكون علينا التعامل مع شريط تمرير أصلي دائم.
في السابق، كان لا يمكن إخفاء شريط التمرير (إلا باستخدام
عنصر اختيار وهمي غير عادي).
لذا، لإخفائه، علينا اللجوء إلى بعض أساليب الاختراق (غير الحسابية). نلفّ
عنصر التمرير في حاوية باستخدام overflow-x: hidden
ونجعل
عنصر التمرير أوسع من الحاوية. أصبح شريط التمرير الأصلي للمتصفّح
غير مرئي الآن.
زعانف
بعد جمع كل هذه العناصر معًا، يمكننا الآن إنشاء شريط التمرير المخصّص الذي يناسب الإطار تمامًا، مثل الشريط المعروض في العرض الترويجي لتطبيق Nyan cat.
إذا لم تتمكّن من رؤية قطة Nyan، يعني ذلك أنّه حدث خطأ رصدناه وأبلغنا عنه أثناء إنشاء هذا العرض التجريبي (انقر على الصورة المصغّرة لظهور قطة Nyan). يجيد Chrome تجنُّب الأعمال غير الضرورية، مثل رسم أو إضافة حركة إلى العناصر التي لا تظهر على الشاشة. والخبر السيئ هو أن الأخطاء التي تحدث في المصفوفة تجعل Chrome يعتقد أن ملف GIF لقطط ناين (Nyan cat) يظهر بالفعل خارج الشاشة. نأمل أن يتم حلّ هذه المشكلة قريبًا.
هذا كل ما في الأمر. لقد كان هذا جهدًا كبيرًا. أُثني على قراءتك للملف بالكامل. هذه هي بعض الصعوبات التي تواجهك عند تنفيذ ذلك، ومن النادر أن يستحق الأمر الجهد المبذول، إلا إذا كان شريط التمرير المخصّص جزءًا أساسيًا من التجربة. من الجيد معرفة أنّ ذلك ممكن، أليس كذلك؟ حقيقة أنه من الصعب تنفيذ شريط تمرير مخصص تُظهر أنه هناك عمل يجب القيام به من جانب CSS. لكن لا تخف! وفي المستقبل، تسعى أداة AnimationWorklet من Houdini إلى تسهيل استخدام هذه التأثيرات الرائعة المرتبطة بالتمرير.