النص المتحرك (marquee) هو تأثير بصري يتم فيه تمرير النص تلقائياً أفقياً داخل منطقة محددة، مما يسمح بعرض الأسطر الطويلة في حاوية مدمجة. تاريخياً، يأتي المصطلح من وسم HTML <marquee>، الذي كان شائعاً على الويب في التسعينيات. في تطوير التطبيقات الحديثة، يُستخدم scrolling text في المشغلات، وشريط الأخبار، وأشرطة الحالة. وفقاً لإرشادات Apple Human Interface و Google Material Design، فإن النص المتحرك مقبول فقط في السيناريوهات المحدودة حيث يؤدي القطع الثابت بعلامات الحذف إلى تدهور تجربة المستخدم.
الخلاصة
النص المتحرك هو تأثير واجهة مستخدم يتحرك فيه النص أفقياً داخل منطقة ثابتة، ويتحرك دورياً ليتمكن المستخدم من قراءة كل المحتوى الذي لا يتسع في الحاوية. على عكس ellipsize الذي يخفي جزءاً من النص، يعرض marquee النص بالكامل ولكن متحركاً.
المصطلح marquee ترسخ بفضل وسم HTML <marquee> الذي أُضيف في الإصدارات المبكرة من متصفحي Netscape و Internet Explorer. كان الوسم يسمح بإنشاء نص متحرك بدون JavaScript أو CSS، لكنه لم يكن جزءاً من معيار HTML قط. تستمر المتصفحات الحديثة في دعمه للتوافق مع الإصدارات القديمة، لكن لا يُنصح باستخدامه.
في تطوير التطبيقات، يحل marquee مشكلة التسميات الطويلة في الحاويات المدمجة: أسماء المسارات في المشغل، وأسعار العملات، والأخبار على شاشات التلفزيون. ولكن بسبب التحريك، يُشكل عبئاً على إمكانية الوصول وقد يزعج المستخدمين — لذلك يُستخدم بشكل انتقائي.
وفقاً لبحث Nielsen Norman Group (2023)، فإن النص المتحرك يقلل سرعة القراءة بنسبة 15-20% ويزيد العبء المعرفي. استخدم marquee فقط عندما يكون القطع الثابت (ellipsize) غير مقبول حقاً.
على Android، يُنفذ marquee عبر الآلية المدمجة في TextView باستخدام السمة android:ellipsize="marquee". هذا هو وضع ellipsize الوحيد الذي لا يقطع النص بل يحرك تمريره.
لتفعيل marquee يجب توفر ثلاثة شروط: singleLine="true" (أو maxLines="1")، و ellipsize="marquee"، و setSelected(true) في الكود. بدون استدعاء setSelected()، لن تبدأ الحركة.
<!-- XML layout -->
<TextView
android:id="@+id/marquee_text"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:singleLine="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever" />
// Kotlin: activate marquee
textView.setSelected(true)
textView.isSingleLine = true
تحدد السمة marqueeRepeatLimit عدد مرات تكرار الحركة: marquee_forever (لا نهائي) أو رقم محدد. القيمة الافتراضية هي marquee_forever. إذا كان النص أقصر من عرض الحاوية، لا تبدأ الحركة — يعرض TextView نصاً ثابتاً.
ميزة مهمة: setSelected(true) تعمل فقط عندما يكون TextView في حالة التركيز أو في الوضع المحدد. في RecyclerView، قد يسبب هذا مشاكل — تبدأ الحركة لعنصر واحد فقط. الحل: استخدام MarqueeTextView مخصص يجاوز isFocused() ويعيد true دائماً.
ابتداءً من Android 9 (API 28)، حسنت Google سلوك marquee — أصبحت الحركة أكثر سلاسة ولا تتجمد عند تمرير القائمة. لكن على الأجهزة القديمة، قد يتعطل marquee خاصة في كثافات البكسل العالية.
iOS لا يحتوي على مكافئ مدمج لـ android:ellipsize="marquee". يُنفذ marquee يدوياً عبر Core Animation أو تحريك SwiftUI. النهج الأكثر شيوعاً هو تحريك layer.position مع حاوية مقطوعة.
في UIKit، يُبنى marquee من ثلاثة عناصر: حاوية UILabel مع clipsToBounds = true، والنص نفسه، و CABasicAnimation لتحريك النص على المحور X بتكرار لا نهائي و autoreverses = false، لينزلق السطر في اتجاه واحد ويعود قفزة.
func startMarquee(for label: UILabel) {
label.clipsToBounds = true
label.numberOfLines = 1
let textWidth = (label.text as? NSString)?
.size(withAttributes: [.font: label.font as any]).width ?? 0
guard textWidth > label.bounds.width else { return }
let animation = CABasicAnimation(keyPath: "position.x")
animation.fromValue = label.layer.position.x + label.bounds.width / 2
animation.toValue = label.layer.position.x - textWidth - label.bounds.width / 2
animation.duration = CFTimeInterval(textWidth / 60)
animation.repeatCount = .infinity
animation.autoreverses = false
label.layer.add(animation, forKey: "marquee")
}
حساب duration = textWidth / 60 يعطي سرعة حوالي 60 بكسل في الثانية — وهي وتيرة قراءة مريحة. إذا تجاوزت السرعة 120 بكسل/ثانية، يصبح النص غير قابل للقراءة. إذا كانت أقل من 30 بكسل/ثانية، تبدو الحركة بطيئة. اضبط السرعة حسب السيناريو الخاص بك.
في SwiftUI، يُنفذ marquee ببساطة أكبر — عبر معدل offset() مع حركة قابلة للتكرار و .animation(.linear(duration:).repeatForever(autoreverses: false)). يدير SwiftUI دورة حياة الحركة تلقائياً لكنه يتطلب GeometryReader لتحديد عرض النص.
على الويب، وسم <marquee> القديم غير موصى به للاستخدام، رغم أن المتصفحات تستمر في دعمه. التنفيذ الحديث للنص المتحرك هو تحريك CSS مع keyframes و transform: translateX.
مزايا نهج CSS: تحكم كامل في السرعة والإيقاف ومنحنى الحركة، ودعم prefers-reduced-motion لإمكانية الوصول، وغياب وسم غير صحيح دلالياً.
.marquee {
overflow: hidden;
white-space: nowrap;
width: 300px;
}
.marquee-content {
display: inline-block;
animation: marquee 10s linear infinite;
}
@keyframes marquee {
0% { transform: translateX(0); }
100% { transform: translateX(-100%); }
}
/* Pause on hover */
.marquee-content:hover {
animation-play-state: paused;
}
الحيلة الأساسية: display: inline-block في المحتوى يضمن أن عرض العنصر يساوي عرض النص، و translateX(-100%) يزيحه بمقدار هذا العرض إلى اليسار. إذا كان المحتوى display: block، فإن translateX(-100%) سيساوي عرض الحاوية وليس عرض النص، وستنكسر الحركة.
لإمكانية الوصول، أضف @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } } — المستخدمون الذين يعانون من اضطرابات دهليزية سيرون نصاً ثابتاً مع إمكانية التمرير.
النص المتحرك مبرر في السيناريوهات التي لا يستطيع المستخدم فيها توسيع الحاوية بنفسه والنص الكامل مهم بشكل حاسم: المسار الحالي في مشغل على شاشة مقفلة، وشريط أخبار على لوحة تلفزيون، ونص متحرك في تطبيق نقل (حالة الطلب).
لا يُستخدم النص المتحرك في الجداول والقوائم والنماذج والحوارات — حيث يتفاعل المستخدم بنشاط مع الواجهة. الحركة تشتت الانتباه عن الهدف، وتزيد وقت البحث عن العنصر، وتخلق عدم ارتياح. وفقاً لـ WebAIM (2024)، 1 من كل 3 مستخدمين يعانون من اضطرابات دهليزية يبلغون عن عدم الراحة عند مشاهدة نص متحرك.
قاعدة عامة: إذا كان يمكن تقصير النص أو وضعه في السطر التالي — فافعل ذلك. النص المتحرك هو الحل الأخير، وليس حلاً عالمياً للتسميات الطويلة.
النص المتحرك هو أحد أكثر حركات واجهة المستخدم إشكالية من وجهة نظر إمكانية الوصول. يمكن أن يسبب النص المتحرك دواراً وغثياناً وارتباكاً للأشخاص الذين يعانون من اضطرابات دهليزية. وفقاً لـ WCAG 2.2، يجب أن يحتوي أي تحريك يستمر لأكثر من 5 ثوانٍ على آلية إيقاف أو تعطيل.
على iOS، استخدم الخاصية UIAccessibility.isReduceMotionEnabled للتحقق من إعداد النظام. إذا كان reduceMotion مفعّلاً — لا تبدأ الحركة، اعرض نصاً ثابتاً مع ellipsize.
على Android، تحقق من حركة النظام عبر Settings.Global.ANIMATOR_DURATION_SCALE. إذا كانت الحركة العامة معطلة (duration scale = 0)، فلا يجب تفعيل marquee.
على الويب، استخدم ميزة الوسائط prefers-reduced-motion: @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } }. هذه ميزة CSS قياسية مدعومة من جميع المتصفحات الحديثة. تحقق في DevTools: فعّل محاكاة prefers-reduced-motion وتأكد من إيقاف الحركة.
الأسئلة الشائعة
Ellipsize يقطع النص ويضيف علامات حذف، مخفياً جزءاً من المحتوى. Marquee يمرر النص بالكامل ولكن متحركاً. يعتمد الاختيار على الأولوية: إظهار كل شيء (marquee) أو الحفاظ على العرض الثابت (ellipsize).
android:ellipsize="marquee" المدمج لا يسمح بتغيير السرعة — يتم تحديدها بواسطة ScrollSpeed النظام. للسرعة المخصصة، استخدم Animation أو ValueAnimator يدوياً.
بسبب إعادة استخدام العناصر، يتم إعادة تعيين setSelected(true). الحل: أنشئ MarqueeTextView مخصصاً يجاوز isFocused() ويعيد true، أو استخدم حركة مخصصة في Adapter.onViewAttachedToWindow().
نعم، باستخدام translateY بدلاً من translateX. يُستخدم marquee العمودي للاعتمادات أو شريط الأخبار في التغذيات الرأسية. المبدأ هو نفسه لكن محور الحركة يتغير.
في CSS: :hover { animation-play-state: paused; }. على Android: تتبع التركيز عبر OnFocusChangeListener وأعد تعيين setSelected(false). على iOS: أزل الحركة عبر layer.removeAnimation(forKey:).
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا