Κυλιόμενο κείμενο (marquee) — είναι ένα οπτικό εφέ κατά το οποίο το κείμενο κυλάει αυτόματα οριζόντια μέσα σε μια περιορισμένη περιοχή, επιτρέποντας την εμφάνιση μεγάλων γραμμών σε ένα συμπαγές δοχείο. Ιστορικά, ο όρος προέρχεται από την ετικέτα HTML <marquee>, δημοφιλή στο web της δεκαετίας του 1990. Στη σύγχρονη κινητή ανάπτυξη, το scrolling text χρησιμοποιείται σε συσκευές αναπαραγωγής, ειδησεογραφικά ticker και γραμμές κατάστασης. Σύμφωνα με το Apple Human Interface Guidelines και το Google Material Design, το κυλιόμενο κείμενο επιτρέπεται μόνο σε περιορισμένα σενάρια όπου η στατική αποκοπή με αποσιωπητικά υποβαθμίζει την UX.
Κύρια σημεία
Κυλιόμενο κείμενο — είναι ένα εφέ UI κατά το οποίο το κείμενο κινείται οριζόντια μέσα σε μια σταθερή περιοχή, κυκλικά κυλώντας ώστε ο χρήστης να μπορεί να διαβάσει όλο το περιεχόμενο που δεν χωράει στο δοχείο. Σε αντίθεση με το ellipsize, που κρύβει μέρος του κειμένου, το marquee εμφανίζει ολόκληρο το κείμενο, αλλά σε κίνηση.
Ο όρος marquee (αγγλ. — “τέντα”, “διαφημιστική πινακίδα”) εδραιώθηκε χάρη στην ετικέτα HTML <marquee>, που προστέθηκε στις πρώτες εκδόσεις των προγραμμάτων περιήγησης Netscape και Internet Explorer. Η ετικέτα επέτρεπε τη δημιουργία κυλιόμενου κειμένου χωρίς JavaScript και CSS, αλλά ποτέ δεν ήταν μέρος του προτύπου HTML. Τα σύγχρονα προγράμματα περιήγησης συνεχίζουν να την υποστηρίζουν για συμβατότητα προς τα πίσω, αλλά η χρήση της δεν συνιστάται.
Στην κινητή ανάπτυξη, το κυλιόμενο κείμενο λύνει το πρόβλημα των μεγάλων ετικετών σε συμπαγή δοχεία: ονόματα τραγουδιών στη συσκευή αναπαραγωγής, ticker συναλλαγματικών ισοτιμιών, γραμμές ειδήσεων σε οθόνες TV. Ωστόσο, λόγω της κίνησης, επιβαρύνει την 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 μπορεί να τραντάζεται, ειδικά σε υψηλή πυκνότητα pixel.
Στο iOS δεν υπάρχει ενσωματωμένο αντίστοιχο του android:ellipsize="marquee". Το κυλιόμενο κείμενο υλοποιείται χειροκίνητα μέσω Core Animation ή με χρήση κίνησης SwiftUI. Η πιο συνηθισμένη προσέγγιση — κίνηση του layer.position σε συνδυασμό με δοχείο clipping.
Στο 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 pixel ανά δευτερόλεπτο — άνετος ρυθμός για ανάγνωση. Εάν η ταχύτητα υπερβαίνει τα 120 px/s, το κείμενο γίνεται δυσανάγνωστο. Εάν είναι κάτω από 30 px/s — η κίνηση φαίνεται αργή. Αλλάξτε το speed ανάλογα με το συγκεκριμένο σενάριο.
Στο SwiftUI, το κυλιόμενο κείμενο υλοποιείται πιο απλά — μέσω του modifier offset() με επαναλαμβανόμενη κίνηση και .animation(.linear(duration:).repeatForever(autoreverses: false)). Το SwiftUI διαχειρίζεται αυτόματα τον κύκλο ζωής της κίνησης, αλλά απαιτεί GeometryReader για τον προσδιορισμό του πλάτους του κειμένου.
Στο web, η παρωχημένη ετικέτα <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), το κυλιόμενο κείμενο δεν πρέπει να ενεργοποιείται.
Στο web, χρησιμοποιήστε τη λειτουργία πολυμέσων 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 χειροκίνητα.
Λόγω επαναχρησιμοποίησης των view, το 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. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης