Sürünən sətir (marquee) — mətnin məhdud bir sahə daxilində avtomatik olaraq üfüqi şəkildə sürüşdüyü vizual effektdir və uzun sətirlərin kompakt konteynerdə göstərilməsinə imkan verir. Tarixən bu termin 1990-cı illərin vebində məşhur olan <marquee> HTML teqindən qaynaqlanır. Müasir mobil inkişafda scrolling text pleyerlərdə, xəbər tickerlərində və status sətirlərində istifadə olunur. Apple Human Interface Guidelines və Google Material Design-a görə, sürünən sətir yalnız məhdud ssenarilərdə icazəlidir, burada statik kəsmə üç nöqtə ilə UX-i pisləşdirir.
Əsas məqamlar
Sürünən sətir — mətnin sabit bir sahə daxilində üfüqi şəkildə hərəkət etdiyi, dövrəvi olaraq sürüşdüyü UI effektidir ki, istifadəçi konteynerə sığmayan bütün məzmunu oxuya bilsin. Mətni gizlədən ellipsize-dən fərqli olaraq, marquee bütün mətni göstərir, ancaq hərəkətdə.
Marquee termini (ing. — "çadır", "reklam lövhəsi") Netscape və Internet Explorer brauzerlərinin ilkin versiyalarına əlavə edilmiş <marquee> HTML teqi sayəsində möhkəmlənmişdir. Teq JavaScript və CSS olmadan sürüşən mətn yaratmağa imkan verirdi, lakin heç vaxt HTML standartının bir hissəsi olmamışdır. Müasir brauzerlər geriyə uyğunluq üçün onu dəstəkləməyə davam edir, lakin ondan istifadə tövsiyə edilmir.
Mobil inkişafda sürünən sətir kompakt konteynerlərdə uzun etiketlər problemini həll edir: pleyerdə mahnı adları, valyuta məzənnəsi tickerləri, televiziya ekranlarında xəbər sətirləri. Lakin animasiyaya görə accessibility-ə yük yaradır və istifadəçiləri qıcıqlandıra bilər — buna görə də nöqtəli şəkildə tətbiq olunur.
Nielsen Norman Group (2023) tədqiqatlarına görə, hərəkətli mətn oxu sürətini 15-20% azaldır və idrak yükünü artırır. Sürünən sətiri yalnız statik kəsmə (ellipsize) həqiqətən qəbuledilməz olduqda istifadə edin.
Android-də sürünən sətir TextView-nin daxili mexanizmi vasitəsilə android:ellipsize="marquee" atributu ilə həyata keçirilir. Bu, mətni kəsməyən, əksinə onun sürüşməsini animasiya edən yeganə ellipsize rejimidir.
Marquee-ni aktivləşdirmək üçün üç şərt yerinə yetirilməlidir: singleLine="true" (və ya maxLines="1"), ellipsize="marquee" və kodda setSelected(true). setSelected() çağırılmazsa, animasiya başlamayacaq.
<!-- 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-ni aktivləşdir
textView.setSelected(true)
textView.isSingleLine = true
marqueeRepeatLimit atributu animasiyanın təkrar sayını təyin edir: marquee_forever (sonsuz) və ya konkret rəqəm. Varsayılan olaraq — marquee_forever. Mətn konteynerin genişliyindən qısadırsa, animasiya başlamır — TextView statik mətn göstərir.
Əhəmiyyətli xüsusiyyət: setSelected(true) yalnız TextView fokusda olduqda və ya seçilmiş rejimdə olduqda işləyir. RecyclerView-də bu problem yarada bilər — animasiya yalnız bir element üçün başlayır. Həll yolu — isFocused()-u override edən və həmişə true qaytaran xüsusi MarqueeTextView istifadə etməkdir.
Android 9-dan (API 28) başlayaraq, Google marquee davranışını yaxşılaşdırdı — animasiya daha hamar oldu və siyahının sürüşdürülməsi zamanı donmur. Lakin köhnə cihazlarda marquee, xüsusilə yüksək piksel sıxlığında, sıçraya bilər.
iOS-da android:ellipsize="marquee"-nin daxili analoqu yoxdur. Sürünən sətir əl ilə Core Animation və ya SwiftUI animasiyası vasitəsilə həyata keçirilir. Ən geniş yayılmış yanaşma — layer.position animasiyasının clipping konteyneri ilə birləşdirilməsidir.
UIKit-də sürünən sətir üç element üzərində qurulur: clipsToBounds = true olan UILabel konteyneri, mətnin özü və mətni X oxu boyunca sonsuz təkrarla sürüşdürən CABasicAnimation, autoreverses = false ilə ki, sətir bir istiqamətdə sürüşsün və sıçrayışla qayıtsın.
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 hesablanması saniyədə təxminən 60 piksel sürət verir — oxumaq üçün rahat temp. Sürət 120 px/s-dən yuxarı olarsa, mətn oxunmaz hala gəlir. 30 px/s-dən aşağı olarsa — animasiya ləng görünür. Sürəti konkret ssenariyə uyğun dəyişin.
SwiftUI-də sürünən sətir daha sadə həyata keçirilir — offset() modifieri ilə təkrarlanan animasiya və .animation(.linear(duration:).repeatForever(autoreverses: false)). SwiftUI animasiyanın həyat dövrünü avtomatik idarə edir, lakin mətnin genişliyini təyin etmək üçün GeometryReader tələb edir.
Vebdə köhnəlmiş <marquee> teqi tövsiyə edilmir, baxmayaraq ki, brauzerlər onu dəstəkləməyə davam edir. Sürünən sətirin müasir tətbiqi — açar kadrlarla CSS animasiyası və transform: translateX.
CSS yanaşmasının üstünlükləri: sürət, pauza və animasiya əyrisi üzərində tam nəzarət, accessibility üçün prefers-reduced-motion dəstəyi, semantik cəhətdən düzgün olmayan teqin olmaması.
.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;
}
Əsas hiylə: kontentdə display: inline-block elementin genişliyinin mətnin genişliyinə bərabər olmasını təmin edir, translateX(-100%) isə onu dəqiq bu genişlikdə sola sürüşdürür. Kontentdə display: block olarsa, translateX(-100%) konteynerin genişliyinə bərabər olacaq, mətnin genişliyinə yox, və animasiya pozulacaq.
Accessibility üçün @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } } əlavə edin — vestibulyar pozuntusu olan istifadəçilər sürüşdürmə imkanı ilə statik mətn görəcəklər.
Sürünən sətir əsaslandırılmışdır istifadəçinin konteyneri özü genişləndirə bilmədiyi və tam mətnin kritik əhəmiyyət daşıdığı ssenarilərdə: bloklanmış ekranda pleyerdə cari trek, televiziya panelində xəbər sətri, taksi tətbiqində sürünən sətir (sifariş statusu).
Sürünən sətir istifadə edilməməlidir cədvəllərdə, siyahılarda, formalarda və dialoqlarda — istifadəçinin interfeyslə aktiv qarşılıqlı əlaqədə olduğu hər yerdə. Animasiya diqqəti yayındırır, elementin axtarış vaxtını artırır və narahatlıq yaradır. WebAIM (2024) məlumatlarına görə, vestibulyar pozuntusu olan hər 3 istifadəçidən 1-i animasiyalı mətnə baxarkən narahatlıq bildirir.
Əsas qayda: mətni qısaltmaq və ya növbəti sətirə yerləşdirmək mümkündürsə — bunu edin. Sürünən sətir son çarədir, uzun etiketlər üçün universal həll yolu deyil.
Sürünən sətir accessibility baxımından ən problemli UI animasiyalarından biridir. Hərəkətli mətn vestibulyar pozuntusu olan insanlarda başgicəllənmə, ürəkbulanma və oriyentasiya itkisinə səbəb ola bilər. WCAG 2.2-yə görə, 5 saniyədən çox davam edən hər hansı animasiya dayandırma və ya söndürmə mexanizminə malik olmalıdır.
iOS-da sistem parametrini yoxlamaq üçün UIAccessibility.isReduceMotionEnabled xüsusiyyətindən istifadə edin. Əgər reduceMotion aktivdirsə — animasiyanı işə salmayın, ellipsize ilə statik mətn göstərin.
Android-də sistem animasiyasını Settings.Global.ANIMATOR_DURATION_SCALE vasitəsilə yoxlayın. Əgər qlobal animasiya söndürülübsə (duration scale = 0), sürünən sətir işə salınmamalıdır.
Vebdə prefers-reduced-motion media funksiyasından istifadə edin: @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } }. Bu, bütün müasir brauzerlər tərəfindən dəstəklənən standart CSS imkanıdır. DevTools-da yoxlayın: prefers-reduced-motion emulyasiyasını aktivləşdirin və animasiyanın söndüyünə əmin olun.
Tez-tez verilən suallar
Ellipsize mətni kəsir və üç nöqtə qoyur, məzmunun bir hissəsini gizlədir. Marquee bütün mətni sürüşdürür, ancaq hərəkətdə. Seçim prioritetdən asılıdır: hər şeyi göstərmək (marquee) və ya statikanı saxlamaq (ellipsize).
Daxili android:ellipsize="marquee" sürəti dəyişməyə imkan vermir — o, sistem ScrollSpeed tərəfindən müəyyən edilir. Xüsusi sürət üçün əl ilə Animation və ya ValueAnimator istifadə edin.
View-lərin təkrar istifadəsi səbəbindən setSelected(true) sıfırlanır. Həll yolu: isFocused()-u override edən və true qaytaran xüsusi MarqueeTextView yaradın və ya Adapter.onViewAttachedToWindow()-da xüsusi animasiyadan istifadə edin.
Bəli, translateX əvəzinə translateY vasitəsilə. Şaquli marquee titrlər (credits) və ya şaquli lentlərdə xəbər sətirləri üçün istifadə olunur. Prinsip eynidir, ancaq animasiya oxu dəyişir.
CSS-də: :hover { animation-play-state: paused; }. Android-də: fokusu OnFocusChangeListener vasitəsilə izləyin və setSelected(false) çağırın. iOS-da: layer.removeAnimation(forKey:) vasitəsilə animasiyanı silin.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun