Loading...
Loading...
The scrolling crawl at the bottom of the screen — CNN, BBC News, Bloomberg. Headlines flow continuously from right to left. This tutorial covers infinite CSS animations, dynamic data arrays, and seamless looping.
Infinite scroll
Uses CSS @keyframes with infinite repeat. Content is duplicated for seamless looping.
Array data
Instead of single fields, the schema accepts an array of headlines. Each item scrolls past in sequence.
Full-width bar
Spans the entire bottom edge. A colored badge sits on the left, the scrolling text flows to its right.
The ticker duplicates all the headlines so the scroll appears infinite. When the first set scrolls fully off-screen, the second set has already taken its place — the animation resets invisibly.
_renderItems(items) {
// Duplicate the array for seamless looping
const allItems = [...items, ...items];
this._content.innerHTML = allItems.map((item, i) =>
`<span class="ticker-item">
<span class="ticker-dot"></span>${item}
</span>` +
(i < allItems.length - 1
? '<span class="ticker-separator"></span>'
: '')
).join('');
}.ticker-content {
display: flex;
white-space: nowrap;
animation: scroll 20s linear infinite;
padding-left: 100%; /* Start off-screen right */
}
@keyframes scroll {
0% { transform: translateX(0); }
100% { transform: translateX(-50%); }
/* -50% because content is duplicated */
}Why -50%?
The content is duplicated (original + copy), so 50% of the total width equals the full length of one set. When the animation hits -50%, the copy has perfectly replaced the original — the loop is seamless.
Notice the schema uses an array of strings for headlines, not a single text field. Controllers render this as a list where the operator can add, remove, and reorder items.
{
"$schema": "https://ograf.ebu.io/v1/specification/json-schemas/graphics/schema.json",
"id": "dev.ograf.tutorial.ticker",
"version": "1.0.0",
"name": "News Ticker",
"description": "Scrolling headline crawl with an infinite CSS loop. 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": {
"badge": {
"type": "string",
"title": "Badge Text",
"gddType": "single-line",
"default": "Breaking"
},
"items": {
"type": "array",
"title": "Headlines",
"items": {
"type": "string",
"gddType": "single-line"
},
"default": [
"EBU releases OGraf Graphics Definition v1 — the open standard for broadcast graphics",
"SPX Graphics Controller adds full OGraf compliance in upcoming v1.4 release",
"CasparCG community explores native OGraf renderer integration",
"Loopic announces one-click OGraf export for all template projects"
]
}
}
}
}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.
ticker.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
You've learned infinite CSS animations, array data schemas, and the content duplication trick for seamless looping.

Lower Third
Name & title overlay

Bug / LIVE
Corner indicator with pulse

Full Page Quote
Cinematic full-screen typography

Election Bars
Animated percentage chart

Sport Lineup
Team roster grid

Score Bug
Live match scoreboard

Countdown Timer
Self-ticking clock

Breaking News
Full-screen urgent alert

Weather Forecast
Conditions & 3-day outlook

Social Media Card
Post overlay with avatar