تخطَّ إلى المحتوى

slider —— المنزلق

يحوّل المنزلق اختيار الأرقام إلى عملية مكانية: فالسحب أزيد مباشرة من الإدخال، ويرى المستخدم موضع القيمة ضمن المجال بنظرة واحدة. ويتولّى core/slider التحويل في اتجاهين بين «القيمة ↔ الموضع»، والالتقاط وفق الخطوة، والقيمتين الابتدائيتين في وضع النطاق (range)، إضافة إلى دلالة slider وفق APG.

  • تُنجزه الأطراف على هيئة LhSlider: مستوى الصوت، مجال الأسعار، ضبط درجة الشفافية.
  • المجال متواصل أو الخطوة متقطّعة بدرجة خشنة.
  • لا ينبغي استخدامه: لإدخال قيم دقيقة (استخدم input-number، فالمنزلق لا يستطيع التعبير عن قيمة غير مضاعفة للخطوة مثل «37»).
import {
snapToStep,
clampAndSnap,
getValueFromPosition,
getPositionFromValue,
getSliderClassNames,
getInitialValue,
buildMarksList,
getSliderThumbAria,
} from '@lh-ui/share/core/slider'
import type { LhSliderState, LhSliderMark } from '@lh-ui/share/core/slider'

عندما يكون step غير منتهٍ أو <= 0 يُعيد snapToStep القيمة كما هي بدلاً من إعادة NaN — فالقيمة step: 0 تجعل عملية باقي القسمة تنتج NaN، وهنا يُعامَل «الخطوة غير الصحيحة» على أنها «التقاط غير مفعَّل»، ويُترك كلاء الحدود لـ clampAndSnap. فصل المسؤوليات: الالتقاط والكلاء أمران مستقلان.

وإعادة getPositionFromValue للقيمة 0 عند max === min هي تجنُّب للقسمة على صفر: فعندما يتدهور المجال إلى نقطة واحدة، يكون المنزلق بطبيعة الحال عند نقطة البداية.

ويُعيد getInitialValue في وضع النطاق القيمتين [كلاء min، كلاء (min+max)/2] — أي يبدأ المنزلق الثاني عند النقطة الوسطى لا عند max، وذلك لاعتبار بصري (فالمنزلقان متلاصقان يبدوان وكأنهما علة).

الدالة التوقيع الإرجاع الوظيفة
snapToStep (value: number, min: number, step: number) => number قيمة ملتقطة محاذاة أقرب عقدة وفق step
clampAndSnap (value: number, min: number, max: number, step: number) => number قيمة موحَّدة التقاط ثم كلاء
getValueFromPosition (pct: number, min: number, max: number, step: number) => number قيمة عددية النسبة المئوية ← القيمة
getPositionFromValue (value: number, min: number, max: number) => number نسبة مئوية [0,1] القيمة ← الموضع
getSliderClassNames (state: LhSliderState) => string[] أسماء أصناف disabled/range/size
getInitialValue (state: LhSliderState) => number | [number, number] قيمة ابتدائية يُعيد قيمتين في وضع النطاق
buildMarksList (marks?: Record<number, string>) => LhSliderMark[] مصفوفة علامات ترتيب تصاعدي حسب value
getSliderThumbAria (options: { value, min, max, vertical?, disabled?, ariaValueText? }) => LhAriaAttrs حزمة سمات aria دلالة slider
export interface LhSliderMark {
label: string
value: number
}
export interface LhSliderState {
value: number | [number, number] | null
min: number
max: number
step: number
disabled: boolean
range: boolean // وضع النطاق (منزلقان)
showInput: boolean // إقران مع input-number
size: LhComponentSize
marks: Record<number, string> | undefined
}

يُخرِج getSliderThumbAria دلالة slider وفق APG. وفي وضع النطاق يجب استدعاؤها لكل منزلق على حدة:

getSliderThumbAria({ value: 30, min: 0, max: 100 })
// { role: 'slider', 'aria-valuenow': 30, 'aria-valuemin': 0, 'aria-valuemax': 100 }
getSliderThumbAria({ value: 60, min: 0, max: 100, vertical: true, disabled: true })
// { role: 'slider', 'aria-valuenow': 60, 'aria-valuemin': 0, 'aria-valuemax': 100,
// 'aria-orientation': 'vertical', 'aria-disabled': true }
  • يُخرِج vertical القيمة aria-orientation: 'vertical' (أما الأفقي فهو الاتجاه الافتراضي ولا يُصرَّح به).
  • ويُخرِج ariaValueText القيمة aria-valuetext — ويُستخدم عندما تحتاج القيمة إلى تعبير دلالي (مثل «30%» أو «بطيء»)، وله أولوية في الإعلان على valuenow.
  • تفاعل لوحة المفاتيح (طبقة UI): يزيد ←/→ أو ↓/↑ بمقدار step، وينتقل Home/End إلى min/max، ويتحرك PageUp/Down بخطوة أكبر.
