Ticker UI 디자인: UI/UX/AX의 원칙과 실전
“움직이는 정보는 왜 읽기 어려울까?”
주식 티커를 만들면서 고민이 생겼습니다. 데스크톱에서는 완벽하게 보이는데, 과연 사용자들이 제대로 읽을 수 있을까?
UX 연구 자료를 찾아보니 예상대로였습니다. Nielsen Norman Group의 연구에 따르면, 움직이는 콘텐츠는 사용자의 주의를 99%로 끌어당기지만, 정작 사용자는 그 내용을 제대로 읽지 못하는 경우가 많습니다.
더 큰 문제는 접근성이었습니다. WCAG 커뮤니티와 스크린 리더 사용자들의 피드백을 보니:
“티커가 너무 빨라서 읽기 전에 지나가버립니다.”
“멈출 수 없어서 답답합니다.”
“스크린 리더가 계속 주식 정보를 읽어서 다른 내용을 들을 수 없습니다.”
그리고 최근에는 새로운 고민이 하나 더 생겼습니다. AI가 웹을 크롤링할 때 티커를 제대로 이해할 수 있을까? LLM이나 검색 엔진은 끊임없이 변하는 티커를 어떻게 처리할까요?
아름답게 디자인된 티커가 오히려 사용자 경험, 접근성, 그리고 AI 경험을 모두 해칠 수 있습니다.
이 글에서는 Ticker UI를 UI(시각 디자인), UX(사용자 경험), AX(접근성 + AI UX)의 관점에서 근본적으로 이해하고, 올바르게 디자인하는 방법을 알아봅니다.
원론: Ticker UI는 왜 존재하는가?
물리적 제약의 산물
Ticker UI는 원래 물리적 공간의 제약 때문에 탄생했습니다.
전광판의 문제:
┌─────────────────────────────┐
│ 제한된 공간 (가로 3m) │
│ │
│ 보여줄 정보: │
│ - 100개 주식 정보 │
│ - 환율 정보 │
│ - 뉴스 헤드라인 │
└─────────────────────────────┘
해결책:
정보를 "스크롤"시켜서 제한된 공간에
더 많은 정보를 보여주자!
뉴욕 증권거래소의 티커 테이프(1867년)가 시작이었습니다. 전신기로 전송된 주식 정보를 좁은 종이 테이프에 인쇄하며 계속 흘러가는 형태였죠.
웹에서의 재발견
그런데 웹에는 공간 제약이 거의 없습니다. 스크롤할 수 있고, 페이지를 나눌 수 있고, 아코디언을 쓸 수 있습니다.
그럼에도 Ticker UI가 웹에서 사용되는 이유는 무엇일까요?
1. 시각적 계층(Visual Hierarchy):
┌──────────────────────────────────┐
│ [메인 콘텐츠] │ ← 사용자의 주요 관심사
│ │
│ 큰 제목과 중요한 내용 │
│ │
├──────────────────────────────────┤
│ ← 코스피 2,500 | 달러 1,430 → │ ← 보조 정보 (주변 시야)
└──────────────────────────────────┘
티커는 “중요하지만 주의를 끌면 안 되는” 정보를 전달합니다. 메인 콘텐츠를 방해하지 않으면서도, 사용자의 주변 시야(peripheral vision)에 정보를 제공합니다.
2. 실시간성 표현:
움직임은 “지금 이 순간”을 시각적으로 전달합니다. 정적인 숫자보다 스크롤되는 숫자가 “살아있는 데이터”라는 느낌을 줍니다.
3. 공간 효율:
좁은 헤더나 푸터에 여러 정보를 담을 수 있습니다.
❌ 정적 표시 (공간 부족):
코스피 2,500 | 달러 1,430 | 유로 1,520 | 금 2,100 | [잘림...]
✅ 티커 (모든 정보 표시):
← 코스피 2,500 | 달러 1,430 | 유로 1,520 | 금 2,100 | 비트코인 88,000 →
Ticker UI의 4가지 관점: UI, UX, AX (Human + AI)
Ticker를 제대로 디자인하려면 네 가지 관점을 모두 고려해야 합니다.
1. UI (User Interface): 시각 디자인
“어떻게 보일 것인가?”
가독성(Readability)
움직이는 텍스트는 정적인 텍스트보다 읽기 어렵습니다.
/* ❌ 잘못된 예: 작고 빠른 티커 */
.ticker {
font-size: 12px; /* 너무 작음 */
animation: scroll 10s; /* 너무 빠름 */
font-weight: 300; /* 얇은 폰트 */
}
/* ✅ 올바른 예: 크고 느린 티커 */
.ticker {
font-size: 16px; /* 충분히 큼 */
animation: scroll 40s; /* 여유 있는 속도 */
font-weight: 500; /* 적절한 두께 */
letter-spacing: 0.02em; /* 자간 확보 */
}
권장 사항:
- 최소 폰트 크기: 16px (모바일), 14px (데스크톱)
- 속도: 50-100px/초 (1초에 약 3-5글자)
- 명암 대비: 4.5:1 이상 (WCAG AA)
- 폰트: 산세리프, Medium(500) 이상
시각적 계층
티커는 보조 정보이므로, 시각적으로 덜 두드러져야 합니다.
시각적 무게 (Visual Weight):
높음 ████████ 메인 헤딩 (h1, 32px, Bold)
██████ 부제목 (h2, 24px, Semibold)
████ 본문 (16px, Regular)
낮음 ██ 티커 (14px, Medium, 회색 배경)
Good Practice:
<header>
<h1 class="main-title">주식 시장 분석</h1>
<p class="subtitle">2025년 10월 29일</p>
<!-- 티커는 시각적으로 덜 두드러지게 -->
<div class="ticker" role="marquee" aria-label="실시간 주식 정보">
<div class="ticker-content">
코스피 2,500.22 | 달러 1,430.80 | 유로 1,668.03
</div>
</div>
</header>
<style>
.main-title {
font-size: 32px;
font-weight: 700;
color: #000;
}
.ticker {
font-size: 14px;
font-weight: 500;
color: #555; /* 덜 두드러지는 색상 */
background: #f5f5f5; /* 부드러운 배경 */
padding: 8px 0;
}
</style>
색상과 대비
WCAG 2.1 SC 1.4.3 (Contrast Minimum):
- 일반 텍스트: 4.5:1 이상
- 큰 텍스트 (18px+): 3:1 이상
❌ 나쁜 예: 색상만으로 정보 전달
<span style="color: red;">-2.5%</span>
<span style="color: green;">+3.2%</span>
✅ 좋은 예: 아이콘 + 색상 + aria-label
<span class="change negative" aria-label="하락 2.5%">
<svg class="icon-down" aria-hidden="true">▼</svg>
<span>-2.5%</span>
</span>
<span class="change positive" aria-label="상승 3.2%">
<svg class="icon-up" aria-hidden="true">▲</svg>
<span>+3.2%</span>
</span>
<style>
.change {
font-weight: 600;
}
.negative {
color: #c62828; /* 4.5:1 대비 */
}
.positive {
color: #2e7d32; /* 4.5:1 대비 */
}
.icon-down,
.icon-up {
display: inline-block;
width: 12px;
margin-right: 4px;
}
</style>
2. UX (User Experience): 사용자 경험
“사용자는 어떻게 느낄 것인가?”
인지 부하(Cognitive Load)
움직이는 콘텐츠는 자동으로 주의를 끕니다. 이것은 좋은 것일 수도, 나쁜 것일 수도 있습니다.
인간의 시각 시스템:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━
[움직임 감지]
↓
[무의식적 주의 전환] ← 의지와 무관!
↓
[집중 방해]
Jakob Nielsen의 연구 (2010):
“움직이는 콘텐츠는 사용자의 주의를 99%로 끌어당깁니다. 사용자는 움직임을 무시하려 해도 무시할 수 없습니다.”
해결책: 신중한 배치
✅ 좋은 위치: 주변 시야
┌───────────────────────────────────┐
│ ← Ticker (상단) │ ← OK: 주변 시야
├───────────────────────────────────┤
│ │
│ [메인 콘텐츠] │ ← 사용자 집중
│ │
│ │
├───────────────────────────────────┤
│ ← Ticker (하단) │ ← OK: 주변 시야
└───────────────────────────────────┘
❌ 나쁜 위치: 중앙 또는 메인 콘텐츠 옆
┌───────────────────────────────────┐
│ [메인 콘텐츠] │
│ │
├───────────────────────────────────┤
│ ← Ticker (중앙) │ ← ❌ 집중 방해!
├───────────────────────────────────┤
│ [메인 콘텐츠 계속] │
└───────────────────────────────────┘
읽기 속도 vs 스크롤 속도
문제:
- 빠른 독자: 티커를 다 읽고 다음을 기다림 (지루함)
- 느린 독자: 티커가 읽기 전에 지나감 (좌절감)
Nielsen Norman Group 연구: 평균 읽기 속도는 200-250 단어/분 = 약 3-4 단어/초
// ❌ 너무 빠른 티커
const tickerSpeed = 150; // 150px/sec
// "코스피 2,500.22" (약 70px)가 0.5초 만에 지나감
// → 읽을 시간 없음!
// ✅ 적절한 속도
const tickerSpeed = 50; // 50px/sec
// "코스피 2,500.22" (약 70px)가 1.4초 동안 보임
// → 읽을 시간 충분
Best Practice: 사용자 제어권 제공
<div class="ticker-container">
<!-- 일시 정지 버튼 -->
<button
id="ticker-pause"
aria-label="티커 일시 정지"
aria-pressed="false"
>
⏸
</button>
<div
class="ticker"
role="marquee"
aria-label="실시간 주식 정보"
>
<div class="ticker-content">
코스피 2,500.22 | 달러 1,430.80
</div>
</div>
</div>
<style>
/* 호버 시 자동 일시 정지 */
.ticker-container:hover .ticker-content,
.ticker-container:focus-within .ticker-content {
animation-play-state: paused;
}
.ticker-content {
animation: scroll 40s linear infinite;
}
@keyframes scroll {
from { transform: translateX(100%); }
to { transform: translateX(-100%); }
}
</style>
<script>
const pauseBtn = document.getElementById('ticker-pause');
const ticker = document.querySelector('.ticker-content');
pauseBtn.addEventListener('click', () => {
const isPaused = pauseBtn.getAttribute('aria-pressed') === 'true';
if (isPaused) {
ticker.style.animationPlayState = 'running';
pauseBtn.setAttribute('aria-pressed', 'false');
pauseBtn.textContent = '⏸';
} else {
ticker.style.animationPlayState = 'paused';
pauseBtn.setAttribute('aria-pressed', 'true');
pauseBtn.textContent = '▶';
}
});
</script>
정보의 완전성
문제:
사용자가 티커를 보기 시작한 시점:
↓
← 달러 1,430 | 유로 1,668 | 금 2,100 | 코스피 2,500 →
↑
사용자는 처음을 놓침!
해결책 1: 무한 반복
// 티커 콘텐츠를 복제하여 끊김 없이 반복
const tickerContent = document.querySelector('.ticker-content');
const clone = tickerContent.cloneNode(true);
clone.setAttribute('aria-hidden', 'true'); // 중복 읽기 방지
tickerContent.parentNode.appendChild(clone);
해결책 2: 대체 정보 제공
<!-- 티커 외에 완전한 정보를 제공 -->
<div class="ticker-wrapper">
<div class="ticker" role="marquee" aria-label="실시간 주식 정보">
<!-- 스크롤되는 티커 -->
</div>
<!-- 전체 정보를 볼 수 있는 링크 -->
<a href="/markets/live" class="view-all">
전체 시장 정보 보기
</a>
</div>
3. AX (Accessibility): 접근성
“모든 사용자가 접근할 수 있는가?”
WCAG 2.2.2: Pause, Stop, Hide (Level A)
공식 요구사항:
움직이거나, 깜박이거나, 스크롤되는 정보가:
- 자동으로 시작되고
- 5초 이상 지속되며
- 다른 콘텐츠와 함께 표시되면
→ 사용자가 일시 정지, 중지, 숨길 수 있어야 합니다.
예외: 움직임이 필수적인(essential) 활동인 경우만 제외
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
❌ WCAG 위반: 제어 불가능한 티커
<div class="ticker">
<div class="auto-scroll">
<!-- 계속 스크롤, 멈출 수 없음 -->
</div>
</div>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ WCAG 준수: 호버 시 일시 정지
<div class="ticker">
<div class="auto-scroll">
<!-- 호버/포커스 시 자동 일시 정지 -->
</div>
</div>
<style>
.ticker:hover .auto-scroll,
.ticker:focus-within .auto-scroll {
animation-play-state: paused;
}
</style>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ WCAG 준수: 명시적 제어 버튼
<button aria-label="티커 일시 정지" aria-pressed="false">
⏸
</button>
스크린 리더 고려사항
role="marquee"의 기본 동작:
aria-live="off"(기본값)- 스크린 리더가 자동으로 읽지 않음
<!-- ✅ 올바른 사용: 비필수 정보 -->
<div
role="marquee"
aria-label="실시간 주식 정보"
aria-live="off"
>
<!-- 티커 콘텐츠 -->
</div>
스크린 리더 동작:
1. 사용자가 요소에 도달하면: "실시간 주식 정보 영역"
2. 자동 업데이트: 읽지 않음 (조용함)
3. 사용자가 원하면: 탐색하여 읽을 수 있음
중요한 정보는 marquee 사용 금지:
❌ 잘못된 예: 중요한 알림에 marquee
<div role="marquee" aria-label="시스템 알림">
서버 점검이 10분 후 시작됩니다
</div>
→ 스크린 리더가 읽지 않아 사용자가 놓칠 수 있음!
✅ 올바른 예: alert 사용
<div role="alert" aria-live="assertive">
서버 점검이 10분 후 시작됩니다
</div>
→ 즉시 읽어줌
prefers-reduced-motion 지원
WCAG 2.3.3 (Level AAA): Animation from Interactions
일부 사용자는 모션으로 인해 현기증, 메스꺼움, 두통을 경험합니다.
/* 기본: 애니메이션 있음 */
.ticker-content {
animation: scroll 40s linear infinite;
}
/* 사용자가 모션 감소를 선호하면: 애니메이션 없음 */
@media (prefers-reduced-motion: reduce) {
.ticker-content {
animation: none !important;
/* 대신 스크롤 가능하게 */
overflow-x: auto;
scroll-behavior: smooth;
}
}
테스트 방법:
# macOS
System Settings > Accessibility > Display > Reduce motion
# Windows
Settings > Ease of Access > Display > Show animations
# CSS에서 확인
@media (prefers-reduced-motion: reduce) {
/* 이 블록이 적용되는지 확인 */
}
키보드 접근성
<!-- ❌ 키보드로 접근 불가 -->
<div class="ticker">
<div class="ticker-item">코스피 2,500</div>
<div class="ticker-item">달러 1,430</div>
</div>
<!-- ✅ 키보드로 탐색 가능 -->
<div class="ticker" role="marquee" aria-label="실시간 주식 정보">
<a href="/stock/kospi" class="ticker-item" tabindex="0">
코스피 2,500 <span class="sr-only">자세히 보기</span>
</a>
<a href="/currency/usd" class="ticker-item" tabindex="0">
달러 1,430 <span class="sr-only">자세히 보기</span>
</a>
</div>
<style>
/* 포커스 시 애니메이션 일시 정지 */
.ticker:focus-within .ticker-content {
animation-play-state: paused;
}
/* 포커스 표시 */
.ticker-item:focus {
outline: 2px solid #0066cc;
outline-offset: 2px;
}
/* 스크린 리더 전용 텍스트 */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
4. AI UX: AI와 크롤러 경험
“AI는 티커를 어떻게 이해하는가?”
웹은 더 이상 사람만 보는 것이 아닙니다. 검색 엔진, LLM, 데이터 스크래퍼 등 AI 에이전트들도 여러분의 웹페이지를 읽습니다.
검색 엔진과 SEO
문제:
<!-- Googlebot이 페이지를 크롤링할 때 -->
<div class="ticker" role="marquee">
<div class="ticker-content">
코스피 2,500 | 달러 1,430 | 유로 1,668
</div>
</div>
Googlebot의 시점:
- JavaScript 실행 전: 빈 티커 또는 초기 상태만 봄
- 애니메이션: 무시
- 동적으로 업데이트되는 값: 놓침
- 결과: 불완전한 정보 인덱싱
해결책: 구조화된 데이터 제공
<!-- 1. 시맨틱 HTML로 정적 버전 제공 -->
<div class="ticker-wrapper">
<!-- AI와 검색 엔진용 정적 콘텐츠 -->
<div class="ticker-static" aria-hidden="true">
<ul itemscope itemtype="https://schema.org/ItemList">
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<meta itemprop="position" content="1">
<span itemprop="name">코스피</span>
<span itemprop="value">2,500.22</span>
</li>
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
<meta itemprop="position" content="2">
<span itemprop="name">달러</span>
<span itemprop="value">1,430.80</span>
</li>
</ul>
</div>
<!-- 사람용 동적 티커 -->
<div class="ticker" role="marquee" aria-label="실시간 시장 정보">
<div class="ticker-content">
코스피 2,500.22 | 달러 1,430.80
</div>
</div>
</div>
<style>
/* 정적 콘텐츠는 화면에서 숨김 (검색 엔진은 읽음) */
.ticker-static {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
JSON-LD로 구조화:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "ItemList",
"name": "실시간 주식 시장 정보",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "코스피",
"item": {
"@type": "FinancialProduct",
"name": "KOSPI",
"price": 2500.22,
"priceCurrency": "KRW"
}
},
{
"@type": "ListItem",
"position": 2,
"name": "달러",
"item": {
"@type": "ExchangeRateSpecification",
"currency": "USD",
"currentExchangeRate": 1430.80
}
}
]
}
</script>
LLM과 웹 크롤링
ChatGPT, Claude 등이 웹을 브라우징할 때:
AI의 관점:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. HTML 구조 파싱
└─ <div role="marquee"> 발견
└─ "이건 비필수 정보구나" (role="marquee"의 의미)
2. 텍스트 추출
└─ 티커의 현재 상태만 캡처
└─ 애니메이션이나 실시간 업데이트는 놓침
3. 컨텍스트 이해
└─ aria-label을 읽어 "실시간 주식 정보"임을 파악
└─ 하지만 전체 데이터는 보지 못함
결과: 불완전한 정보 제공
Best Practice:
<!-- 1. 명확한 메타데이터 -->
<meta name="description" content="실시간 주식 시장 정보: 코스피, 달러, 유로 환율">
<!-- 2. 의미 있는 aria-label -->
<div
class="ticker"
role="marquee"
aria-label="실시간 주식 정보: 코스피 2,500, 달러 1,430, 유로 1,668"
>
<!-- 티커 콘텐츠 -->
</div>
<!-- 3. 대체 콘텐츠 링크 -->
<a href="/markets/live-data" class="ticker-alt">
전체 시장 데이터 보기 (JSON/CSV 다운로드 가능)
</a>
데이터 스크래핑 고려사항
많은 서비스들이 웹페이지를 스크래핑해서 데이터를 수집합니다.
문제:
// 스크래퍼가 티커를 크롤링할 때
const tickerText = document.querySelector('.ticker').textContent;
// → "코스피 2,500달러 1,430유로 1,668"
// 구분자 없음! 파싱 불가능
해결책: 데이터 속성 활용
<div class="ticker" role="marquee" aria-label="실시간 시장 정보">
<div class="ticker-content">
<span
class="ticker-item"
data-market="KOSPI"
data-value="2500.22"
data-change="+0.45"
>
코스피 2,500.22 ▲ +0.45%
</span>
<span
class="ticker-item"
data-currency="USD"
data-rate="1430.80"
data-change="-0.30"
>
달러 1,430.80 ▼ -0.30%
</span>
</div>
</div>
<!-- AI가 쉽게 파싱 가능 -->
<script>
// 스크래퍼가 이렇게 읽을 수 있음
const items = document.querySelectorAll('.ticker-item');
const data = Array.from(items).map(item => ({
market: item.dataset.market || item.dataset.currency,
value: parseFloat(item.dataset.value || item.dataset.rate),
change: parseFloat(item.dataset.change)
}));
// 결과: 구조화된 데이터!
</script>
API 엔드포인트 제공
최선의 방법: 별도의 API 제공
<!-- 웹페이지에 API 링크 명시 -->
<div class="ticker-wrapper">
<div class="ticker" role="marquee">
<!-- 티커 UI -->
</div>
<div class="ticker-api-info">
<p>
이 데이터는
<a href="/api/markets/live" rel="alternate" type="application/json">
JSON API
</a>
로도 제공됩니다.
</p>
</div>
</div>
<!-- Link 헤더로 명시 -->
<link
rel="alternate"
type="application/json"
href="/api/markets/live"
title="실시간 시장 데이터 API"
>
API 응답 예시:
{
"timestamp": "2025-10-29T10:30:00Z",
"markets": [
{
"id": "KOSPI",
"name": "코스피",
"value": 2500.22,
"change": 0.45,
"changePercent": 0.018,
"currency": "KRW"
},
{
"id": "USD_KRW",
"name": "미국 달러",
"rate": 1430.80,
"change": -0.30,
"changePercent": -0.021,
"baseCurrency": "USD",
"quoteCurrency": "KRW"
}
]
}
robots.txt와 크롤링 정책
# robots.txt
# 티커 데이터는 자주 변하므로 캐시 비활성화
User-agent: *
Disallow: /ticker/live-stream
# 대신 정적 스냅샷 페이지 제공
Allow: /markets/snapshot
# API는 명시적으로 허용
Allow: /api/markets/live
# 크롤링 주기 제안 (초당 1회)
Crawl-delay: 1
요약: AI 친화적 티커 체크리스트
[ ] 구조화된 데이터 제공 (Schema.org, JSON-LD)
[ ] 의미 있는 aria-label과 메타데이터
[ ] data-* 속성으로 파싱 가능한 데이터 제공
[ ] 정적 대체 콘텐츠 제공 (SEO용)
[ ] JSON API 엔드포인트 제공
[ ] robots.txt로 크롤링 정책 명시
[ ] 검색 엔진용 스냅샷 페이지 제공
접근성 체크리스트
Ticker UI를 디자인할 때 다음 체크리스트를 확인하세요:
### WCAG 2.2.2 (Pause, Stop, Hide) - Level A
[ ] 5초 이상 움직이는 콘텐츠에 일시 정지 기능이 있는가?
[ ] 호버 또는 포커스 시 자동으로 일시 정지되는가?
[ ] 명시적인 제어 버튼이 있는가?
### ARIA 및 시맨틱
[ ] role="marquee"를 사용했는가?
[ ] aria-label 또는 aria-labelledby로 이름을 제공했는가?
[ ] aria-live="off"를 유지하고 있는가? (비필수 정보)
[ ] 중복 콘텐츠에 aria-hidden="true"를 적용했는가?
### 모션 및 애니메이션
[ ] prefers-reduced-motion을 지원하는가?
[ ] 모션 감소 모드에서 대체 UI를 제공하는가?
[ ] 애니메이션 속도가 적절한가? (50-100px/초)
### 키보드 및 포커스
[ ] 티커 내 요소에 키보드로 접근 가능한가?
[ ] 포커스 표시가 명확한가?
[ ] Tab 순서가 논리적인가?
### 색상 및 대비
[ ] 명암 대비가 4.5:1 이상인가?
[ ] 색상만으로 정보를 전달하지 않는가?
[ ] 아이콘 + 텍스트 + aria-label을 함께 사용하는가?
### 콘텐츠
[ ] 중요한 정보를 티커에 넣지 않았는가?
[ ] 티커 외에 완전한 정보를 제공하는가?
[ ] 텍스트가 충분히 큰가? (최소 14px)
### AI 및 검색 엔진
[ ] 구조화된 데이터를 제공하는가? (JSON-LD, Schema.org)
[ ] data-* 속성으로 기계가 읽을 수 있는 데이터를 제공하는가?
[ ] 정적 대체 콘텐츠를 제공하는가? (SEO용)
[ ] API 엔드포인트를 제공하거나 링크하는가?
[ ] 메타 태그에 의미 있는 설명이 있는가?
[ ] robots.txt에 크롤링 정책을 명시했는가?
흔한 실수와 함정
❌ 실수 1: 너무 빠른 속도
/* ❌ 10초에 전체 화면을 스크롤 - 너무 빠름 */
.ticker-content {
animation: scroll 10s linear infinite;
}
/* ✅ 40초에 전체 화면을 스크롤 - 읽을 시간 충분 */
.ticker-content {
animation: scroll 40s linear infinite;
}
권장 속도 계산:
// 티커 속도 계산기
function calculateTickerDuration(contentWidth, speed = 60) {
// speed: px/sec (권장: 50-100)
return (contentWidth / speed).toFixed(1);
}
// 예: 3000px 콘텐츠, 60px/sec
// → 50초 권장
❌ 실수 2: 중요한 정보를 티커에 넣기
❌ 잘못된 예: 필수 정보
<div role="marquee">
중요: 비밀번호를 변경하세요. 계정이 해킹 시도를 받았습니다.
</div>
→ 사용자가 놓칠 수 있음!
✅ 올바른 예: alert 사용
<div role="alert" class="alert-danger">
<strong>중요:</strong> 비밀번호를 변경하세요.
계정이 해킹 시도를 받았습니다.
</div>
원칙:
“사용자가 놓쳐도 괜찮은 정보만 티커에 넣어라.”
❌ 실수 3: 일시 정지 기능 없음
❌ WCAG 위반
<div class="ticker">
<div class="auto-scroll">
<!-- 멈출 수 없음 -->
</div>
</div>
✅ WCAG 준수
<div class="ticker">
<div class="auto-scroll">
<!-- 호버 시 일시 정지 -->
</div>
</div>
<style>
.ticker:hover .auto-scroll {
animation-play-state: paused;
}
</style>
❌ 실수 4: 모바일에서 너무 작은 텍스트
/* ❌ 모바일에서 읽기 어려움 */
.ticker {
font-size: 12px;
}
/* ✅ 모바일에서 읽기 쉬움 */
.ticker {
font-size: 14px;
}
@media (min-width: 768px) {
.ticker {
font-size: 16px;
}
}
❌ 실수 5: prefers-reduced-motion 무시
/* ❌ 모션 감소 모드 무시 */
.ticker-content {
animation: scroll 40s linear infinite;
}
/* ✅ 모션 감소 모드 지원 */
.ticker-content {
animation: scroll 40s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
.ticker-content {
animation: none !important;
/* 대신 스크롤 가능하게 */
overflow-x: auto;
}
}
대안: 티커를 사용하지 않는 방법
때로는 티커를 아예 사용하지 않는 것이 최선의 선택입니다.
대안 1: 정적 표시 + 더보기 링크
<div class="market-summary">
<h2>실시간 시장 정보</h2>
<ul class="market-list">
<li>
<strong>코스피</strong>
<span>2,500.22</span>
<span class="positive">▲ +0.45%</span>
</li>
<li>
<strong>달러</strong>
<span>1,430.80</span>
<span class="negative">▼ -0.30%</span>
</li>
<li>
<strong>유로</strong>
<span>1,668.03</span>
<span class="positive">▲ +0.12%</span>
</li>
</ul>
<a href="/markets" class="view-all">
모든 시장 정보 보기 →
</a>
</div>
장점:
- 읽기 쉬움
- 접근성 문제 없음
- 모든 정보 한눈에 파악
단점:
- 공간을 많이 차지
- “실시간” 느낌 부족
대안 2: 탭 또는 아코디언
<div class="market-tabs">
<div role="tablist" aria-label="시장 정보">
<button role="tab" aria-selected="true" aria-controls="panel-stock">
주식
</button>
<button role="tab" aria-selected="false" aria-controls="panel-currency">
환율
</button>
<button role="tab" aria-selected="false" aria-controls="panel-commodity">
원자재
</button>
</div>
<div id="panel-stock" role="tabpanel" tabindex="0">
<!-- 주식 정보 -->
</div>
<div id="panel-currency" role="tabpanel" tabindex="0" hidden>
<!-- 환율 정보 -->
</div>
<div id="panel-commodity" role="tabpanel" tabindex="0" hidden>
<!-- 원자재 정보 -->
</div>
</div>
대안 3: 자동 회전 카드 (페이드 방식)
<div class="rotating-cards">
<div class="card active">
<h3>코스피</h3>
<div class="value">2,500.22</div>
<div class="change positive">▲ +0.45%</div>
</div>
<!-- 5초마다 페이드 인/아웃 -->
</div>
<style>
.card {
opacity: 0;
transition: opacity 0.5s;
}
.card.active {
opacity: 1;
}
/* 호버 시 자동 회전 멈춤 */
.rotating-cards:hover .card {
animation-play-state: paused;
}
</style>
언제 Ticker를 사용해야 할까?
✅ Ticker가 적합한 경우
- 비필수 보조 정보:
- 주식 시장 정보
- 환율, 날씨
- 스포츠 점수
- 광고 배너
- 공간이 제한된 경우:
- 좁은 헤더/푸터
- 모바일 상단 바
- 사이드바
- “실시간” 느낌이 중요한 경우:
- 라이브 이벤트
- 속보 뉴스
❌ Ticker를 피해야 하는 경우
- 필수 정보:
- 경고, 오류
- 중요한 공지사항
- 사용자 액션이 필요한 메시지
- 순서가 중요한 정보:
- 단계별 지침
- 튜토리얼
- 긴 텍스트:
- 긴 문장이나 문단
- 복잡한 설명
- 메인 콘텐츠:
- 페이지의 주요 내용
- 네비게이션
마치며
Ticker UI는 “양날의 검”입니다.
잘 사용하면 제한된 공간에서 많은 정보를 효과적으로 전달할 수 있지만, 잘못 사용하면 사용자 경험을 해치고 접근성 문제를 일으킵니다.
핵심 원칙:
- UI: 가독성을 최우선으로 (크기, 속도, 대비)
- UX: 사용자에게 제어권을 주어라 (일시 정지, 호버)
- AX (Human): 모든 사용자를 고려하라 (WCAG, prefers-reduced-motion, 스크린 리더)
- AX (AI): AI와 검색 엔진을 고려하라 (구조화된 데이터, API, SEO)
실천 가이드:
티커를 만들기 전에 스스로에게 물어보세요:
1. "이 정보를 사용자가 놓쳐도 괜찮은가?"
→ 아니오라면 티커 사용 금지
2. "정적 표시로는 안 되는가?"
→ 정적 표시가 가능하다면 그것을 선택
3. "사용자가 일시 정지할 수 있는가?"
→ 없다면 WCAG 위반
4. "모든 사용자가 접근할 수 있는가?"
→ 스크린 리더, 키보드, prefers-reduced-motion 확인
5. "AI가 이 정보를 이해할 수 있는가?"
→ 구조화된 데이터, data 속성, API 제공 확인
6. "정말 움직여야 하는가?"
→ 정직하게 답하세요
하나씩 적용하다 보면, 어느새 사용자 친화적이고 접근 가능한 Ticker UI를 마스터하게 될 겁니다.
참고 자료
WCAG 공식 문서
- WCAG 2.1 SC 2.2.2: Pause, Stop, Hide - 움직이는 콘텐츠 제어 요구사항
- WCAG 2.1 SC 1.4.3: Contrast (Minimum) - 명암 대비 요구사항
- WCAG 2.1 SC 2.3.3: Animation from Interactions - 모션 감소
- Understanding WCAG 2.0 - Time Limits - 시간 제한 가이드라인
ARIA 명세
- WAI-ARIA 1.0 - marquee role - marquee role 공식 정의
- ARIA Live Regions - Live Region 속성 상세
- MDN: ARIA marquee role - MDN 가이드
UX 연구 및 Best Practices
- Nielsen Norman Group: Moving, Blinking, and Flashing Content - 움직이는 콘텐츠의 UX 연구
- A List Apart: Accessibility and Motion - 모션 접근성
- Smashing Magazine: Designing Safer Web Animation - 안전한 웹 애니메이션
커뮤니티 토론
- UX Stack Exchange: Arguments against ticker/marquee - 티커/마퀴 사용에 대한 논의
- UX Stack Exchange: Do Marquees Have Good UX? - 마퀴의 UX 품질
구현 예제
- CSS-Tricks: Infinite Scrolling Ticker - CSS 티커 구현
- Motion Dev: React Ticker Component - React 티커 컴포넌트
- FreeCodeCamp: Building a Marquee Component - React 마퀴 구현
도구 및 테스팅
- WebAIM Contrast Checker - 색상 대비 확인
- axe DevTools - 접근성 자동 테스트
- NVDA Screen Reader - 무료 스크린 리더
- VoiceOver (macOS) - 내장 스크린 리더
AI 및 SEO
- Schema.org - 구조화된 데이터 표준
- Google Structured Data Testing Tool - 구조화된 데이터 테스트
- Schema.org: FinancialProduct - 금융 상품 스키마
- Google Search Central: JavaScript SEO - JavaScript SEO 기초
- OpenAI: GPT Best Practices for Web Content - LLM이 읽기 좋은 웹 콘텐츠
- robots.txt 명세 - 크롤러 제어
- JSON-LD - Linked Data 포맷
관련 문서
- 📖 ARIA: marquee role - marquee role 상세 가이드
- 🔴 Live Regions 완전 가이드 - 동적 콘텐츠 접근성
- 🎨 웹 애니메이션 접근성 - 모션과 접근성
- ⚡ prefers-reduced-motion 가이드 - 모션 감소 모드 구현
댓글