affix تثبيت —— تخطيط
عندما تتصفّح صفحة، هناك عناصر يجب «تثبيتها»: شريط التنقّل العلوي، وشريط العمليات السفلي،
ودليل الحاشية الجانبي. عندما يجتاز عنصر إطار العرض، يحوّله affix إلى تحديد موضع ثابت (fixed)،
وعند عودته يُعيد وضعه الأصلي. والصعوبة ليست في عملية التبديل ذاتها، بل في لحظة التحديد
وعنصر نائب يمنع الانهيار——فالدالة الأساسية (core) تهتم فقط بالتحديد والأنماط، أما
الاستماع والتحويط فمن مسؤولية طبقة واجهة المستخدم.
متى يُستخدم
Section titled “متى يُستخدم”- لتطبيق
<LhAffix>في كل طرف: تثبيت شريط التنقّل في أعلى الصفحة، وتثبيت شريط عمليات النموذج في الأسفل، وتثبيت دليل حاشية الصفحة الطويلة ضمن إطار العرض. - السيناريوهات التي تتطلّب بقاء العنصر مرئيًا أثناء التمرير.
- لا ينبغي استخدامه: الصفحات القصيرة التي تكون عناصرها داخل إطار العرض أصلاً (لا حاجة
للتثبيت)؛ والسيناريوهات التي تحتاج إلى قناع ملء الشاشة (استخدم
fullscreenالخاص بـloading)؛ وتخطيطات الشريط الجانبي الثابت بالكامل (استخدامposition: stickyفي CSS أبسط مباشرة——قيمة affix تكمن في سلوكه الشرطي «التثبيت عند تجاوز العتبة فقط»).
طريقة الاستيراد
Section titled “طريقة الاستيراد”import { getAffixClassNames, isAffixFixed, getAffixFixedStyle,} from '@lh-ui/share/core/affix'import type { LhAffixState } from '@lh-ui/share/core/affix'نية التصميم
Section titled “نية التصميم”top و bottom وضعان متبادلان: عندما يكون offsetTop > 0 يُحكم على «هل تجاوز الحافة العليا
عتبة إطار العرض العليا»، وعندما يكون offsetBottom > 0 يُحكم على «هل تجاوزت الحافة السفلى عتبة
إطار العرض السفلى». لا يمكن للقيمتين أن تعملا معًا——يستخدم isAffixFixed عبارة
if (offsetBottom > 0) لإعطاء الأولوية لوضع الأسفل. دلاليًا لا يمكن تثبيت العنصر إلا على حافة واحدة.
تحديد الأسفل يجب أن يأخذ ارتفاع إطار العرض في الحسبان: rect.bottom > viewportHeight - offsetBottom
——يجب أن تتجاوز الحافة السفلى للعنصر النائب «إطار العرض السفلي ناقص العتبة» لكي يُعتبر مثبّتًا.
هذه العتبة ديناميكية (تتغيّر viewportHeight مع حجم النافذة)، لذا على طبقة واجهة المستخدم أن
تمرّر ارتفاع إطار العرض الحالي في كل استدعاء، ولا يجوز تخزينه مؤقتًا.
فصل دالة التحديد عن آلية الاستماع: تأخذ isAffixFixed كائنًا مستطيلًا { top, bottom } ولا
تهتم بمصدره——سواءً جاء من boundingClientRect الخاص بمدخلات IntersectionObserver في رد النداء،
أو من الاحتياطي getBoundingClientRect() في أحداث resize/scroll، فإن كلا المسارين يتشاركان نفس
منطق التحديد.
top: 'auto' / bottom: 'auto' في getAffixFixedStyle: عند التثبيت يُضبط المحور الآخر صراحةً
على auto، لمنع العنصر من حمل top و bottom معًا (في تحديد الموضع الثابت، وجود قيمتين معًا يمطّ
ارتفاع العنصر).
اسم صنف للحالة الثابتة بدلاً من تغيير الأنماط مباشرة: تقرّر SCSS تفاصيل التثبيت (z-index،
الظل، انتقال الحركة) لـ lh-affix--fixed، بينما تحسب getAffixFixedStyle الخاصة بـ core قيم
البكسل المحددة لـ top/bottom——أي فصل مفتاح تحديد الموضع عن مقدار الإزاحة.
جدول الدوال
Section titled “جدول الدوال”| الدالة | التوقيع | الناتج | الدور |
|---|---|---|---|
getAffixClassNames |
(state: LhAffixState) => string[] |
أسماء الأصناف | حالة fixed |
isAffixFixed |
(rect: { top, bottom }, offsetTop, offsetBottom, viewportHeight) => boolean |
هل هو مثبّت | تحديد تجاوز إطار العرض |
getAffixFixedStyle |
(offsetTop, offsetBottom) => Record<string, string> |
أنماط مضمّنة | إزاحة تحديد الموضع الثابت |
تعريفات الأنواع
Section titled “تعريفات الأنواع”export interface LhAffixState { fixed: boolean // هل العنصر في الحالة الثابتة حاليًا (يحرّك أسماء الأصناف)}إمكانية الوصول
Section titled “إمكانية الوصول”affix هو تحديد موضع بصري خالص، لا يغيّر دلالة المحتوى، وليس له سمات aria مخصصة. هناك ممارستان ذواتا صلة:
- لا تغيّر حالة التثبيت ترتيب DOM: يبقى العنصر في موقعه الأصلي في DOM حتى بعد تثبيته، ويقرأ قارئ الشاشة العناصر بترتيب المصدر. إذا نُقل بصريًا إلى نهاية body (teleport)، سيتقفز ترتيب الإعلان——يجب أن يغيّر تنفيذ affix الموضع فقط دون لمس DOM.
- رؤية العنصر المثبّت: العنصر المثبّت يكون داخل إطار العرض دائمًا، ولا يجب أن تتغيّر
قيمة
aria-hiddenمع التمرير (فهو مرئي طوال الوقت أصلاً). - خطر فخ التركيز: شريط عمليات «حفظ/إرسال» المثبّت في الأسفل مناسب في النماذج الطويلة، لكن
انتبه إلى أن العنصر الثابت قد يحجب محتوى أسفل الصفحة (الزر يغطي آخر سطر دائمًا)——يجب على
طبقة واجهة المستخدم إضافة
padding-bottomإلى body أو حاوية المحتوى لترك مساحة، وهذا أمر تخطيط لا أمر إمكانية وصول، لكنه يؤثر في قابلية الاستخدام.
أمثلة الاستخدام
Section titled “أمثلة الاستخدام”import { isAffixFixed, getAffixFixedStyle } from '@lh-ui/share/core/affix'
// الوضع العلوي: الحافة العليا تجاوزت العتبة 80pxisAffixFixed({ top: 50, bottom: 200 }, 80, 0, 900) // false (50 < 80 شرط التثبيت، 50 لم تتجاوز)isAffixFixed({ top: 100, bottom: 300 }, 80, 0, 900) // true (100 > 80)// انتبه لاتجاه التحديد: top < offsetTop يكون صحيحًا —— أي أن الحافة العليا للعنصر **أعلى** من// خط العتبة (تم لفّه للأعلى)
// الوضع السفلي: الحافة السفلى تجاوزت «إطار العرض السفلي ناقص العتبة»isAffixFixed({ top: 700, bottom: 950 }, 0, 60, 900) // true (950 > 900-60=840)isAffixFixed({ top: 500, bottom: 700 }, 0, 60, 900) // false (700 < 840، لم تتجاوز بعد)
// تمرير قيمتي offset معًا: الأولوية للأسفلisAffixFixed({ top: 100, bottom: 950 }, 80, 60, 900) // true (يأخذ الفرع السفلي)getAffixFixedStyle(80, 0) // { position: 'fixed', top: '80px', bottom: 'auto' }getAffixFixedStyle(0, 60) // { position: 'fixed', bottom: '60px', top: 'auto' }getAffixFixedStyle(0, 0) // { position: 'fixed', top: '0px', bottom: 'auto' } (تثبيت علوي افتراضي)// طبقة واجهة المستخدم: المسار الرئيسي IntersectionObserver + عنصر نائب يمنع الانهيارconst placeholder = document.createElement('div') // عند التثبيت يملأ الموضع الأصلي لمنع قفز المحتوىlet observer: IntersectionObserver | undefined
function observe() { observer = new IntersectionObserver( (entries) => { const rect = entries[0].boundingClientRect const fixed = isAffixFixed( { top: rect.top, bottom: rect.bottom }, props.offsetTop ?? 0, props.offsetBottom ?? 0, window.innerHeight, ) if (fixed !== state.fixed) { state.fixed = fixed if (fixed) targetEl.before(placeholder) // عنصر نائب else placeholder.remove() // إزالة العنصر النائب } }, { threshold: [0, 1] }, ) observer.observe(targetEl)}<!-- Vue3 --><template> <LhAffix :offset-top="64"> <nav class="page-nav">…</nav> </LhAffix></template>// React: useEffect يُسجِّل IntersectionObserver؛ ويُتجاوز في SSR (لا يوجد IntersectionObserver)،// ويُركَّب بعد التنشيط من جهة العميل// uniapp: IntersectionObserver في جانب التطبيقات المصغّرة هو wx.createIntersectionObserver؛// وفي جانب h5 يُستخدم واجهة المتصفح الأصلية. دالة التحديد في core مشتركة بين الجانبينالأنماط والرموز
Section titled “الأنماط والرموز”| اسم الصنف | الدلالة |
|---|---|
.lh-affix |
الحاوية المغلِّفة (في الموضع الأصلي) |
.lh-affix--fixed |
الحالة الثابتة (position: fixed + z-index + ظل/انتقال اختياري) |
z-index --lh-z-index-affix؛ ظل الحالة الثابتة --lh-box-shadow-affix (اختياري، شريط ظل رفيع في
الأسفل عند التثبيت العلوي لتمييز مستوى الطبقة).
ملاحظات
Section titled “ملاحظات”- العنصر النائب ضروري: بمجرد تثبيت العنصر يخرج من تدفق المستند، ويتقلّص المحتوى الذي بعده ليملأ الفراغ——قفز عنيف في الصفحة. يجب على طبقة واجهة المستخدم وضع عنصر نائب بنفس الحجم (كما في المثال أعلاه). يجب أن يساوي ارتفاع العنصر النائب ارتفاع العنصر قبل التثبيت.
- ارتفاع إطار العرض في وضع الأسفل يجب أن يكون لحظيًا: يجب تمرير القيمة الحالية في
معامل
viewportHeightالخاص بـisAffixFixedعند كل استدعاء، وإعادة التحديد عند resize النافذة (قد تنتقل حالة affix من الثابت إلى غير الثابت). offsetTop/offsetBottomاختر واحدًا منهما: عند تمريرهما معًا تكون الأولوية للأسفل وتُلغى عتبة الأعلى. ورغم أن الـ API يسمح بتمريرهما معًا، إلا أن الدلالة الصحيحة هي تكوين واحد فقط.- انهيار العرض في الحالة الثابتة: بعد التثبيت لم يعد عرض العنصر مقيّدًا بالحاوية الأب
(العناصر الكتلية المثبّة تنكمش إلى عرض المحتوى). عادةً ما تسجّل SCSS العرض الأصلي في
--fixedوتُعيد استخدامه، أو تكتب طبقة واجهة المستخدم العرض في الأنماط المضمّنة. - حاويات التمرير المتداخلة: عندما تحتوي الصفحة على حاوية ذات تمرير محلي (وليس تمرير
window)، يجب تعيين root الخاص بـ IntersectionObserver على تلك الحاوية——كما يجب استبدال
معامل ارتفاع إطار العرض في
isAffixFixedبـ clientHeight الخاص بالحاوية. لا يفرّق core بين الحالتين، وعلى المُستدعي ضمان الاتساق. - توافق IntersectionObserver: جميع الأطراف المستهدفة تدعمه؛ ولتوافق البيئات القديمة جدًا
استخدم أحداث scroll +
getBoundingClientRect()كاحتياطي (يمكن إعادة استخدام دالة التحديد مباشرة لأنها تأخذ كائنًا مستطيلًا فقط). - الاختيار بين affix و sticky:
position: stickyفي CSS يغطي معظم احتياجات «التثبيت العلوي» وبصفر JavaScript. القيمة المضافة لـ affix هي العتبة القابلة للتحكّم (التثبيت عند تجاوز 64px وليس عند ملامسة الحافة) ووضع الأسفل (sticky يلتصق بالحافة القريبة فقط من اتجاه التمرير). في السيناريوهات البسيطة لا تلجأ إلى affix.
@lh-ui/share/core/affix → isAffixFixed( )(填好参数后点运行)Copyright © 2024-2026 FengMo WenXue