Běžící text (marquee) — je vizuální efekt, při kterém se text automaticky posouvá horizontálně v omezené oblasti, což umožňuje zobrazovat dlouhé řádky v kompaktním kontejneru. Historicky termín pochází z HTML tagu <marquee>, populárního na webu 90. let. V moderním mobilním vývoji se scrolling text používá v přehrávačích, zpravodajských tickerech a stavových řádcích. Podle Apple Human Interface Guidelines a Google Material Design je běžící text povolen pouze v omezených scénářích, kde statické oříznutí s třemi tečkami zhoršuje UX.
Hlavní body
Běžící text — je UI efekt, při kterém se text pohybuje horizontálně v pevné oblasti, cyklicky se posouvá, aby uživatel mohl přečíst veškerý obsah, který se nevejde do kontejneru. Na rozdíl od ellipsize, který skrývá část textu, marquee zobrazuje celý text, ale v pohybu.
Termín marquee (angl. — „markýza“, „reklamní panel“) se ustálil díky HTML tagu <marquee>, který byl přidán v raných verzích prohlížečů Netscape a Internet Explorer. Tag umožňoval vytvářet posouvající se text bez JavaScriptu a CSS, ale nikdy nebyl součástí HTML standardu. Moderní prohlížeče jej nadále podporují kvůli zpětné kompatibilitě, ale jeho použití se nedoporučuje.
V mobilním vývoji běžící text řeší problém dlouhých popisků v kompaktních kontejnerech: názvy skladeb v přehrávači, tickery směnných kurzů, zpravodajské řádky na televizních obrazovkách. Kvůli animaci však zatěžuje accessibility a může uživatele dráždit — proto se používá cíleně.
Podle výzkumu Nielsen Norman Group (2023) pohyblivý text snižuje rychlost čtení o 15-20 % a zvyšuje kognitivní zátěž. Používejte běžící text pouze tehdy, když je statické oříznutí (ellipsize) skutečně nepřijatelné.
Na Androidu se běžící text implementuje vestavěným mechanismem TextView přes atribut android:ellipsize="marquee". Toto je jediný režim ellipsize, který text neořezává, ale animuje jeho posouvání.
Pro aktivaci marquee je třeba splnit tři podmínky: singleLine="true" (nebo maxLines="1"), ellipsize="marquee" a setSelected(true) v kódu. Bez volání setSelected() se animace nespustí.
<!-- 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: aktivovat marquee
textView.setSelected(true)
textView.isSingleLine = true
Atribut marqueeRepeatLimit určuje počet opakování animace: marquee_forever (nekonečně) nebo konkrétní číslo. Výchozí — marquee_forever. Pokud je text kratší než šířka kontejneru, animace se nespustí — TextView zobrazuje statický text.
Důležitá vlastnost: setSelected(true) funguje pouze když má TextView fokus nebo je v režimu selected. V RecyclerView to může způsobovat problémy — animace se spouští pouze pro jeden prvek. Řešení — použijte vlastní MarqueeTextView, který přepíše isFocused() a vždy vrací true.
Od Androidu 9 (API 28) Google vylepšil chování marquee — animace se stala plynulejší a nezasekává se při rolování seznamu. Na starších zařízeních může marquee cukat, zejména při vysoké hustotě pixelů.
V iOS neexistuje vestavěný ekvivalent android:ellipsize="marquee". Běžící text se implementuje ručně přes Core Animation nebo pomocí SwiftUI animace. Nejrozšířenější přístup — animace layer.position v kombinaci s clipping kontejnerem.
V UIKit se běžící text staví na třech prvcích: kontejner UILabel s clipsToBounds = true, samotný text a CABasicAnimation pro posun textu po X s nekonečným opakováním a autoreverses = false, aby se řádek posouval jedním směrem a vracel se skokem.
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")
}
Výpočet duration = textWidth / 60 dává rychlost přibližně 60 pixelů za sekundu — pohodlné tempo pro čtení. Pokud rychlost překročí 120 px/s, text se stává nečitelným. Pokud je pod 30 px/s — animace vypadá pomalu. Měňte speed podle konkrétního scénáře.
Ve SwiftUI se běžící text implementuje jednodušeji — přes modifikátor offset() s opakovatelnou animací a .animation(.linear(duration:).repeatForever(autoreverses: false)). SwiftUI automaticky spravuje životní cyklus animace, ale vyžaduje GeometryReader pro určení šířky textu.
Na webu se zastaralý tag <marquee> nedoporučuje používat, ačkoli jej prohlížeče nadále podporují. Moderní implementace běžícího textu — CSS animace s klíčovými snímky a transform: translateX.
Výhody CSS přístupu: plná kontrola nad rychlostí, pauzou a křivkou animace, podpora prefers-reduced-motion pro accessibility, absence sémanticky nesprávného tagu.
.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;
}
Klíčový trik: display: inline-block u obsahu zaručuje, že šířka prvku je rovna šířce textu, a translateX(-100%) jej posouvá přesně o tuto šířku doleva. Pokud má obsah display: block, translateX(-100%) bude roven šířce kontejneru, ne textu, a animace se rozbije.
Pro accessibility přidejte @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } } — uživatelé s vestibulárními poruchami uvidí statický text s možností rolování.
Běžící text je opodstatněný ve scénářích, kde uživatel nemůže sám rozšířit kontejner a plný text je kritický: aktuální skladba v přehrávači na zamčené obrazovce, zpravodajský řádek na TV panelu, běžící text v taxi aplikaci (stav objednávky).
Běžící text není vhodné používat v tabulkách, seznamech, formulářích a dialozích — všude tam, kde uživatel aktivně interaguje s rozhraním. Animace rozptyluje, prodlužuje dobu hledání prvku a způsobuje nepohodlí. Podle WebAIM (2024) 1 ze 3 uživatelů s vestibulárními poruchami hlásí nepohodlí při sledování animovaného textu.
Pravidlo palce: pokud lze text zkrátit nebo umístit na další řádek — udělejte to. Běžící text je krajní opatření, nikoli univerzální řešení pro dlouhé popisky.
Běžící text je jednou z nejproblematičtějších UI animací z hlediska accessibility. Pohyblivý text může způsobovat závratě, nevolnost a dezorientaci u lidí s vestibulárními poruchami. Podle WCAG 2.2 musí mít každá animace trvající déle než 5 sekund mechanismus pauzy nebo vypnutí.
Na iOS použijte vlastnost UIAccessibility.isReduceMotionEnabled pro kontrolu systémového nastavení. Pokud je reduceMotion zapnuto — nespouštějte animaci, zobrazte statický text s ellipsize.
Na Androidu kontrolujte systémovou animaci přes Settings.Global.ANIMATOR_DURATION_SCALE. Pokud je globální animace vypnuta (duration scale = 0), běžící text by neměl být zapínán.
Na webu použijte media funkci prefers-reduced-motion: @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } }. Toto je standardní CSS možnost podporovaná všemi moderními prohlížeči. Zkontrolujte v DevTools: zapněte emulaci prefers-reduced-motion a ujistěte se, že se animace vypíná.
Často kladené otázky
Ellipsize ořezává text a vkládá tři tečky, skrývá část obsahu. Marquee posouvá celý text, ale v pohybu. Volba závisí na prioritě: zobrazit vše (marquee) nebo zachovat statiku (ellipsize).
Vestavěný android:ellipsize="marquee" neumožňuje měnit rychlost — je určena systémovým ScrollSpeed. Pro vlastní rychlost použijte ručně Animation nebo ValueAnimator.
Kvůli opětovnému použití view se setSelected(true) resetuje. Řešení: vytvořte vlastní MarqueeTextView, který přepíše isFocused() a vrací true, nebo použijte vlastní animaci v Adapter.onViewAttachedToWindow().
Ano, přes translateY místo translateX. Vertikální marquee se používá pro titulky (credits) nebo zpravodajské řádky ve vertikálních pásech. Princip je stejný, ale mění se osa animace.
V CSS: :hover { animation-play-state: paused; }. Na Androidu: sledujte fokus přes OnFocusChangeListener a resetujte setSelected(false). Na iOS: odstraňte animaci přes layer.removeAnimation(forKey:).
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také