Плъзгащ се текст (marquee) — визуален ефект, при който текстът автоматично се превърта хоризонтално в ограничена област, позволявайки показване на дълги редове в компактен контейнер. Исторически терминът произлиза от HTML тага <marquee>, популярен в уеб през 90-те години. В съвременната мобилна разработка 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 в комбинация с контейнер за изрязване.
В 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 — анимацията изглежда бавна. Променяйте speed според конкретния сценарий.
В 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), 1 от 3 потребители с вестибуларни нарушения съобщава за дискомфорт при гледане на анимиран текст.
Основно правило: ако текстът може да бъде съкратен или поставен на следващия ред — направете го. Плъзгащият се текст е крайна мярка, а не универсално решение за дълги етикети.
Плъзгащият се текст е една от най-проблемните 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 ръчно.
Поради повторното използване на изгледи, 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 създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също