English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

تحكم في تأثير السحب المتزامن لعدة عمود التمرير باستخدام JavaScript

في بعض المواقع التي تدعم كتابة المقالات باستخدام markdown، في صفحات التحرير الخاصة بالخلفية، عادة ما يدعمها عرض markdown في الوقت الحقيقي، أي تقسيم الصفحة إلى جزأين، الجزء الأيسر هو النص المدخل markdown، والجزء الأيمن هو عرض الصفحة في الوقت الحقيقي، على سبيل المثال، هو تأثير عرض markdown في الوقت الحقيقي لصفحة تحرير خلفية CSDN:

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

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

هيكل DOM

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

بالنسبة لهذا بنية DOM:}

<div id="container">
 <div class="left"></div>
 <div class="right"></div>
</div>

حيث، عناصر .left هي عناصر حاوية الإدخال الجزء الأيسر، عناصر .right هي عناصر حاوية العرض الجزء الأيمن، .container هي العنصر الأب المشترك لهما.

بما أن هناك حاجة إلى التمرير المتداخل، لذا يجب أيضًا تعيين بعض الأسلوب المميز (فقط الأسلوب الأساسي، وليس كل الأسلوب):

#container {
 display: flex;
 border: 1px solid #bbb;
}
.left, .right {
 flex: 1;
 height: 100%;
 word-wrap: break-word;
 overflow-y: scroll;
}

بإضافة محتوى كافٍ إلى عناصر .left و .right، يمكن أن يظهر سطر التمرير في كلاهما، مثل هذا النوع من التأثير:

التصميم الأساسي قد تم إتمامه، لذا يمكن القيام بجميع العمليات على هذه DOM.

المحاولة الأولى

الفكرة الأساسية، مراقبة حدث التمرير لعنصرين من عناصر الحاوية، عند تمرير أحد العناصر، الحصول على قيمة scrollTop الخاص به، ثم تعيين هذه القيمة كقيمة scrollTop للعنصر الآخر.

مثال:

var l=document.querySelector('.left')
var r=document.querySelector('.right')
l.addEventListener('scroll',function(){
 r.scrollTop = l.scrollTop
})

النتيجة تبدو كالتالي:

يبدو أن هذا جيد جدًا، ولكن الآن لا أريد فقط أن يتبع الجانب الأيمن الجانب الأيسر عند التمرير، بل أيضًا أريد أن يتبع الجانب الأيسر الجانب الأيمن، لذا أضف التالي:

addEventListener('scroll',function(){
 r.scrollTop = l.scrollTop
})

يبدو أن كل شيء جيد، ولكن لا يوجد شيء بسيط كما يبدو.

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

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

حل مشكلة تنشيط حدث التمرير في نفس الوقت

لحل هذه المشكلة، في الوقت الحالي هناك طريقتان.

استبدال أحداث scroll بأحداث mousewheel

بما أن أحداث scroll يمكن أن تُطلق ليس فقط بسبب تدوير الفأرة، بل أيضًا تغيير scrollTop لعنصر الحاوية، فإن تدوير العنصر يكون في الواقع بسبب تدوير عجلة الفأرة، لذا يمكن استبدال أحداث scroll بأحداث 'mousewheel' التي تكون حساسة للتدوير وليس للعنصر، لذا أصبح الكود المسمع الآن:

addEventListener('mousewheel',function(){
  r.scrollTop = l.scrollTop
})
r.addEventListener('mousewheel',function(){
  l.scrollTop = r.scrollTop
})

النتيجة تبدو كالتالي:

يبدو أن هناك بعض الفوائد، ولكن هناك أيضًا مشكلتان.

عند تدوير عنصر الحاوية الأول، فإن الحاوية الثانية تتبعها ولكن ليس بشكل سلس، ويكون الارتفاع لهذه الحاوية له انفجارات واضحة.

بعد البحث في الإنترنت، لم أجد أي معلومات عن تردد أحداث wheel، أعتقد أن هذا قد يكون ميزة لهذا الحدث.

عند تدوير الفأرة، لا يكون التدوير دائمًا باستخدام وحدة 1px، وحدة الأدنى هي أكبر بكثير من أحداث scroll، استخدمت فأرتي في متصفح Chrome تدور على بعد 100px في كل مرة، ولكن يمكن أن تكون هذه القيمة مختلفة للفأرة أو المتصفح المختلفين، وأحداث wheel في الواقع هي مراقبة أحداث تدوير عجلة الفأرة عبر نقطة التثبيت للزنبرك، وهذا يمكن أن يفسر لماذا يحدث الارتجاف.

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

وإذا لم يكن هناك انفجار فوري في عنصر التتبع عند تدوير scroll المذكور أعلاه، فإن السبب في ذلك هو أن قيمة scrollTop لكل عنصر التتبع لا تتغير بنطاق 100px، ولا تكون صغيرة حتى 1px، ولكن نظرًا لعدة التشغيل عالية وال跨度 من التدوير صغير، على الأقل من الناحية البصرية، فإن التدوير يكون سلسًا.

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

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

مشاكل التوافق لحدث wheel

