UIDex

المنزلق

Slider

<input type="range">

اسمه كمانسلايدر، الشريط المنزلق، شريط الانزلاق، منزلق المدى، مقبض القيمة، شريط تحديد القيمة، range input، range slider، volume slider، dual range slider، track and thumb، value slider

المنزلق عنصر إدخال يختار قيمة رقمية واحدة من مدى متّصل بين حدّ أدنى وحدّ أقصى، ويعبّر عن الاختيار بموضع مقبض على مسار. القيمة تتغيّر أثناء السحب لا بعده، وهذا ما يجعله مناسباً لكل شيء يُرى أثره في اللحظة: مستوى الصوت، حجم الخطّ، سطوع الشاشة، سقف السعر في فلتر البحث. وهو ليس شريط تقدّم (progress bar)، فشريط التقدّم مخرَج لا مدخَل، يبلّغ عن حالة عملية جارية ويحمل role="progressbar" ولا يستجيب لسحب أصلاً. وهو ليس شريط التمرير (scrollbar)، فشريط التمرير يحرّك نافذة عرض فوق محتوى أطول منها، وطول مقبضه نفسه معلومة تقول لك كم ترى من المحتوى، بينما مقبض المنزلق ثابت الحجم لأن موضعه وحده هو الذي يحمل المعنى. ولا هو الحقل الرقمي ذو الأسهم (stepper)، فالحقل الرقمي يقبل الكتابة ويصلح للقيم الدقيقة التي يعرفها المستخدم مسبقاً، أمّا المنزلق فيقايض الدقّة بالسرعة. والقاعدة العملية في المراجعة: إذا كان المستخدم يعرف الرقم قبل أن يمدّ يده فأعطه حقلاً، وإذا كان يبحث عنه بالتجريب فأعطه منزلقاً.

لو قلت عليه…

«الزرار اللي بتحرّكه على شريط عشان تعلّي الصوت»«المسطرة اللي بتظبط بيها السعر في الفلتر»«حاجة بتزوّد وتقلّل بالسحب من غير ما تكتب رقم»«الشريط بتاع السطوع اللي في الإعدادات»«الحاجة اللي ليها مقبضين بتحدّد بيهم من كام لكام»

العيّنة الحيّة

تفاعل مع الديمو. كل الأجزاء حقيقية ومرقّمة.

الحدّ الأعلى للسعر
aria-valuenow="400" aria-valuemin="0" aria-valuemax="1000"وينطقها قارئ الشاشة «400 جنيه» لأن aria-valuetext يحمل الوحدة
RTLالمحور كلّه ينعكس: الصفر عند اليمين والامتلاء ينمو نحو اليسار

تشريح العنصر: كل جزء واسمه

مرّر على أي سطر ليتحدّد مكانه في الديمو فوق.

برومبت البناء

ابنِ منزلقاً لاختيار قيمة رقمية واحدة. ابدأ من <input type="range"> بـ min و max و step ما دام الشكل يسمح، لأنه يعطيك مجّاناً لوحة المفاتيح وشجرة الوصول وانعكاس المحور في RTL. وإن اضطررت إلى بنائه يدوياً فاجعل المقبض عنصراً يستقبل التركيز ويحمل role="slider": زرّاً حقيقياً، أو <div> مع tabindex="0"، ومعه aria-valuemin و aria-valuemax و aria-valuenow محدَّثة عند كل تغيير. أعطِ المنزلق اسماً مقروءاً من <label> أو aria-labelledby، وأضف aria-valuetext كلّما كانت القيمة وحدة أو عملة أو اسم يوم، لأن الرقم وحده لا يُفهم. اعرض القيمة نصّاً ثابتاً بجانب المسار بدل فقاعة لا تظهر إلا أثناء السحب، وضع aria-hidden على ذلك النصّ حتى لا يُقرأ الرقم مرّتين. اضمن منطقة لمس ٤٤×٤٤ بكسل حول المقبض بالحشو مع إبقاء القرص المرسوم صغيراً، واجعل الضغط على المسار ينقل المقبض إلى الموضع المضغوط. اضبط step على قيمة يمكن إصابتها فعلاً بعرض المسار المتاح، وتذكّر أنها تُحسب من min. وإن كان المنزلق بمقبضين فامنع تقاطعهما بحدّ أدنى من الخطوات بينهما ولا تدع القيمة الدنيا تتجاوز العليا. استخدم الخصائص المنطقية في كل مكان: inset-inline-start و inline-size و padding-inline بدل left و width و padding-left، وحرّك توسيط المقبض بـ translateX(calc(-50% * var(--dir))) مع ضبط ‎--dir: -1 داخل [dir="rtl"]. احسب السحب من الحافّة المنطقية بـ (rect.right - clientX) عند rtl بدل (clientX - rect.left)، واعكس معنى ArrowLeft و ArrowRight بنفس الإشارة مع إبقاء ArrowUp و ArrowDown و Home و End كما هي. وافصل التغيير الجاري أثناء السحب عن الالتزام النهائي عند الإفلات، فلا ترسل طلباً إلى الخادم مع كل بكسل.

سلوكه في الاتجاه من اليمين لليسار

الجزء ده حصري عندنا.

المحور كلّه ينعكس

ينعكس

المنزلق الأفقي محور مضمّن كامل، فينقلب بأكمله في العربية: الحدّ الأدنى يجلس عند اليمين، والحدّ الأقصى عند اليسار، والجزء الممتلئ ينمو نحو اليسار. و<input type="range"> يفعل هذا وحده لأنه يتبع قيمة direction المحسوبة على الحقل نفسه لا على الصفحة، ومن هنا تأتي المصيدة: أي غلاف يفرض dir="ltr" حول الحقل (وهي عادة شائعة مع الأرقام والحقول الرقمية) يترك منزلقاً بالاتجاه الإنجليزي داخل صفحة عربية. أمّا المنزلق المبنيّ يدوياً على left و width فلا يعرف الاتجاه أصلاً ويبقى بعد الانعكاس كما كان قبله.

