ข้อความวิ่งในแอปพลิเคชัน — คืออะไร วิธีการสร้างและกำหนดค่า

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-07-25 เวลาอ่าน: 9 นาที

ข้อความวิ่ง (marquee) เป็นเอฟเฟกต์ภาพที่ข้อความเลื่อนโดยอัตโนมัติในแนวนอนภายในพื้นที่จำกัด ทำให้สามารถแสดงสตริงยาวในคอนเทนเนอร์ขนาดเล็กได้ ในอดีต คำนี้มาจากแท็ก HTML <marquee> ซึ่งได้รับความนิยมบนเว็บในยุค 1990 ในการพัฒนาแอปพลิเคชันสมัยใหม่ scrolling text ถูกใช้ในเครื่องเล่น ข่าววิ่ง และแถบสถานะ ตาม Apple Human Interface Guidelines และ Google Material Design ข้อความวิ่งจะยอมรับได้เฉพาะในสถานการณ์จำกัดเท่านั้นที่การตัดแบบคงที่ด้วยจุดไข่ปลาทำให้ UX แย่ลง

ประเด็นสำคัญ

  • ข้อความวิ่ง — การเลื่อนแนวนอนอัตโนมัติของข้อความยาวในคอนเทนเนอร์ที่จำกัด
  • บน Android ใช้งานผ่าน android:ellipsize="marquee" ใน TextView
  • บน iOS ทำเอฟเฟกต์ด้วยตนเองผ่าน Core Animation หรือ SwiftUI ด้วยแอนิเมชัน offset
  • บนเว็บ แท็ก <marquee> เลิกใช้แล้ว ใช้ CSS แอนิเมชันด้วย translateX แทน
  • สำหรับการเข้าถึง จำเป็นต้องรองรับ prefers-reduced-motion

ข้อความวิ่งคืออะไร?

ข้อความวิ่ง เป็นเอฟเฟกต์ UI ที่ข้อความเคลื่อนที่ในแนวนอนภายในพื้นที่คงที่ เลื่อนเป็นวงจรเพื่อให้ผู้ใช้สามารถอ่านเนื้อหาทั้งหมดที่ไม่พอดีในคอนเทนเนอร์ แตกต่างจาก ellipsize ที่ซ่อนส่วนหนึ่งของข้อความ marquee แสดงข้อความทั้งหมดแต่เคลื่อนที่

คำว่า marquee ถูกกำหนดขึ้นจากแท็ก HTML <marquee> ที่ถูกเพิ่มในเบราว์เซอร์ Netscape และ Internet Explorer รุ่นแรก แท็กนี้ช่วยให้สร้างข้อความวิ่งได้โดยไม่ต้องใช้ JavaScript หรือ CSS แต่ไม่เคยเป็นส่วนหนึ่งของมาตรฐาน HTML เบราว์เซอร์สมัยใหม่ยังคงรองรับเพื่อความเข้ากันได้ย้อนหลัง แต่ไม่แนะนำให้ใช้งาน

ในการพัฒนาแอปพลิเคชัน marquee แก้ปัญหาป้ายข้อความยาวในคอนเทนเนอร์ขนาดเล็ก: ชื่อเพลงในเครื่องเล่น ราคาสกุลเงิน ข่าววิ่งบนหน้าจอทีวี อย่างไรก็ตาม เนื่องจากแอนิเมชัน มันสร้างภาระต่อการเข้าถึงและอาจรบกวนผู้ใช้ จึงใช้อย่าง selective

จากการวิจัยของ Nielsen Norman Group (2023) ข้อความที่เคลื่อนที่ ลดความเร็วในการอ่านลง 15-20% และเพิ่มภาระทางปัญญา ใช้ marquee เฉพาะเมื่อการตัดแบบคงที่ (ellipsize) ไม่สามารถยอมรับได้จริง ๆ

ข้อความวิ่งบน Android ผ่าน TextView

บน Android marquee ถูกใช้งานผ่านกลไกในตัวของ TextView ด้วยแอตทริบิวต์ android:ellipsize="marquee" นี่เป็นโหมด ellipsize เดียวที่ไม่ตัดข้อความแต่ทำให้การเลื่อนเป็นแอนิเมชัน

