マーキー(marquee)は、テキストが制限された領域内で自動的に水平スクロールする視覚効果であり、長い文字列をコンパクトなコンテナに表示できます。歴史的には、この用語は1990年代にウェブで人気のあったHTMLタグ <marquee> に由来します。現代のモバイル開発では、scrolling textはプレーヤー、ニュースティッカー、ステータスバーで使用されています。Apple Human Interface GuidelinesとGoogle Material Designによると、マーキーは省略記号による静的な切り捨てがUXを損なう限られたシナリオでのみ許容されます。
重要なポイント
マーキーは、テキストが固定領域内を水平に移動し、コンテナに収まらないすべてのコンテンツをユーザーが読めるように循環スクロールするUI効果です。テキストの一部を隠すellipsizeとは異なり、marqueeはテキスト全体を動的に表示します。
マーキーという用語は、NetscapeおよびInternet Explorerブラウザの初期バージョンで追加されたHTMLタグ <marquee> により定着しました。このタグはJavaScriptやCSSなしでスクロールテキストを作成できましたが、HTML標準の一部になることはありませんでした。最新のブラウザは後方互換性のためにサポートを続けていますが、その使用は推奨されていません。
モバイル開発では、マーキーはコンパクトなコンテナ内の長いラベルの問題を解決します:プレーヤーのトラック名、為替ティッカー、TV画面のニュースティッカーなどです。ただし、アニメーションのためにアクセシビリティに負荷をかけ、ユーザーを悩ませる可能性があるため、選択的に使用されます。
Nielsen Norman Groupの調査(2023年)によると、動くテキストは読書速度を15〜20%低下させ、認知的負荷を増加させます。静的な切り捨て(ellipsize)が本当に受け入れられない場合にのみマーキーを使用してください。
Androidでは、マーキーは TextView の組み込みメカニズムにより、android:ellipsize="marquee" 属性を通じて実装されます。これはテキストを切り捨てずにスクロールをアニメーション化する唯一のellipsizeモードです。
マーキーを有効にするには、singleLine="true"(またはmaxLines="1")、ellipsize="marquee"、コード内でsetSelected(true)の3つの条件を満たす必要があります。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では、これが問題を引き起こす可能性があります—アニメーションは1つのアイテムに対してのみ開始されます。解決策は、isFocused()をオーバーライドして常にtrueを返すカスタムMarqueeTextViewを作成することです。
Android 9(API 28)以降、Googleはマーキーの動作を改善しました—アニメーションはよりスムーズになり、リストのスクロール時にフリーズしなくなりました。ただし、古いデバイスでは、特に高いピクセル密度でマーキーが震えることがあります。
iOSにはandroid:ellipsize="marquee"の組み込み相当機能はありません。マーキーは Core Animation またはSwiftUIアニメーションを使用して手動で実装されます。最も一般的なアプローチは、クリッピングコンテナと組み合わせてlayer.positionをアニメーション化することです。
UIKitでは、マーキーは3つの要素で構築されます: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> タグは 推奨されていません が、ブラウザはサポートを続けています。マーキーの現代的な実装は、キーフレームと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; } } を追加してください—前庭障害を持つユーザーはスクロール機能付きの静的テキストを表示します。
マーキーは、ユーザーが自分でコンテナを拡張できず、完全なテキストが極めて重要なシナリオで 正当化されます:ロック画面のプレーヤーでの現在のトラック、TVパネルのニュースティッカー、ライドヘイリングアプリのマーキー(注文ステータス)。
マーキーはテーブル、リスト、フォーム、ダイアログでは 使用すべきではありません—ユーザーがインターフェースと積極的にやり取りするすべての場所です。アニメーションは目的から気をそらし、要素の検索時間を増やし、不快感を生み出します。WebAIM(2024年)によると、前庭障害を持つユーザーの3人に1人がアニメーションテキストを見たときに不快感を報告しています。
経験則:テキストを短縮したり次の行に折り返したりできるなら、そうしてください。マーキーは最後の手段であり、長いラベルのための普遍的な解決策ではありません。
マーキーはアクセシビリティの観点から最も 問題のある UIアニメーションの1つです。動くテキストは、前庭障害を持つ人々にめまい、吐き気、見当識障害を引き起こす可能性があります。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) がリセットされます。解決策:isFocused()をオーバーライドして常にtrueを返すカスタムMarqueeTextViewを作成するか、Adapter.onViewAttachedToWindow()でカスタムアニメーションを使用します。
はい、translateXの代わりに translateY を使用します。垂直マーキーはクレジットや垂直フィードのニュースティッカーに使用されます。原理は同じですが、アニメーションの軸が変わります。
CSSの場合::hover { animation-play-state: paused; }。Androidの場合:OnFocusChangeListenerでフォーカスを追跡し、setSelected(false)をリセットします。iOSの場合:layer.removeAnimation(forKey:)でアニメーションを削除します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。