UIDex

هيكل التحميل

Skeleton

aria-busy="true"

اسمه كمانسكيليتون، الهيكل التحميلي، شاشة الهيكل، العناصر النائبة، هيكل المحتوى، شيمر، skeleton screen، skeleton loader، content placeholder، shimmer، ghost loader

هيكل التحميل نسخة رمادية مبسّطة من الواجهة، تُرسم في أماكن المحتوى الحقيقي وبمقاساته أثناء جلبه. وظيفته أن يخبرك بشكل الصفحة قبل وصول البيانات، فيقصر الإحساس بالانتظار ويمنع القفز البصري لحظة وصولها. والفرق بينه وبين المؤشّر الدوّار (spinner) أن الدوّار يقول «انتظر» ولا يقول شيئاً عن التخطيط، أمّا الهيكل فيقول «هذا ما سيأتي، وهنا سيأتي». وشريط التقدّم يعرض نسبة معلومة من عمل مُقاس، والهيكل لا يعرف كم بقي أصلاً. وحالة الفراغ نتيجة نهائية معناها «لا توجد بيانات»، بينما الهيكل وعد مؤقّت معناه «البيانات في الطريق». وهيكل لا ينتهي أسوأ من رسالة خطأ صريحة. والقاعدة الحاكمة: إن لم يطابق الهيكل شكل المحتوى ومقاسه، قفز التخطيط عند وصول البيانات، وتكون قد صنعت المشكلة التي كنت تحاول إخفاءها.

لو قلت عليه…

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

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

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

حدّ أدنى 600msجارٍ التحميل…
RTLاللمعة تمشي مع اتجاه القراءة، جرّب تبديل اللغة من الأعلى

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

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

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

ابنِ هيكل تحميل (skeleton) لبطاقة ملفّ شخصي. لُفّ المنطقة الجاري تحميلها بعنصر يحمل aria-busy="true" وأزِل السمة فور وصول البيانات. اجعل كل كتلة هيكلية aria-hidden="true" وبنفس صندوق العنصر الحقيقي تماماً: نفس الارتفاع ونفس نصف القطر ونفس الحشو، واكتب الحشو والفجوات بخصائص منطقية (padding-inline و margin-inline و gap) في الهيكل والمكوّن الحقيقي معاً حتى لا يقفز التخطيط في RTL وحده. نفّذ أسطر النص كأشرطة داخل خانات ارتفاعها يساوي line-height، وآخر شريط بعرض ٥٥٪ بلا هوامش يدوية. مرّر لمعة واحدة فوق المنطقة كلها بدل حركة لكل كتلة، وحرّكها بـ translateX(calc(100% * var(--dir))) مع ‎--dir: 1 على الجذر و‎-1 داخل [dir="rtl"]، واحسب زاوية التدرّج من نفس المتغيّر، وألغِ الحركة بالكامل تحت prefers-reduced-motion: reduce. أضف سطر حالة واحداً للمنطقة كلها بـ role="status" يقول «جارٍ التحميل» ثم «اكتمل»، ولا تكرّره داخل كل كتلة. وأخيراً أخّر ظهور الهيكل ٢٠٠ مللي ثانية، وإن ظهر فأبقِه ٦٠٠ مللي ثانية على الأقل، واعرض حالة خطأ صريحة لا هيكلاً أبدياً إذا فشل الطلب.

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

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

مسار اللمعة

ينعكس

اللمعة تسير في اتجاه القراءة: من اليسار إلى اليمين في الإنجليزية، ومن اليمين إلى اليسار في العربية. المشكلة أن transform خاصية فيزيائية لا تنقلب مع dir أبداً، فـ translateX(-100%) ثابت يجعل اللمعة تمشي عكس القراءة في الصفحة العربية. اربطها بمتغيّر: ‎--dir: 1 على الجذر و‎--dir: -1 داخل [dir="rtl"]، ثم translateX(calc(100% * var(--dir)))؛ ولو كانت الحركة خطّية بسيطة كفاك animation-direction: reverse داخل [dir="rtl"].

ميل التدرّج

ينعكس

زاوية linear-gradient فيزيائية هي الأخرى: لا توجد في CSS كلمة منطقية مثل to inline-end للتدرّجات حتى اليوم. لمعة بزاوية 110deg ستميل في الاتجاه الخاطئ بعد الانعكاس، فاحسبها من نفس متغيّر الاتجاه: linear-gradient(calc(90deg + 20deg * var(--dir)), …). والقاعدة نفسها تنطبق على background-position إن كنت تحرّك الخلفية بدل العنصر.

هندسة الكتل نفسها

لا ينعكس

لا تصلح اتجاه اللمعة بعكس الهيكل كله عبر transform: scaleX(-1). الكتل ليست عناصر اتجاهية: العكس يقلب زوايا border-radius غير المتماثلة، ويعكس أي أيقونة نائبة بداخلها، ويغيّر اتجاه أي clip-path أو mask-image مطبّق عليها. اقصر الانعكاس على الحركة وحدها (أي على قيمة ‎--dir في الـ keyframes) واترك الصناديق كما هي. ولو احتجت أنصاف أقطار غير متماثلة فاكتبها بالخصائص المنطقية border-start-start-radius و border-start-end-radius لتنعكس وحدها.

السطر الأخير الأقصر

ينعكس

الشريط الأخير في الفقرة يبدأ من البداية المنطقية وينقص عند النهاية المنطقية: في العربية يبدأ من اليمين ويتوقّف قبل الحافّة اليسرى. صندوق كتلي بعرض 55% وهوامش صفرية يفعل ذلك وحده، لكن margin-right: auto سيثبّته في الجهة الخطأ، فاستخدم margin-inline-end: auto. ولو أضفت تلاشياً بـ mask-image: linear-gradient(to right, …) فهو فيزيائي ويحتاج قلباً صريحاً في RTL.

حشو الهيكل يطابق حشو المكوّن

ينعكس

الهيكل والمكوّن الحقيقي يجب أن يتقاسما نفس قيم padding و gap بالضبط. لو كتبت في البطاقة الحقيقية padding-inline-start وفي هيكلها padding-left، تطابقا في الإنجليزية واختلفا في العربية، فيقفز التخطيط عند وصول البيانات في اللغة العربية وحدها. وهذا أسوأ أنواع العيوب لأنه لا يظهر في اختبارك.

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

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

ARIAaria-busy="true"يعلّم المنطقة كمشغولة فتؤجّل التقنية المساعدة إعلان ما يتغيّر داخلها.
ARIArole="status" + aria-hidden="true"إعلان واحد للمنطقة كلها، وإخفاء كل كتلة هيكلية عن شجرة إمكانية الوصول.
Material UI<Skeleton variant="circular" animation="wave" />variant يضبط الشكل، و animation="wave" هي اللمعة و false توقفها تماماً.
Ant Design<Skeleton active paragraph={{ rows: 3 }} />active يفعّل اللمعة، و paragraph.rows يحدّد عدد أسطر النصّ الهيكلية.
shadcn/ui<Skeleton className="h-4 w-[220px]" />مجرّد div بخلفية ونبض؛ مطابقة المقاس مسؤوليتك أنت، وهي موضع الخطأ الأشهر.
SwiftUI.redacted(reason: .placeholder)المكافئ على أبل: يحجب المحتوى الحقيقي نفسه، فتأتي المطابقة في المقاس مجّاناً.
CSS@media (prefers-reduced-motion: reduce)الاستعلام الذي يجب أن يلغي اللمعة بالكامل، لا أن يكتفي بإبطائها.

شوف كمان

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