ในการเปิดใช้ marquee ต้องปฏิบัติตามเงื่อนไขสามประการ: singleLine="true" (หรือ maxLines="1"), ellipsize="marquee" และ setSelected(true) ในโค้ด หากไม่เรียก setSelected() แอนิเมชันจะไม่เริ่ม

xml
<!-- 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

iOS ไม่มีสิ่งที่เทียบเท่าในตัวสำหรับ android:ellipsize="marquee" marquee ถูกใช้งานด้วยตนเองผ่าน Core Animation หรือแอนิเมชัน SwiftUI วิธีที่พบบ่อยที่สุดคือการทำแอนิเมชัน layer.position ร่วมกับคอนเทนเนอร์ตัดขอบ

ใน UIKit marquee สร้างจากสามองค์ประกอบ: คอนเทนเนอร์ UILabel ที่มี clipsToBounds = true, ข้อความ และ CABasicAnimation สำหรับเลื่อนข้อความตามแกน X ด้วยการทำซ้ำไม่จำกัดและ autoreverses = false เพื่อให้บรรทัดเลื่อนในทิศทางเดียวแล้วกระโดดกลับ

swift
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 แอนิเมชันจะดูเฉื่อย ปรับความเร็วตามสถานการณ์เฉพาะของคุณ

ใน SwiftUI marquee ใช้งานได้ง่ายกว่า — ผ่านตัวปรับแต่ง offset() ด้วยแอนิเมชันที่ทำซ้ำได้และ .animation(.linear(duration:).repeatForever(autoreverses: false)) SwiftUI จัดการวงจรชีวิตแอนิเมชันโดยอัตโนมัติแต่ต้องใช้ GeometryReader เพื่อกำหนดความกว้างของข้อความ

CSS แอนิเมชันแทนแท็ก marquee

บนเว็บ แท็ก <marquee> ที่เลิกใช้แล้ว ไม่แนะนำ ให้ใช้ แม้ว่าเบราว์เซอร์จะยังคงรองรับ การใช้งาน marquee สมัยใหม่คือ CSS แอนิเมชันด้วย keyframes และ transform: translateX

ข้อดีของวิธี CSS: ควบคุมความเร็ว การหยุดชั่วคราว และเส้นโค้งแอนิเมชันได้อย่างสมบูรณ์ รองรับ prefers-reduced-motion สำหรับการเข้าถึง และไม่มีแท็กที่ไม่ถูกต้องทางความหมาย

css
.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%) จะเท่ากับความกว้างของคอนเทนเนอร์ ไม่ใช่ความกว้างของข้อความ และแอนิเมชันจะเสีย

สำหรับการเข้าถึง เพิ่ม @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } } — ผู้ใช้ที่มีความผิดปกติของ vestibular จะเห็นข้อความคงที่พร้อมความสามารถในการเลื่อน

เมื่อใดควรใช้และเมื่อใดควรหลีกเลี่ยง

marquee สมเหตุสมผล ในสถานการณ์ที่ผู้ใช้ไม่สามารถขยายคอนเทนเนอร์ได้ด้วยตนเองและข้อความเต็มมีความสำคัญมาก: เพลงปัจจุบันในเครื่องเล่นบนหน้าจอล็อก ข่าววิ่งบนแผงทีวี หรือ marquee ในแอปเรียกแท็กซี่ (สถานะคำสั่งซื้อ)

marquee ไม่ควรใช้ ในตาราง รายการ ฟอร์ม และไดอะล็อก — ทุกที่ที่ผู้ใช้โต้ตอบกับอินเทอร์เฟซอย่างแข็งขัน แอนิเมชันเบี่ยงเบนความสนใจจากเป้าหมาย เพิ่มเวลาในการค้นหาองค์ประกอบ และสร้างความไม่สบาย ตาม WebAIM (2024) 1 ใน 3 ผู้ใช้ที่มีความผิดปกติของ vestibular รายงานความไม่สบายเมื่อดูข้อความเคลื่อนไหว

กฎทั่วไป: หากสามารถย่อข้อความหรือตัดไปบรรทัดถัดไปได้ — ทำเลย marquee เป็นทางเลือกสุดท้าย ไม่ใช่ทางออกสากลสำหรับป้ายข้อความยาว

การเข้าถึง: แอนิเมชันและการลดการเคลื่อนไหว

