متن متحرک (marquee) — یک اثر بصری است که در آن متن به طور خودکار در یک ناحیه محدود به صورت افقی حرکت میکند و امکان نمایش خطوط طولانی در یک ظرف فشرده را فراهم میکند. از نظر تاریخی، این اصطلاح از تگ HTML <marquee> گرفته شده است که در وب دهه 1990 محبوب بود. در توسعه مدرن موبایل، scrolling text در پخشکنندهها، تیکرهای خبری و نوارهای وضعیت استفاده میشود. طبق Apple Human Interface Guidelines و Google Material Design، متن متحرک فقط در سناریوهای محدودی مجاز است که برش ایستا با سه نقطه UX را بدتر میکند.
نکات اصلی
متن متحرک — یک اثر UI است که در آن متن در یک منطقه ثابت به صورت افقی حرکت میکند و به صورت چرخهای پیمایش میشود تا کاربر بتواند تمام محتوایی که در ظرف جا نمیشود را بخواند. برخلاف ellipsize که بخشی از متن را پنهان میکند، marquee تمام متن را نشان میدهد، اما در حال حرکت.
اصطلاح marquee (انگلیسی به معنای «چادر»، «تابلوی تبلیغاتی») به لطف تگ HTML <marquee> تثبیت شد که در نسخههای اولیه مرورگرهای Netscape و Internet Explorer اضافه شده بود. این تگ امکان ایجاد متن متحرک بدون JavaScript و CSS را فراهم میکرد، اما هرگز بخشی از استاندارد HTML نبود. مرورگرهای مدرن برای سازگاری با عقب به پشتیبانی از آن ادامه میدهند، اما استفاده از آن توصیه نمیشود.
در توسعه موبایل، متن متحرک مشکل برچسبهای طولانی در ظروف فشرده را حل میکند: نام آهنگها در پخشکننده، تیکرهای نرخ ارز، نوارهای خبری در صفحههای تلویزیون. با این حال، به دلیل انیمیشن، بار accessibility را افزایش میدهد و ممکن است کاربران را آزار دهد — بنابراین به صورت محدود استفاده میشود.
طبق تحقیقات Nielsen Norman Group (2023)، متن متحرک سرعت خواندن را 15-20% کاهش میدهد و بار شناختی را افزایش میدهد. فقط زمانی از متن متحرک استفاده کنید که برش ایستا (ellipsize) واقعاً غیرقابل قبول باشد.
در Android، متن متحرک از طریق مکانیزم داخلی 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: فعالسازی marquee
textView.setSelected(true)
textView.isSingleLine = true
ویژگی marqueeRepeatLimit تعداد تکرار انیمیشن را تعیین میکند: marquee_forever (بینهایت) یا یک عدد مشخص. پیشفرض — marquee_forever. اگر متن کوتاهتر از عرض ظرف باشد، انیمیشن شروع نمیشود — TextView متن ایستا نشان میدهد.
ویژگی مهم: setSelected(true) فقط زمانی کار میکند که TextView فوکوس داشته باشد یا در حالت selected باشد. در RecyclerView این میتواند مشکلاتی ایجاد کند — انیمیشن فقط برای یک عنصر شروع میشود. راه حل — استفاده از MarqueeTextView سفارشی که isFocused() را بازنویسی میکند و همیشه true برمیگرداند.
از Android 9 (API 28) به بعد، Google رفتار marquee را بهبود بخشید — انیمیشن نرمتر شد و هنگام اسکرول لیست قفل نمیشود. با این حال، در دستگاههای قدیمی marquee ممکن است تکان بخورد، به خصوص در تراکم پیکسل بالا.
در iOS معادل داخلی android:ellipsize="marquee" وجود ندارد. متن متحرک به صورت دستی از طریق Core Animation یا با استفاده از انیمیشن SwiftUI پیادهسازی میشود. رایجترین روش — انیمیشن layer.position در ترکیب با ظرف clipping.
در UIKit متن متحرک بر سه عنصر ساخته میشود: ظرف 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 px/s باشد، متن غیرقابل خواندن میشود. اگر کمتر از 30 px/s باشد — انیمیشن کند به نظر میرسد. سرعت را متناسب با سناریوی خاص تغییر دهید.
در SwiftUI متن متحرک سادهتر پیادهسازی میشود — از طریق اصلاحکننده offset() با انیمیشن تکراری و .animation(.linear(duration:).repeatForever(autoreverses: false)). SwiftUI به طور خودکار چرخه حیات انیمیشن را مدیریت میکند، اما برای تعیین عرض متن به GeometryReader نیاز دارد.
در وب، تگ قدیمی <marquee> توصیه نمیشود، اگرچه مرورگرها همچنان از آن پشتیبانی میکنند. پیادهسازی مدرن متن متحرک — انیمیشن CSS با فریمهای کلیدی و transform: translateX.
مزایای رویکرد CSS: کنترل کامل بر سرعت، مکث و منحنی انیمیشن، پشتیبانی از prefers-reduced-motion برای accessibility، عدم وجود تگ نادرست از نظر معنایی.
.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%) برابر با عرض ظرف خواهد بود، نه عرض متن، و انیمیشن خراب میشود.
برای accessibility @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } } اضافه کنید — کاربران با اختلالات دهلیزی متن ایستا با قابلیت اسکرول مشاهده خواهند کرد.
متن متحرک موجه است در سناریوهایی که کاربر نمیتواند ظرف را خودش بزرگ کند و متن کامل اهمیت حیاتی دارد: آهنگ فعلی در پخشکننده در صفحه قفل شده، نوار خبری در صفحه تلویزیون، متن متحرک در برنامه تاکسی (وضعیت سفارش).
متن متحرک نباید استفاده شود در جداول، لیستها، فرمها و دیالوگها — هر جایی که کاربر به طور فعال با رابط تعامل دارد. انیمیشن حواس را پرت میکند، زمان جستجوی عنصر را افزایش میدهد و ناراحتی ایجاد میکند. طبق WebAIM (2024)، از هر 3 کاربر با اختلالات دهلیزی، 1 نفر هنگام مشاهده متن متحرک ناراحتی گزارش میکند.
قانون کلی: اگر متن قابل کوتاه کردن یا قرار دادن در خط بعدی است — این کار را انجام دهید. متن متحرک آخرین راه حل است، نه یک راه حل جهانی برای برچسبهای طولانی.
متن متحرک یکی از مشکلدارترین انیمیشنهای UI از نظر accessibility است. متن متحرک میتواند باعث سرگیجه، تهوع و سرگردانی در افراد مبتلا به اختلالات دهلیزی شود. طبق WCAG 2.2، هر انیمیشنی که بیش از 5 ثانیه طول میکشد باید دارای مکانیزم توقف یا خاموشی باشد.
در iOS از ویژگی UIAccessibility.isReduceMotionEnabled برای بررسی تنظیمات سیستم استفاده کنید. اگر reduceMotion فعال است — انیمیشن را اجرا نکنید، متن ایستا با ellipsize نشان دهید.
در Android انیمیشن سیستم را از طریق Settings.Global.ANIMATOR_DURATION_SCALE بررسی کنید. اگر انیمیشن جهانی خاموش است (duration scale = 0)، متن متحرک نباید فعال شود.
در وب از تابع رسانهای 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 به صورت دستی استفاده کنید.
به دلیل استفاده مجدد از viewها setSelected(true) بازنشانی میشود. راه حل: یک MarqueeTextView سفارشی ایجاد کنید که isFocused() را بازنویسی کرده و true برمیگرداند، یا از انیمیشن سفارشی در Adapter.onViewAttachedToWindow() استفاده کنید.
بله، از طریق translateY به جای translateX. marquee عمودی برای تیتراژ (credits) یا نوارهای خبری در نوارهای عمودی استفاده میشود. اصل یکسان است، اما محور انیمیشن تغییر میکند.
در CSS: :hover { animation-play-state: paused; }. در Android: فوکوس را از طریق OnFocusChangeListener ردیابی کنید و setSelected(false) را فراخوانی کنید. در iOS: انیمیشن را از طریق layer.removeAnimation(forKey:) حذف کنید.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید