Banda rulantă (marquee) — este un efect vizual prin care textul se derulează automat pe orizontală într-o zonă limitată, permițând afișarea liniilor lungi într-un container compact. Din punct de vedere istoric, termenul provine de la tag-ul HTML <marquee>, popular în web-ul anilor 1990. În dezvoltarea mobilă modernă, scrolling text este utilizat în playere, tickere de știri și bare de stare. Conform Apple Human Interface Guidelines și Google Material Design, banda rulantă este permisă doar în scenarii limitate, unde tăierea statică cu puncte de suspensie degradează UX.
Principalele puncte
Banda rulantă — este un efect UI prin care textul se mișcă pe orizontală într-o zonă fixă, derulându-se ciclic pentru ca utilizatorul să poată citi tot conținutul care nu încape în container. Spre deosebire de ellipsize, care ascunde o parte din text, marquee afișează întregul text, dar în mișcare.
Termenul marquee (engl. — „copertină”, „panou publicitar”) s-a fixat datorită tag-ului HTML <marquee>, care a fost adăugat în versiunile timpurii ale browserelor Netscape și Internet Explorer. Tag-ul permitea crearea de text derulant fără JavaScript și CSS, dar nu a fost niciodată parte a standardului HTML. Browserele moderne continuă să îl suporte pentru compatibilitate retrospectivă, dar utilizarea lui nu este recomandată.
În dezvoltarea mobilă, banda rulantă rezolvă problema etichetelor lungi în containere compacte: numele melodiilor în player, tickerele cursurilor valutare, liniile de știri pe ecranele TV. Totuși, din cauza animației, încarcă accessibility și poate irita utilizatorii — de aceea se aplică punctual.
Conform cercetărilor Nielsen Norman Group (2023), textul în mișcare reduce viteza de citire cu 15-20% și crește încărcarea cognitivă. Folosește banda rulantă doar atunci când tăierea statică (ellipsize) este cu adevărat inacceptabilă.
Pe Android, banda rulantă se implementează prin mecanismul încorporat TextView prin atributul android:ellipsize="marquee". Acesta este singurul mod ellipsize care nu taie textul, ci animează derularea lui.
Pentru activarea marquee trebuie îndeplinite trei condiții: singleLine="true" (sau maxLines="1"), ellipsize="marquee" și setSelected(true) în cod. Fără apelul setSelected() animația nu va porni.
<!-- 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: activează marquee
textView.setSelected(true)
textView.isSingleLine = true
Atributul marqueeRepeatLimit determină numărul de repetări ale animației: marquee_forever (infinit) sau un număr concret. Implicit — marquee_forever. Dacă textul este mai scurt decât lățimea containerului, animația nu pornește — TextView afișează text static.
Caracteristică importantă: setSelected(true) funcționează doar când TextView are focus sau se află în modul selected. În RecyclerView aceasta poate cauza probleme — animația pornește doar pentru un element. Soluția — utilizarea unui MarqueeTextView personalizat care suprascrie isFocused() și returnează întotdeauna true.
Începând cu Android 9 (API 28), Google a îmbunătățit comportamentul marquee — animația a devenit mai lină și nu se blochează la derularea listei. Totuși, pe dispozitivele vechi marquee poate sacada, mai ales la densitate mare de pixeli.
În iOS nu există un echivalent încorporat pentru android:ellipsize="marquee". Banda rulantă se implementează manual prin Core Animation sau cu ajutorul animației SwiftUI. Cel mai răspândit abord — animația layer.position în combinație cu un container clipping.
În UIKit banda rulantă se construiește pe trei elemente: container UILabel cu clipsToBounds = true, textul propriu-zis și CABasicAnimation pentru deplasarea textului pe X cu repetare infinită și autoreverses = false, pentru ca banda să se deruleze într-o direcție și să revină cu salt.
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")
}
Calculul duration = textWidth / 60 oferă o viteză de aproximativ 60 de pixeli pe secundă — un ritm confortabil pentru citire. Dacă viteza depășește 120 px/s, textul devine ilizibil. Dacă este sub 30 px/s — animația pare lentă. Modifică speed în funcție de scenariul specific.
În SwiftUI banda rulantă se implementează mai simplu — prin modificatorul offset() cu animație repetabilă și .animation(.linear(duration:).repeatForever(autoreverses: false)). SwiftUI gestionează automat ciclul de viață al animației, dar necesită GeometryReader pentru determinarea lățimii textului.
În web, tag-ul învechit <marquee> nu este recomandat pentru utilizare, deși browserele continuă să îl suporte. Implementarea modernă a benzii rulante — animația CSS cu cadre cheie și transform: translateX.
Avantajele abordării CSS: control complet asupra vitezei, pauzei și curbei de animație, suport pentru prefers-reduced-motion pentru accessibility, absența unui tag semantic incorect.
.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;
}
Trucul cheie: display: inline-block la conținut garantează că lățimea elementului este egală cu lățimea textului, iar translateX(-100%) îl deplasează exact cu această lățime la stânga. Dacă conținutul are display: block, translateX(-100%) va fi egal cu lățimea containerului, nu a textului, și animația se va strica.
Pentru accessibility adăugați @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } } — utilizatorii cu tulburări vestibulare vor vedea text static cu posibilitate de derulare.
Banda rulantă este justificată în scenarii unde utilizatorul nu poate extinde singur containerul, iar textul complet este critic: melodia curentă în player pe ecranul blocat, linia de știri pe panoul TV, banda rulantă în aplicația de taxi (statusul comenzii).
Banda rulantă nu trebuie utilizată în tabele, liste, formulare și dialoguri — oriunde utilizatorul interacționează activ cu interfața. Animația distrage atenția, mărește timpul de căutare a elementului și creează disconfort. Conform WebAIM (2024), 1 din 3 utilizatori cu tulburări vestibulare raportează disconfort la vizualizarea textului animat.
Regula de bază: dacă textul poate fi scurtat sau plasat pe rândul următor — fă aceasta. Banda rulantă este ultima soluție, nu o soluție universală pentru etichete lungi.
Banda rulantă este una dintre cele mai problematic animații UI din punct de vedere al accessibility. Textul în mișcare poate provoca amețeli, greață și dezorientare la persoanele cu tulburări vestibulare. Conform WCAG 2.2, orice animație care durează mai mult de 5 secunde trebuie să aibă un mecanism de pauză sau dezactivare.
Pe iOS folosește proprietatea UIAccessibility.isReduceMotionEnabled pentru verificarea setării sistemului. Dacă reduceMotion este activată — nu porni animația, afișează text static cu ellipsize.
Pe Android verifică animația sistemului prin Settings.Global.ANIMATOR_DURATION_SCALE. Dacă animația globală este dezactivată (duration scale = 0), banda rulantă nu trebuie activată.
În web folosește funcția media prefers-reduced-motion: @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } }. Aceasta este o capacitate standard CSS, suportată de toate browserele moderne. Verifică în DevTools: activează emularea prefers-reduced-motion și asigură-te că animația se dezactivează.
Întrebări frecvente
Ellipsize taie textul și pune puncte de suspensie, ascunzând o parte din conținut. Marquee derulează întregul text, dar în mișcare. Alegerea depinde de prioritate: să afișezi totul (marquee) sau să păstrezi staticul (ellipsize).
android:ellipsize="marquee" încorporat nu permite schimbarea vitezei — aceasta este determinată de ScrollSpeed-ul sistemului. Pentru viteză personalizată folosește Animation sau ValueAnimator manual.
Din cauza reutilizării view-urilor, setSelected(true) se resetează. Soluția: creează un MarqueeTextView personalizat care suprascrie isFocused() și returnează true, sau folosește o animație personalizată în Adapter.onViewAttachedToWindow().
Da, prin translateY în loc de translateX. Marquee-ul vertical este folosit pentru generice (credits) sau linii de știri în benzi verticale. Principiul este același, dar se schimbă axa de animație.
În CSS: :hover { animation-play-state: paused; }. Pe Android: urmărește focusul prin OnFocusChangeListener și resetează setSelected(false). Pe iOS: elimină animația prin layer.removeAnimation(forKey:).
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și