تثبيت الامتلاء والمقبض

ينعكس

استبدل left: 0; width: 40% بـ inset-inline-start: 0; inline-size: 40% فينعكس الامتلاء من تلقاء نفسه لأن الحافّة التي رُسي عليها هي التي تبدّلت. أمّا تحريكه بـ transform: scaleX() فلا يمنحك هذه الراحة، لأن كلمات transform-origin كلّها فيزيائية (left و right و top و bottom و center) إلى جانب الأطوال والنِسَب ولا يوجد فيها مقابل منطقي واحد، فلا مفرّ من transform-origin: right داخل [dir="rtl"] أو من متغيّر اتجاه. وتوسيط المقبض هو المصيدة الأخيرة: inset-inline-start: 40% تجعل حافّته اليمنى في RTL على بُعد ٤٠٪ من يمين المسار، فتدفعه إزاحة translateX(-50%) المعتادة إلى الجهة الخطأ بنصف عرضه، والصواب translateX(calc(-50% * var(--dir))) مع ضبط ‎--dir: -1 داخل [dir="rtl"].

الأسهم تتبادل معناها

ينعكس

السهمان الأفقيان يتبادلان مع المحور، ففي العربية يزيد ArrowLeft القيمة ويُنقصها ArrowRight، والمنزلق الأصلي يفعل ذلك وحده لأنه يعكس الأسهم مع الرسم. وجدول لوحة المفاتيح في نمط المنزلق لدى WAI-ARIA APG مكتوب من زاوية اليسار إلى اليمين ولا يذكر هذا الانعكاس، ولهذا يظلّ الكود اليدوي من نوع switch (e.key) { case "ArrowRight": v + step } يُنسخ كما هو ويسلّم المستخدم العربي عكس ما ضغط عليه. وما عدا ذلك ثابت: ArrowUp يزيد و ArrowDown يُنقص في الاتجاهين، و Home و End يقفزان إلى الحدّ الأدنى والأقصى المنطقيين لا إلى يمين الشاشة ويسارها.

حساب السحب بالمؤشّر

ينعكس

أحداث المؤشّر لا تعرف الاتجاه إطلاقاً: clientX إحداثي فيزيائي، و getBoundingClientRect() تعيد left و right بالمعنى الفيزيائي مهما كانت قيمة dir. لذلك تقيس المعادلة المعتادة (e.clientX - rect.left) / rect.width من الحافّة اليسرى، وهي في العربية طرف الحدّ الأقصى، فتخرج القيمة مقلوبة ويقفز المقبض إلى الطرف البعيد أول لمسة. قِس من الحافّة المنطقية بـ (rect.right - e.clientX) / rect.width حين تكون getComputedStyle(track).direction === "rtl"، وضع touch-action: none على المسار حتى لا يبتلع تمرير الصفحة السحب على الموبايل.

الرقم نفسه لا ينقلب

لا ينعكس

المحور ينعكس والرقم لا. الأرقام تجري من اليسار إلى اليمين داخل النصّ العربي بحكم خوارزمية bidi، فلا تعِد ترتيب محارفها ولا تقلبها بـ scaleX(-1). والمشكلة الحقيقية في الرموز الملاصقة لها: قيمة سالبة مثل ‎-5 تُعرض «5-» داخل فقرة عربية لأن محرف الناقص يؤول إلى محايد فيأخذ اتجاه الفقرة، وتنزلق وحدة لاتينية مثل km إلى الجهة الخطأ من الرقم بالطريقة نفسها، فاعزل كل تسمية قيمة داخل <bdi> أو بـ unicode-bidi: isolate وولّد النصّ بـ Intl.NumberFormat حتى تأتي الفاصلة العشرية ورمز العملة بالشكل الذي تتوقّعه اللغة.

أسماؤه في الكود

كل سطر هو كلمة مكتبة واحدة عن نفس الشيء. خد السطر اللي بيكلّم مشروعك.

HTML<input type="range" min max step>العنصر الأصلي؛ لوحة المفاتيح وقيم ARIA وانعكاس المحور في RTL تأتي معه بلا كود.
ARIArole="slider" + aria-valuenowما تدين به للمستخدم عند إعادة البناء من <div>، ومعه valuemin و valuemax واسم مقروء.
Radix UI<Slider.Root value={[a, b]}>القيمة مصفوفة، وبها يغطّي المكوّن الواحد المقبض والمقبضين؛ minStepsBetweenThumbs يمنع التقاطع و onValueCommit يطلق مرّة عند الإفلات.
Material UI<Slider marks disableSwap />marks تقبل true فتوزّع العلامات حسب step، أو مصفوفة من value و label؛ و disableSwap يمنع المقبض المسحوب من تجاوز جاره.
Ant Design<Slider range tooltip={{ formatter }} />خاصّية واحدة، range، تحوّل المقبض الواحد إلى اثنين والقيمة إلى زوج، و tooltip كائن لا مجرّد علامة منطقية.
CSSinset-inline-start + inline-sizeالزوج الذي يجعل الامتلاء والمقبض ينعكسان بلا أي قاعدة [dir="rtl"].
SwiftUISlider(value: $v, in: 0...100, step: 1)أبل تعطيك مقبضاً واحداً فقط؛ منزلق المدى بمقبضين عنصر تبنيه بنفسك.

شوف كمان

آخر تحديث · 2026-08-19