Rullande text (marquee) — är en visuell effekt där text automatiskt rullas horisontellt inom ett begränsat område, vilket gör det möjligt att visa långa rader i en kompakt behållare. Historiskt kommer termen från HTML-taggen <marquee>, populär på webben under 1990-talet. I modern mobilutveckling används scrolling text i spelare, nyhetstickers och statusfält. Enligt Apple Human Interface Guidelines och Google Material Design är rullande text endast tillåten i begränsade scenarier där statisk trunkering med ellips försämrar UX.
Huvudpunkter
Rullande text — är en UI-effekt där text rör sig horisontellt inom ett fast område, cykliskt rullande så att användaren kan läsa allt innehåll som inte får plats i behållaren. Till skillnad från ellipsize, som döljer en del av texten, visar marquee hela texten, men i rörelse.
Termen marquee (eng. — “markis”, “reklamskylt”) etablerades tack vare HTML-taggen <marquee>, som lades till i tidiga versioner av webbläsarna Netscape och Internet Explorer. Taggen gjorde det möjligt att skapa rullande text utan JavaScript och CSS, men var aldrig en del av HTML-standarden. Moderna webbläsare fortsätter att stödja den för bakåtkompatibilitet, men dess användning rekommenderas inte.
I mobilutveckling löser rullande text problemet med långa etiketter i kompakta behållare: låttitlar i spelaren, valutakurstickers, nyhetsrader på TV-skärmar. Men på grund av animationen belastar den accessibility och kan irritera användare — därför tillämpas den punktvis.
Enligt forskning från Nielsen Norman Group (2023) minskar rörlig text läshastigheten med 15-20% och ökar den kognitiva belastningen. Använd endast rullande text när statisk trunkering (ellipsize) verkligen är oacceptabel.
På Android implementeras rullande text via den inbyggda mekanismen i TextView med attributet android:ellipsize="marquee". Detta är det enda ellipsize-läget som inte trunkerar text, utan animerar dess rullning.
För att aktivera marquee måste tre villkor uppfyllas: singleLine="true" (eller maxLines="1"), ellipsize="marquee" och setSelected(true) i koden. Utan anrop till setSelected() startar inte animationen.
<!-- 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: aktivera marquee
textView.setSelected(true)
textView.isSingleLine = true
Attributet marqueeRepeatLimit bestämmer antalet repetitioner av animationen: marquee_forever (oändligt) eller ett specifikt tal. Standard — marquee_forever. Om texten är kortare än behållarens bredd startar inte animationen — TextView visar statisk text.
Viktig egenskap: setSelected(true) fungerar endast när TextView har fokus eller är i selected-läge. I RecyclerView kan detta orsaka problem — animationen startar bara för ett element. Lösning — använd en anpassad MarqueeTextView som åsidosätter isFocused() och alltid returnerar true.
Från och med Android 9 (API 28) har Google förbättrat marquees beteende — animationen blev jämnare och fryser inte vid rullning av listan. På äldre enheter kan marquee dock rycka, särskilt vid hög pixeltäthet.
I iOS finns ingen inbyggd motsvarighet till android:ellipsize="marquee". Rullande text implementeras manuellt via Core Animation eller med SwiftUI-animation. Det vanligaste tillvägagångssättet — animation av layer.position i kombination med en clipping-behållare.
I UIKit byggs rullande text på tre element: en UILabel-behållare med clipsToBounds = true, själva texten och en CABasicAnimation för att flytta texten längs X med oändlig upprepning och autoreverses = false, så att bandet rullar åt ett håll och återvänder med ett hopp.
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")
}
Beräkning av duration = textWidth / 60 ger en hastighet på cirka 60 pixlar per sekund — en bekväm takt för läsning. Om hastigheten överstiger 120 px/s blir texten oläslig. Under 30 px/s — ser animationen trög ut. Ändra speed enligt det specifika scenariot.
I SwiftUI implementeras rullande text enklare — via offset()-modifieraren med repeterbar animation och .animation(.linear(duration:).repeatForever(autoreverses: false)). SwiftUI hanterar automatiskt animationens livscykel, men kräver GeometryReader för att bestämma textens bredd.
På webben är den föråldrade taggen <marquee> inte rekommenderad att använda, även om webbläsare fortsätter att stödja den. Modern implementering av rullande text — CSS-animation med nyckelbildrutor och transform: translateX.
Fördelar med CSS-metoden: full kontroll över hastighet, paus och animeringskurva, stöd för prefers-reduced-motion för accessibility, frånvaro av semantiskt felaktig tagg.
.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;
}
Det viktigaste tricket: display: inline-block på innehållet garanterar att elementets bredd är lika med textens bredd, och translateX(-100%) flyttar det exakt med den bredden åt vänster. Om innehållet har display: block kommer translateX(-100%) att vara lika med behållarens bredd, inte textens, och animationen går sönder.
För accessibility, lägg till @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } } — användare med vestibulära störningar kommer att se statisk text med möjlighet att rulla.
Rullande text är berättigad i scenarier där användaren inte själv kan utöka behållaren och den fullständiga texten är kritisk: aktuell låt i spelaren på låst skärm, nyhetsrad på TV-panel, rullande text i taxi-app (beställningsstatus).
Rullande text bör inte användas i tabeller, listor, formulär och dialoger — överallt där användaren aktivt interagerar med gränssnittet. Animation distraherar, ökar söktiden för element och orsakar obehag. Enligt WebAIM (2024) rapporterar 1 av 3 användare med vestibulära störningar obehag vid visning av animerad text.
Tumregel: om texten kan förkortas eller placeras på nästa rad — gör det. Rullande text är en sista utväg, inte en universallösning för långa etiketter.
Rullande text är en av de mest problematiska UI-animationerna ur accessibility-synvinkel. Rörlig text kan orsaka yrsel, illamående och desorientering hos personer med vestibulära störningar. Enligt WCAG 2.2 måste varje animation som varar längre än 5 sekunder ha en paus- eller avstängningsmekanism.
På iOS, använd egenskapen UIAccessibility.isReduceMotionEnabled för att kontrollera systeminställningen. Om reduceMotion är aktiverad — starta inte animationen, visa statisk text med ellipsize.
På Android, kontrollera systemanimation via Settings.Global.ANIMATOR_DURATION_SCALE. Om global animation är avstängd (duration scale = 0), bör rullande text inte aktiveras.
På webben, använd mediafunktionen prefers-reduced-motion: @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } }. Detta är en standard CSS-funktion som stöds av alla moderna webbläsare. Kontrollera i DevTools: aktivera emulering av prefers-reduced-motion och se till att animationen stängs av.
Vanliga frågor
Ellipsize trunkerar text och sätter ellips, döljer en del av innehållet. Marquee rullar hela texten, men i rörelse. Valet beror på prioritet: visa allt (marquee) eller behålla statiskt (ellipsize).
Inbyggda android:ellipsize="marquee" tillåter inte hastighetsändring — den bestäms av systemets ScrollSpeed. För anpassad hastighet, använd Animation eller ValueAnimator manuellt.
På grund av återanvändning av vyer återställs setSelected(true). Lösning: skapa en anpassad MarqueeTextView som åsidosätter isFocused() och returnerar true, eller använd en anpassad animation i Adapter.onViewAttachedToWindow().
Ja, via translateY istället för translateX. Vertikal marquee används för eftertexter (credits) eller nyhetsrader i vertikala band. Principen är densamma, men animationsaxeln ändras.
I CSS: :hover { animation-play-state: paused; }. På Android: övervaka fokus via OnFocusChangeListener och återställ setSelected(false). På iOS: ta bort animationen via layer.removeAnimation(forKey:).
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också