মার্কি (marquee) একটি ভিজুয়াল ইফেক্ট যেখানে টেক্সট স্বয়ংক্রিয়ভাবে একটি সীমিত এলাকার মধ্যে অনুভূমিকভাবে স্ক্রল হয়, যা একটি কম্প্যাক্ট কন্টেইনারে লম্বা স্ট্রিং প্রদর্শনের অনুমতি দেয়। ঐতিহাসিকভাবে, শব্দটি HTML ট্যাগ <marquee> থেকে এসেছে, যা 1990-এর দশকে ওয়েবে জনপ্রিয় ছিল। আধুনিক মোবাইল ডেভেলপমেন্টে, scrolling text প্লেয়ার, নিউজ টিকার এবং স্ট্যাটাস বারে ব্যবহৃত হয়। Apple Human Interface Guidelines এবং Google Material Design অনুসারে, মার্কি শুধুমাত্র সীমিত দৃশ্যপটে গ্রহণযোগ্য যেখানে এলিপসিস সহ স্ট্যাটিক ট্রাঙ্কেশন UX-কে খারাপ করে।
মূল পয়েন্ট
মার্কি একটি UI ইফেক্ট যেখানে টেক্সট একটি নির্দিষ্ট এলাকার মধ্যে অনুভূমিকভাবে চলে, চক্রাকারে স্ক্রল করে যাতে ব্যবহারকারী কন্টেইনারে না ফিট হওয়া সমস্ত বিষয়বস্তু পড়তে পারে। ellipsize-এর বিপরীতে, যা টেক্সটের অংশ লুকিয়ে রাখে, marquee সম্পূর্ণ টেক্সট দেখায়, কিন্তু গতিতে।
মার্কি শব্দটি HTML ট্যাগ <marquee>-এর কারণে প্রতিষ্ঠিত হয়েছে, যা Netscape এবং Internet Explorer ব্রাউজারের প্রাথমিক সংস্করণে যুক্ত করা হয়েছিল। ট্যাগটি JavaScript বা CSS ছাড়াই স্ক্রলিং টেক্সট তৈরি করার অনুমতি দিত, কিন্তু কখনও HTML স্ট্যান্ডার্ডের অংশ ছিল না। আধুনিক ব্রাউজারগুলি পশ্চাদগামী সামঞ্জস্যের জন্য এটি সমর্থন করতে থাকে, তবে এর ব্যবহার সুপারিশ করা হয় না।
মোবাইল ডেভেলপমেন্টে, marquee কম্প্যাক্ট কন্টেইনারে লম্বা লেবেলের সমস্যা সমাধান করে: প্লেয়ারে ট্র্যাকের নাম, মুদ্রা টিকার, টিভি পর্দায় নিউজ টিকার। তবে অ্যানিমেশনের কারণে, এটি অ্যাক্সেসিবিলিটির উপর বোঝা তৈরি করে এবং ব্যবহারকারীদের বিরক্ত করতে পারে — তাই এটি নির্বাচিতভাবে ব্যবহৃত হয়।
Nielsen Norman Group (2023) গবেষণা অনুসারে, চলমান টেক্সট পড়ার গতি 15-20% কমিয়ে দেয় এবং জ্ঞানীয় ভার বাড়ায়। শুধুমাত্র তখনই মার্কি ব্যবহার করুন যখন স্ট্যাটিক ট্রাঙ্কেশন (ellipsize) সত্যিই অগ্রহণযোগ্য।
Android-এ, মার্কি TextView-এর অন্তর্নির্মিত ব্যবস্থার মাধ্যমে android:ellipsize="marquee" অ্যাট্রিবিউটের মাধ্যমে বাস্তবায়িত হয়। এটি একমাত্র ellipsize মোড যা টেক্সট ট্রাঙ্কেট করে না বরং এর স্ক্রলিং অ্যানিমেট করে।
মার্কি সক্রিয় করতে তিনটি শর্ত পূরণ করতে হবে: 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 ফোকাসে থাকে বা নির্বাচিত মোডে থাকে। RecyclerView-এ, এটি সমস্যা সৃষ্টি করতে পারে — অ্যানিমেশন শুধুমাত্র একটি আইটেমের জন্য শুরু হয়। সমাধান: একটি কাস্টম MarqueeTextView তৈরি করুন যা isFocused() ওভাররাইড করে এবং সবসময় true রিটার্ন করে।
Android 9 (API 28) থেকে শুরু করে, Google marquee আচরণ উন্নত করেছে — অ্যানিমেশন মসৃণ হয়েছে এবং তালিকা স্ক্রল করার সময় জমে যায় না। তবে পুরানো ডিভাইসে, marquee ঝাঁকুনি দিতে পারে, বিশেষ করে উচ্চ পিক্সেল ঘনত্বে।
iOS-এ android:ellipsize="marquee"-এর কোনো অন্তর্নির্মিত সমতুল্য নেই। মার্কি ম্যানুয়ালি Core Animation বা SwiftUI অ্যানিমেশনের মাধ্যমে বাস্তবায়িত হয়। সবচেয়ে সাধারণ পদ্ধতি হল একটি ক্লিপিং কন্টেইনারের সাথে layer.position অ্যানিমেট করা।
UIKit-এ, মার্কি তিনটি উপাদানের উপর নির্মিত: clipsToBounds = true সহ UILabel কন্টেইনার, টেক্সট নিজেই, এবং X বরাবর টেক্সট সরানোর জন্য অসীম পুনরাবৃত্তি এবং autoreverses = false সহ CABasicAnimation, যাতে লাইন এক দিকে স্ক্রল হয় এবং ফিরে লাফায়।
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-তে, মার্কি আরও সহজে বাস্তবায়িত হয় — পুনরাবৃত্তিযোগ্য অ্যানিমেশন সহ offset() মডিফায়ার এবং .animation(.linear(duration:).repeatForever(autoreverses: false))-এর মাধ্যমে। SwiftUI স্বয়ংক্রিয়ভাবে অ্যানিমেশন জীবনচক্র পরিচালনা করে তবে টেক্সটের প্রস্থ নির্ধারণের জন্য GeometryReader প্রয়োজন।
ওয়েবে, অপ্রচলিত <marquee> ট্যাগ সুপারিশ করা হয় না, যদিও ব্রাউজারগুলি এটি সমর্থন করতে থাকে। মার্কির আধুনিক বাস্তবায়ন হল keyframes এবং transform: translateX সহ CSS অ্যানিমেশন।
CSS পদ্ধতির সুবিধা: গতি, বিরতি এবং অ্যানিমেশন কার্ভের উপর সম্পূর্ণ নিয়ন্ত্রণ, অ্যাক্সেসিবিলিটির জন্য prefers-reduced-motion-এর সমর্থন, এবং শব্দার্থগতভাবে ভুল ট্যাগের অনুপস্থিতি।
.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; } } যোগ করুন — ভেস্টিবুলার ব্যাধিযুক্ত ব্যবহারকারীরা স্ক্রলিং ক্ষমতা সহ স্ট্যাটিক টেক্সট দেখবেন।
মার্কি সেই দৃশ্যপটে ন্যায্য যেখানে ব্যবহারকারী নিজে কন্টেইনার প্রসারিত করতে পারে না এবং সম্পূর্ণ টেক্সট গুরুত্বপূর্ণ: লক স্ক্রিনে প্লেয়ারে বর্তমান ট্র্যাক, টিভি প্যানেলে নিউজ টিকার, রাইড-হেলিং অ্যাপে মার্কি (অর্ডারের অবস্থা)।
মার্কি ব্যবহার করা উচিত নয় টেবিল, তালিকা, ফর্ম এবং ডায়ালগে — যেখানেই ব্যবহারকারী সক্রিয়ভাবে ইন্টারফেসের সাথে যোগাযোগ করে। অ্যানিমেশন লক্ষ্য থেকে বিভ্রান্ত করে, উপাদান অনুসন্ধানের সময় বাড়ায় এবং অস্বস্তি তৈরি করে। WebAIM (2024) অনুসারে, ভেস্টিবুলার ব্যাধিযুক্ত 3 জন ব্যবহারিকের মধ্যে 1 জন অ্যানিমেটেড টেক্সট দেখার সময় অস্বস্তি জানান।
একটি সাধারণ নিয়ম: যদি টেক্সট ছোট করা যায় বা পরবর্তী লাইনে মোড়ানো যায় — তবে করুন। মার্কি শেষ উপায়, লম্বা লেবেলের জন্য সার্বজনীন সমাধান নয়।
মার্কি অ্যাক্সেসিবিলিটির দৃষ্টিকোণ থেকে সবচেয়ে সমস্যাজনক UI অ্যানিমেশনগুলির মধ্যে একটি। চলমান টেক্সট ভেস্টিবুলার ব্যাধিযুক্ত ব্যক্তিদের মধ্যে মাথা ঘোরা, বমি বমি ভাব এবং বিভ্রান্তি সৃষ্টি করতে পারে। 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 ইমুলেশন সক্ষম করুন এবং নিশ্চিত করুন যে অ্যানিমেশন বন্ধ হয়ে যায়।
সচরাচর জিজ্ঞাসিত প্রশ্ন
Ellipsize টেক্সট কেটে ফেলে এবং এলিপসিস যোগ করে, বিষয়বস্তুর অংশ লুকিয়ে রাখে। Marquee সম্পূর্ণ টেক্সট স্ক্রল করে, কিন্তু গতিতে। পছন্দ অগ্রাধিকারের উপর নির্ভর করে: সবকিছু দেখানো (marquee) বা স্ট্যাটিক প্রদর্শন বজায় রাখা (ellipsize)।
অন্তর্নির্মিত android:ellipsize="marquee" গতি পরিবর্তনের অনুমতি দেয় না — এটি সিস্টেম ScrollSpeed দ্বারা নির্ধারিত হয়। কাস্টম গতির জন্য, ম্যানুয়ালি Animation বা ValueAnimator ব্যবহার করুন।
ভিউ রিসাইক্লিংয়ের কারণে, setSelected(true) রিসেট হয়ে যায়। সমাধান: একটি কাস্টম MarqueeTextView তৈরি করুন যা isFocused() ওভাররাইড করে এবং true রিটার্ন করে, অথবা Adapter.onViewAttachedToWindow()-এ কাস্টম অ্যানিমেশন ব্যবহার করুন।
হ্যাঁ, translateX-এর পরিবর্তে translateY ব্যবহার করে। উল্লম্ব মার্কি ক্রেডিট বা উল্লম্ব ফিডে নিউজ টিকারের জন্য ব্যবহৃত হয়। নীতি একই, কিন্তু অ্যানিমেশন অক্ষ পরিবর্তিত হয়।
CSS-এ: :hover { animation-play-state: paused; }। Android-এ: OnFocusChangeListener-এর মাধ্যমে ফোকাস ট্র্যাক করুন এবং setSelected(false) রিসেট করুন। iOS-এ: layer.removeAnimation(forKey:) এর মাধ্যমে অ্যানিমেশন সরান।
সারসংক্ষেপ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন