Бегущая строка (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: activate 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 — нет, чтобы строка прокручивалась в одну сторону и возвращалась скачком.
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() с repeatable анимацией и .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; } } — пользователи с vestibular-расстройствами увидят статичный текст с возможностью скролла.
Бегущая строка оправдана в сценариях, где пользователь не может самостоятельно расширить контейнер, а полный текст критически важен: текущий трек в плеере на заблокированном экране, новостная строка на телевизионной панели, бегущая строка в приложении для такси (статус заказа).
Бегущую строку не стоит использовать в таблицах, списках, формах и диалогах — везде, где пользователь активно взаимодействует с интерфейсом. Анимация отвлекает от цели, увеличивает время поиска элемента и создаёт дискомфорт. По данным WebAIM (2024), 1 из 3 пользователей с расстройствами vestibular сообщает о дискомфорте при просмотре анимированного текста.
Правило большого пальца: если текст можно сократить или поместить на следующую строку — сделайте это. Бегущая строка — крайняя мера, а не универсальное решение для длинных подписей.
Бегущая строка — одна из самых проблемных 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также