Loading...
Loading...
Countdown timers are used everywhere in broadcast — pre-show countdowns, segment timers, auction clocks, and event countdowns. This graphic is unique because it ticks itself using setInterval — no external update calls needed once it starts.
Self-ticking
Uses setInterval internally. Once started, it counts down on its own — no updateAction calls from the playout system.
Urgency state
When 10 seconds or fewer remain, the timer shifts to red with a pulsing animation — signaling urgency to the viewer without any operator intervention.
Clean dispose
The interval must be cleared in dispose(). Forgetting this causes memory leaks and ghost timers ticking in the background.
The _startTicking method sets up a 1-second interval. Each tick decrements the remaining seconds, formats the display, and checks for the urgency threshold. When it hits zero, it clears itself.
_startTicking() {
this._clearInterval();
this._remaining = this._totalSeconds;
this._updateDisplay();
this._interval = setInterval(() => {
this._remaining--;
if (this._remaining <= 10 && this._remaining > 0) {
this._root.classList.add('urgent');
}
if (this._remaining <= 0) {
this._remaining = 0;
this._clearInterval();
this._root.classList.add('finished');
this._root.classList.remove('urgent');
}
this._updateDisplay();
}, 1000);
}
_updateDisplay() {
const mins = Math.floor(this._remaining / 60);
const secs = this._remaining % 60;
this._display.textContent =
`${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
}
_clearInterval() {
if (this._interval) {
clearInterval(this._interval);
this._interval = null;
}
}
async load({ data }) {
this._totalSeconds = data?.seconds || 60;
if (data?.label) this._label.textContent = data.label;
this._remaining = this._totalSeconds;
this._updateDisplay();
return { statusCode: 200 };
}
async playAction() {
this._root.classList.add('visible');
this._startTicking();
await new Promise(r => setTimeout(r, 600));
return { statusCode: 200, currentStep: 0 };
}
async dispose() {
this._clearInterval();
this.innerHTML = '';
return { statusCode: 200 };
}Design tip
Always clear intervals in dispose(). In a broadcast environment, graphics are loaded and unloaded frequently. A forgotten interval means a timer ticking in the background, consuming CPU and potentially causing unexpected behavior when the graphic is reloaded.
Each second tick gets a subtle scale animation on the digits. When urgency kicks in, the entire timer shifts to red with a pulsing glow.
.countdown {
position: absolute; /* against the graphic's root, not the viewport */
bottom: 80px;
left: 50%;
transform: translateX(-50%) scale(0.9);
opacity: 0;
text-align: center;
transition: opacity 0.5s ease, transform 0.5s ease;
}
.countdown.visible {
opacity: 1;
transform: translateX(-50%) scale(1);
}
.countdown-display {
font-size: 72px;
font-weight: 700;
font-variant-numeric: tabular-nums;
letter-spacing: -2px;
color: white;
animation: tick 1s steps(1) infinite;
}
@keyframes tick {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.02); }
}
/* Urgency state — last 10 seconds */
.countdown.urgent .countdown-display {
color: #ef4444;
animation: urgent-pulse 1s ease-in-out infinite;
}
@keyframes urgent-pulse {
0%, 100% { text-shadow: 0 0 20px rgba(239, 68, 68, 0.3); }
50% { text-shadow: 0 0 40px rgba(239, 68, 68, 0.6); }
}
/* Finished state */
.countdown.finished .countdown-display {
color: #22c55e;
animation: none;
}{
"$schema": "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json",
"id": "dev.ograf.tutorial.countdown",
"version": "1.0.0",
"name": "Countdown Timer",
"description": "Self-ticking countdown clock that goes red in the last 10 seconds. Tutorial from ograf.dev.",
"author": {
"name": "ograf.dev",
"url": "https://ograf.dev"
},
"main": "graphic.mjs",
"stepCount": 1,
"supportsRealTime": true,
"supportsNonRealTime": false,
"schema": {
"type": "object",
"properties": {
"label": {
"type": "string",
"title": "Label",
"gddType": "single-line",
"default": "STARTING IN"
},
"seconds": {
"type": "integer",
"title": "Seconds",
"default": 120,
"minimum": 1
}
}
}
}A real OGraf Graphics Definition v1 package. A compliant renderer reads the manifest and drives the lifecycle. MIT-licensed; drop it into any OGraf-compatible system.
countdown.ograf.json
Manifest — what a renderer reads (id, schema, lifecycle flags)
graphic.mjs
Web Component with load / play / update / stop / customAction / dispose
style.css
Stylesheet, loaded by graphic.mjs via a <link> tag
README.md
Usage notes
Self-ticking with setInterval, urgency states, and proper cleanup in dispose() — a self-contained timer graphic.

Lower Third
Name & title overlay

Bug / LIVE
Corner indicator with pulse

News Ticker
Scrolling headline crawl

Full Page Quote
Cinematic full-screen typography

Election Bars
Animated percentage chart

Sport Lineup
Team roster grid

Score Bug
Live match scoreboard

Breaking News
Full-screen urgent alert

Weather Forecast
Conditions & 3-day outlook

Social Media Card
Post overlay with avatar