حدث wheel هو معيار DOM Level3، ولكن بالإضافة إلى هذا الحدث، هناك العديد من الحوادث غير المعايير، تختلف المعايير بين نوايا المتصفحات المختلفة، لذا قد تحتاج إلى التوافق حسب الحالة، يمكن الرجوع إلى MDN MouseWheelEvent للحصول على التفاصيل.

تحديد وقتي

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

مشكلة حدث scroll تكمن في عدم تحديد العنصر المحرك للتمرير، بمجرد تحديد العنصر المحرك للتمرير، فإن الأمر يصبح سهلاً، مثل استخدام حدث wheel المذكور أعلاه، يمكن استخدام حدث scroll لأنه يمكن تحديد بسهولة العنصر المحرك للتمرير.

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

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

يمكن الحصول على إحداثيات الفأرة الحالية عند تحرك الفأرة على الشاشة.

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

هذا مثال على رمز تحرك الفأرة إلى عنصر .left:

if (e.clientX>l.left && e.clientX<l.right && e.clientY>l.top) {
  // الدخول إلى عنصر .left
}

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

addEventListener('mouseover',function(){
 // الدخول إلى عنصر التمرير .left
})

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

حسنًا، التأثير جيد جدًا، وكذلك الأداء، perfect، يمكننا ان ننتهي.

التمرير بنسب

كل الأمثلة المذكورة أعلاه هي في حالة حيث تكون محتويات العناصر الفرعية للتمرير متطابقة تمامًا، ماذا إذا كانت محتويات العناصر الفرعية للتمرير مختلفة؟

وهذا هو التأثير التالي:

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

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

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

على الرغم من أن ارتفاع محتوى وحدة التمرير لا يمكن تحديده بشكل دقيق، ولكن هناك شيء يمكن تحديده، وهو ارتفاع التمرير الأقصى، أو قيمة scrollTop، وهو بالتأكيد له علاقة مع ارتفاع محتوى وحدة التمرير وارتفاع وحدة التمرير نفسها.

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

<div id="container">
 <div class="left">
	 <div class="child"></div>
 </div>
 <div class="right">
	 <div class="child"></div>
 </div>
</div>

مثال على التركيب كالتالي:

من خلال مراقبتي واستنتاجي وتحققي من خلال التجربة، تم تحديد علاقتهم، وهي بسيطة للغاية، وهي عملية الجمع والطرح الأساسية:

ارتفاع التمرير الأقصى (scrollTopMax) = ارتفاع محتوى وحدة التمرير (أي ارتفاع عنصر الفرع ch) - ارتفاع وحدة التمرير نفسها (أي ارتفاع عنصر العنصر ph)

بمعنى آخر

ذلك يعني، إذا تم تحديد ارتفاع محتوى وحدة التمرير (أي ارتفاع عنصر الفرع ch) وارتفاع وحدة التمرير نفسها (أي ارتفاع عنصر العنصر ph)، فإنه يمكن تحديد ارتفاع التمرير الأقصى (scrollTop) بشكل مؤكد، ويمكن الحصول على قيمتين لهاتين الارتفاعات بشكل أساسي، لذا يمكن الحصول على scrollTop

لذلك، إذا كنت تريد أن يتحرك العنصران المتدفقان العكسيان بشكل متناسب في اتجاه السحب، أي أن يصل العنصر إلى أعلى أو أسفل، فإن العنصر الآخر يجب أن يصل أيضًا إلى أعلى أو أسفل، يجب الحصول على نسبة (scale) القصوى لـ scrollTop بين العنصرين المتدفقين العكسيين.

بعد تحديد scale، عند التدفق العكسي في الوقت الحقيقي، يمكن الحصول على scrollTop1 للعنصر المتدفق العكسي النشط للحصول على scrollTop2 للعنصر المتدفق العكسي التالي:

بمجرد توضيح الفكر، يصبح كتابة الكود أمرًا سهلاً، النتيجة تبدو كالتالي:

يبدو هذا جيدًا~

الخاتمة

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

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

الخاتمة

ما ذكرته أعلاه هو مبدأ التحكم في تأثيرات التدفق العكسي للمعدلات المتنوعة، آمل أن يكون هذا مفيداً لك، إذا كان لديك أي أسئلة، فلا تتردد في ترك تعليق، وسأقوم بالرد عليك في أقرب وقت ممكن. وأود أن أعبر عن امتناني الشديد لدعمكم لتعليم呐喊!

بيان: محتوى هذا المقال تم جمعه من الإنترنت، حقوق الطبع والنشر مملوكة للمالك الأصلي، تم جمع المحتوى من قبل المستخدمين عبر الإنترنت وتم تحميله بشكل تلقائي، هذا الموقع لا يمتلك حقوق الملكية، لم يتم تعديل المحتوى بشكل يدوي ولا يتحمل أي مسؤولية قانونية متعلقة بذلك. إذا كنت قد وجدت محتوى يشتبه في انتهاك حقوق الطبع والنشر، فنرجو منك إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (عند إرسال البريد الإلكتروني، يرجى استبدال '#' ب '@') لإبلاغنا، وقدم الدليل على ذلك، إذا تم التحقق من ذلك، سيتم حذف المحتوى المزعوم فوراً.

سيحبك