marquee เป็นหนึ่งในแอนิเมชัน UI ที่ มีปัญหามากที่สุด จากมุมมองการเข้าถึง ข้อความเคลื่อนที่อาจทำให้เกิดอาการวิงเวียน คลื่นไส้ และสับสนในผู้ที่มีความผิดปกติของ vestibular ตาม WCAG 2.2 แอนิเมชันใด ๆ ที่นานกว่า 5 วินาทีต้องมีกลไกหยุดชั่วคราวหรือปิด

บน iOS ใช้คุณสมบัติ UIAccessibility.isReduceMotionEnabled เพื่อตรวจสอบการตั้งค่าระบบ หากเปิด reduceMotion ไว้ — อย่าเริ่มแอนิเมชัน แสดงข้อความคงที่ด้วย ellipsize

บน Android ตรวจสอบแอนิเมชันของระบบผ่าน Settings.Global.ANIMATOR_DURATION_SCALE หากแอนิเมชันทั่วโลกถูกปิด (duration scale = 0) ไม่ควรเปิดใช้ marquee

บนเว็บ ใช้มีเดียฟีเจอร์ prefers-reduced-motion: @media (prefers-reduced-motion: reduce) { .marquee-content { animation: none; } } นี่คือคุณสมบัติ CSS มาตรฐานที่รองรับโดยเบราว์เซอร์สมัยใหม่ทั้งหมด ตรวจสอบใน DevTools: เปิดการจำลอง prefers-reduced-motion และยืนยันว่าแอนิเมชันปิด

คำถามที่พบบ่อย

Marquee แตกต่างจาก Ellipsize อย่างไร?

Ellipsize ตัดข้อความและเพิ่มจุดไข่ปลา ซ่อนเนื้อหาส่วนหนึ่ง Marquee เลื่อนข้อความทั้งหมดแต่เคลื่อนที่ การเลือกขึ้นอยู่กับลำดับความสำคัญ: แสดงทุกอย่าง (marquee) หรือคงการแสดงผลแบบคงที่ (ellipsize)

จะเพิ่มหรือลดความเร็ว marquee บน Android ได้อย่างไร?

android:ellipsize="marquee" ในตัวไม่อนุญาตให้เปลี่ยนความเร็ว — กำหนดโดย ScrollSpeed ของระบบ สำหรับความเร็วแบบกำหนดเอง ใช้ Animation หรือ ValueAnimator ด้วยตนเอง

ทำไม marquee ไม่ทำงานใน RecyclerView?

เนื่องจากการนำ view กลับมาใช้ใหม่ setSelected(true) ถูกรีเซ็ต วิธีแก้: สร้าง MarqueeTextView แบบกำหนดเองที่ลบล้าง isFocused() และคืนค่า true หรือใช้แอนิเมชันแบบกำหนดเองใน Adapter.onViewAttachedToWindow()

สามารถทำ marquee แนวตั้งได้หรือไม่?

ได้ โดยใช้ translateY แทน translateX marquee แนวตั้งใช้สำหรับเครดิตหรือข่าววิ่งในฟีดแนวตั้ง หลักการเหมือนกันแต่แกนแอนิเมชันเปลี่ยนไป

จะหยุด marquee เมื่อโฟกัสได้อย่างไร?

ใน CSS: :hover { animation-play-state: paused; } บน Android: ติดตามโฟกัสผ่าน OnFocusChangeListener และรีเซ็ต setSelected(false) บน iOS: ลบแอนิเมชันผ่าน layer.removeAnimation(forKey:)

สรุป

  • ข้อความวิ่ง — การเลื่อนแนวนอนของข้อความ แสดงเนื้อหาทั้งหมดผ่านแอนิเมชัน
  • บน Android android:ellipsize="marquee" ในตัวต้องการ setSelected(true) และ singleLine="true"
  • บน iOS การใช้งานผ่าน CABasicAnimation ด้วยคีย์พาธ position.x
  • บนเว็บ แท็ก <marquee> เลิกใช้แล้ว ใช้ CSS @keyframes ด้วย translateX
  • ใช้ marquee เท่านั้น เมื่อ ellipsize ไม่สามารถยอมรับได้ — ในเครื่องเล่น ข่าววิ่ง แถบสถานะ
  • อย่าลืมปิดแอนิเมชันสำหรับ prefers-reduced-motion บนทุกแพลตฟอร์ม
  • ความเร็วแอนิเมชัน: ~60 px/s — สบายในการอ่าน เกิน 120 px/s — อ่านไม่ได้

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม