Giriş Yap API Anahtarı Al
Tutorials

How to Build a Live Score Ticker with HTML, CSS, and an API

Screenshot mockup of a scrolling live football score ticker bar

A step-by-step guide to building a scrolling live football score ticker for your website's header, using vanilla JavaScript and Live Football API.

Bu Sayfada

A scrolling score ticker across the top of a site is one of the most recognizable sports-media UI patterns — and one of the more approachable things to build with a football API. This guide walks through building one from scratch with vanilla HTML, CSS, and JavaScript.

What We're Building

A horizontal strip showing today's matches with live scores, auto-updating every 30 seconds, that scrolls continuously like a news ticker.

Step 1: The HTML Structure

<div class="ticker-wrapper">
  <div class="ticker-track" id="tickerTrack">
    <!-- match items injected here -->
  </div>
</div>

Step 2: The CSS Scroll Animation

.ticker-wrapper {
  overflow: hidden;
  white-space: nowrap;
  background: #0f172a;
  padding: 10px 0;
}

.ticker-track {
  display: inline-flex;
  animation: scroll-left 40s linear infinite;
}

.ticker-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 0 24px;
  color: white;
  font-size: 14px;
  border-right: 1px solid #334155;
}

.ticker-item .live {
  color: #f97316;
  font-weight: bold;
}

@keyframes scroll-left {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

Step 3: Fetching and Rendering Matches

async function fetchTodayMatches() {
  const today = new Date().toISOString().split('T')[0];
  const res = await fetch(
    `https://live-football-api.com/api/v1/matches?api_key=YOUR_KEY&date=${today}&lang=en`
  );
  const result = await res.json();
  return result.data.matches;
}

function renderTicker(matches) {
  const track = document.getElementById('tickerTrack');

  // Duplicate the list so the scroll loop has no visible gap
  const html = [...matches, ...matches].map(match => `
    <div class="ticker-item">
      <span>${match.home.name}</span>
      <strong>${match.home.score} - ${match.away.score}</strong>
      <span>${match.away.name}</span>
      ${match.status.is_live
        ? `<span class="live">${match.status.display}</span>`
        : `<span>${match.status.display || match.kickoff}</span>`}
    </div>
  `).join('');

  track.innerHTML = html;
}

Step 4: Keeping Scores Live

Poll on an interval to refresh live scores without a full page reload. 30 seconds is a reasonable default that balances freshness against credit usage:

async function updateTicker() {
  const matches = await fetchTodayMatches();
  renderTicker(matches);
}

updateTicker();
setInterval(updateTicker, 30000); // refresh every 30 seconds

Step 5: Avoiding a Visual Jump on Refresh

Re-rendering the whole ticker on each poll can cause the scroll animation to jump. A simple fix is updating only the score text inside existing DOM nodes rather than replacing the entire track:

function updateScoresInPlace(matches) {
  matches.forEach(match => {
    const el = document.querySelector(`[data-match-id="${match.id}"] strong`);
    if (el) el.textContent = `${match.home.score} - ${match.away.score}`;
  });
}

This requires adding a data-match-id attribute to each ticker item when first rendered, so subsequent updates can target the right element without rebuilding the whole strip.

Step 6: Reducing Credit Usage

Polling every 30 seconds across a full day adds up. A few ways to cut usage without hurting the experience:

  • Pause polling when the browser tab isn't visible, using the Page Visibility API
  • Slow the interval when there are no live matches (e.g. check once every 5 minutes instead of 30 seconds if nothing is is_live)
  • Share one poll across all visitors by caching the response on your own server for a few seconds, rather than every browser tab hitting the API directly
let pollInterval;

document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    clearInterval(pollInterval);
  } else {
    updateTicker();
    pollInterval = setInterval(updateTicker, 30000);
  }
});

Frequently Asked Questions

How many credits does a ticker use per day?

At a 30-second refresh over 24 hours, that's roughly 2,880 calls/day per active viewer session if polling client-side — server-side caching (one shared poll for all visitors) reduces this dramatically.

Can I filter the ticker to specific leagues only?

Yes, filter the matches array client-side by match.league.id after fetching, since /matches returns all matches for the date by default.

Should I poll or use webhooks for a ticker?

Polling is simpler for a ticker showing many simultaneous matches, since webhooks deliver one event at a time per match rather than a full day's board — polling /matches is the more natural fit here.

How do I handle a day with no matches?

Check if the matches array is empty and render a fallback message ("No matches today") instead of an empty scrolling strip.

Bu Yazıyı Paylaş:
← Bloga Dön