import { getValueFromPosition, getPositionFromValue, snapToStep } from '@lh-ui/share/core/slider'
getValueFromPosition(0.5, 0, 100, 1) // 50
getValueFromPosition(0.333, 0, 100, 5) // 35 (يلتقط إلى مضاعفات 5)
getPositionFromValue(30, 0, 100) // 0.3
getPositionFromValue(150, 0, 100) // 1 (مكلَّاة)
getPositionFromValue(50, 50, 50) // 0 (تجنُّب القسمة على صفر عند max===min)
snapToStep(7, 0, 5) // 5
snapToStep(8, 0, 5) // 10
snapToStep(7, 0, 0) // 7 (خطوة غير صحيحة بلا التقاط)
buildMarksList({ 0: 'منخفض', 100: 'مرتفع' }) // [{ label: 'منخفض', value: 0 }, { label: 'مرتفع', value: 100 }]
<!-- Vue3 -->
<div :class="getSliderClassNames({ value, min, max, step, disabled, range, showInput, size, marks })">
<div class="lh-slider__track">
<div class="lh-slider__bar" :style="{ width: pct + '%' }" />
<div
class="lh-slider__button-wrapper"
:style="{ left: pct + '%' }"
v-bind="getSliderThumbAria({ value, min, max, disabled })"
@pointerdown="startDrag"
>
<div class="lh-slider__button" />
</div>
<template v-if="marks">
<div v-for="mark in buildMarksList(marks)" :key="mark.value" class="lh-slider__marks">
<span class="lh-slider__mark" :style="{ left: getPositionFromValue(mark.value, min, max) * 100 + '%' }">
{{ mark.label }}
</span>
</div>
</template>
</div>
</div>
// React: السحب بـ pointer events + getBoundingClientRect لحساب النسبة المئوية
// const pct = (clientX - rect.left) / rect.width
// onChange(getValueFromPosition(pct, min, max, step))
// uniapp: <slider> مكوّن مدمج (منزلق واحد)، يدعم min/max/step/show-value؛
// ووضع النطاق والعلامات يحتاجان إلى تخصيص بـ view، والسحب بـ @touchmove
اسم الصنف الدلالة
.lh-slider الصنف الأساسي
.lh-slider--{small|large} درجة الحجم (حجم المنزلق)
.lh-slider--disabled تعطيل
.lh-slider--range وضع النطاق (منزلقان + مقطع مُبرَز في الوسط)
.lh-slider__track المسار
.lh-slider__bar المجال المحدد (المقطع المُبرَز)
.lh-slider__button-wrapper / __button المقبض
.lh-slider__marks / __mark العلامات
.lh-slider__input / __input-text حقل الإدخال المُقترَن

لون المسار --lh-fill-color، والمقطع المحدد --lh-primary-color.

  • يُعيد getPositionFromValue عدداً عشرياً في [0,1]، وتُضاف طبقة UI الرمز %؛ فلا تظن أنه يُعيد نسبة مئوية.
  • محاذاة الالتقاط مع العلامات: يجب أن تكون قيمة marks مضاعفاً لـ step، وإلا وقعت العلامة بين قيمتي التقاط، فلا يستطيع المستخدم السحب إليها.
  • تقاطع المنزلقين في وضع النطاق: يمكن للمنزلقين أن يعبر كلٌّ منهما الآخر (وتمنعه طبقة UI عادةً، أو تُبدِّلهما تلقائياً)، ولم يُعالِج clampAndSnap هذه الدلالة.
  • الوضع العمودي: في الوضع vertical يحل ارتفاع المسار محل عرضه، فيتغيَّر مقام التحويل؛ أما getValueFromPosition في core فهو مستقل عن المحور (يستقبل نسبة مئوية)، وتحسب طبقة UI قيمة pct.
  • اقتران showInput: يجب أن تتطابق قيم min/max/step في input-number مع slider، وإذا لم تتطابق اختلف مجال القيم بينهما.
@lh-ui/share/core/slider → getValueFromPosition( )
返回值
(填好参数后点运行)

Copyright © 2024-2026 FengMo WenXue