زرّ الإجراء العائم
Floating Action ButtonFloatingActionButton
اسمه كمانالزرّ العائم، فاب، زرّ الإضافة العائم، الزرّ الدائري العائم، زرّ الإجراء الأساسي، FAB، floating button، extended FAB، mini FAB، speed dial button، compose button
زرّ الإجراء العائم قرص يطفو فوق محتوى الشاشة في طبقة أعلى منه، ويبقى في مكانه بينما يمرّ المحتوى تحته، ويحمل الفعل الوحيد الأرجح أن يريده المستخدم في هذه الشاشة. وهو يختلف عن الزرّ العادي في أنه خارج تدفّق التخطيط: الزرّ العادي يجلس داخل الصفحة فيدفعه المحتوى ويرحل معه عند التمرير، أمّا هذا فمثبّت في الركن ويغطّي ما يمرّ تحته. ويختلف عن أزرار شريط التطبيق أو شريط الأدوات، لأن تلك أفعال متعدّدة متساوية في الوزن تخصّ ما هو معروض الآن (فرز، بحث، مشاركة)، بينما العائم فعل واحد رُفع فوق كل ما حوله. ويختلف عن القائمة الشعاعية (speed dial)، وهي العنقود الذي يتفرّع منه ثلاثة أو أربعة أزرار مصغّرة عند الضغط: تلك حالة موسّعة من العنصر نفسه لا عنصر مستقلّ، واللجوء إليها غالباً اعتراف بأنك لم تستطع اختيار فعل واحد. وله قاعدة تحسم أغلب النقاش في المراجعة: زرّ عائم واحد في الشاشة يحمل فعلاً ينشئ أو يضيف. أمّا الحذف وكل فعل لا رجعة فيه، ومعهما الأفعال الثانوية، فلا مكان لها في موضع يَعِد بحجمه وارتفاعه بأنه أهمّ ما في الشاشة. وإن لم يكن في الشاشة فعل يستحقّ هذا الوعد فالصواب ألّا يكون فيها زرّ عائم أصلاً.
لو قلت عليه…
العيّنة الحيّة
تفاعل مع الديمو. كل الأجزاء حقيقية ومرقّمة.
الرسائل
تشريح العنصر: كل جزء واسمه
مرّر على أي سطر ليتحدّد مكانه في الديمو فوق.
برومبت البناء
سلوكه في الاتجاه من اليمين لليسار
الجزء ده حصري عندنا.
الركن ينتقل إلى الجهة المقابلة
ينعكسالزرّ المثبّت في الركن السفلي الأيمن في الإنجليزية يجلس في الركن السفلي الأيسر في العربية، لأن مرساته نهاية سطر القراءة لا جهة بعينها. على الويب اكتب inset-inline-end مع inset-block-end بدل right و bottom. وفي Compose استعمل Modifier.align(Alignment.BottomEnd) أو FabPosition.End داخل Scaffold، وابتعد عن AbsoluteAlignment.BottomRight فهي تتجاهل LayoutDirection عن قصد. وفي تخطيطات أندرويد التقليدية القيمة android:layout_gravity="bottom|end" لا "bottom|right"، ولن ينعكس شيء منها على الجهاز ما لم تكن android:supportsRtl="true" موضوعة على وسم <application>. وعلى iOS قيّد الزرّ بـ trailingAnchor لا rightAnchor، أو ضعه بـ .overlay(alignment: .bottomTrailing) في SwiftUI.
رمز الزرّ لا ينعكس
لا ينعكسعلامة الزائد والكاميرا والقلم والميكروفون أشكال ثابتة لا اتجاه في معناها، وقلبها في العربية يجعلها تبدو معطوبة لا مترجمة. الذي ينعكس هو الأيقونة الاتجاهية وحدها: سهم الرجوع، سهم الإرسال، علامة «التالي». لذلك لا تكتب قاعدة شاملة مثل [dir="rtl"] svg { transform: scaleX(-1) }، بل اقصر القلب على صنف تضعه على الأسهم. والمنصّات تحمل هذه المعلومة بنفسها: android:autoMirrored="true" على الـ vector drawable، و Icons.AutoMirrored.Filled.Send في Compose، و Image.flipsForRightToLeftLayoutDirection(true) في SwiftUI، وفي SF Symbols اختر arrow.backward التي تنعكس بدل arrow.left التي لا تنعكس أبداً. أمّا الزائد الذي يدور ٤٥ درجة ليصير علامة إغلاق فلا يحتاج شيئاً من هذا، لأن الدوران محايد تجاه الاتجاه.
الأيقونة عند بداية الكبسولة الممتدّة
ينعكسداخل الكبسولة تقع الأيقونة عند البداية المنطقية والنصّ بعدها: يمين النصّ في العربية، يساره في الإنجليزية. اجعل الكبسولة صفّ flex واترك ترتيب DOM كما هو، فالمحور المضمّن ينعكس وحده. ولا تضف flex-direction: row-reverse داخل [dir="rtl"]، لأنك تقلب ما انقلب فتعود الأيقونة إلى الجهة الخطأ وينفصل الترتيب البصري عن ترتيب القراءة وعن ترتيب التنقّل. والمسافة بين الأيقونة والنصّ تُكتب gap أو margin-inline-end لا margin-right. وخانة icon في ExtendedFloatingActionButton داخل Compose تضعها عند البداية تلقائياً، و Label في SwiftUI يفعل الشيء نفسه. أمّا حشو الكبسولة غير المتماثل (أضيق عند الأيقونة وأوسع عند النصّ) فمكانه padding-inline-start و padding-inline-end.
محور العنقود لا ينقلب
لا ينعكسالعنقود يفتح إلى الأعلى في الاتجاهين معاً، لأنه يترتّب على المحور الكتلي، و direction لا تمسّ هذا المحور أصلاً (الذي يقلبه writing-mode وحده). فحاوية flex-direction: column لا تحتاج column-reverse في العربية، ويبقى ترتيب DOM مطابقاً للترتيب البصري ولترتيب قارئ الشاشة، وعكس المصفوفة باليد هو ما يفسد هذا التطابق. أمّا تسمية كل زرّ مصغّر فتقع عند بدايته المنطقية: يساره في الإنجليزية ويمينه في العربية. ضع التسمية قبل الزرّ في DOM داخل صفّ flex فتنعكس مجّاناً، أو ثبّتها بـ inset-inline-end: calc(100% + 8px) لا بـ right. وحاذِ العنقود كلّه بـ align-items: flex-end، فهي في حاوية عمودية محاذاة على المحور المضمّن تتبع direction من تلقاء نفسها.
مصدر حركة الانبثاق يُقلب يدوياً
ينعكسحين تكبر الكبسولة أو ينبثق العنقود من القرص تبدأ الحركة من ركن الزرّ، وهذا الركن ينتقل مع الاتجاه بينما transform لا تعرف الاتجاه: لا مكافئ منطقياً لها في CSS، و transform-origin على المحور الأفقي لا تقبل إلا left أو right أو center أو طولاً أو نسبة مئوية، ولا وجود لكلمة inline-end فيها. اكتب transform-origin: bottom right ثم اقلبها إلى bottom left داخل [dir="rtl"]، أو احسب النسبة من متغيّر اتجاه. وفي المقابل الارتفاع فوق شريط الإشعار السفلي حركة على المحور الكتلي: قيمة translateY أو inset-block-end واحدة في الاتجاهين ولا تحتاج قاعدة RTL إطلاقاً، والعطب المعتاد هناك ليس الاتجاه بل ترك الشريط يغطّي الزرّ لأن الاثنين مثبّتان في الركن نفسه بلا تنسيق بينهما. وفي أندرويد يتكفّل CoordinatorLayout بهذه الإزاحة عبر FloatingActionButton.Behavior، وخارجه ترفعه بنفسك.
أسماؤه في الكود
كل سطر هو كلمة مكتبة واحدة عن نفس الشيء. خد السطر اللي بيكلّم مشروعك.
شوف كمان
آخر تحديث · 2026-08-19