This tutorial is for:
By completing this tutorial, you will:
onTrack callback to detect when widgets are readyBefore starting this tutorial, ensure you have:
This tutorial uses the onTrack callback, which is a core widget feature available for all widget types.
Widget loading happens in multiple asynchronous phases:
SIR function becomes availableThe time between widget initialization and data rendering can vary from milliseconds to several seconds depending on network conditions. A loading indicator improves user experience during this wait.
Users see a blank space while widgets load, which can:
A loading indicator provides visual feedback that content is on the way.
Start with a standard widget integration. The widgetloader script downloads asynchronously, then the SIR function loads the widget.
<script>
(function(a,b,c,d,e,f,g,h,i){a[e]||(i=a[e]=function(){(a[e].q=a[e].q||[]).push(arguments)},i.l=1*new Date,i.o=f,
g=b.createElement(c),h=b.getElementsByTagName(c)[0],g.async=1,g.src=d,g.setAttribute("n",e),h.parentNode.insertBefore(g,h)
)})(window,document,"script", "https://widgets.sir.sportradar.com/sportradar/widgetloader", "SIR", {
language: "en"
});
SIR("addWidget", "#sr-widget-1", "match.scoreboard", {matchId: 41960917});
</script>
<div class="widgets">
<div id="sr-widget-1" class="box"></div>
</div>
Important: Even though we call SIR("addWidget", ...) immediately, it only executes after the widgetloader downloads. Once executed, it:
Add a separate element for your loading indicator. Keep it outside the widget's target element.
<div class="widgets">
<!-- Widget target element - will be populated by widget code -->
<div id="sr-widget-1" class="box"></div>
<!-- Separate element for loading indicator -->
<div id="your-content" class="box">
<div class="loader"></div>
</div>
</div>Critical: Never add the loading indicator inside the widget's target element (#sr-widget-1). The widget will replace all content in that element when it initializes.
Add styles to size the elements and animate the loader.
View Complete CSS
body {
display: flex;
justify-content: center;
}
.widgets {
max-width: 360px;
width: 100%;
}
/* Set same size for widget and loader containers */
.box {
border: rgba(0,0,0,0.12) solid 1px;
min-height: 160px;
}
/* Center loader inside content element */
#your-content {
display: flex;
align-items: center;
justify-content: center;
}
/* Spinning loader animation */
.loader {
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
width: 40px;
height: 40px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
You can use any loading indicator style - spinners, skeleton screens, progress bars, or branded animations. Just ensure it provides clear visual feedback.
Use the onTrack callback to detect when the widget has loaded data, then swap visibility between the loader and widget.
let widgetHasInitialized = false;
function onTrack(target, data) {
// Listen for 'data_change' event - fired when widget receives data
if (target === "data_change" && !data.error && !widgetHasInitialized) {
// Show the widget
document.getElementById('sr-widget-1').style.display = 'block';
// Hide the loader
document.getElementById('your-content').style.display = 'none';
widgetHasInitialized = true;
}
}The data_change Event: This event fires when the widget successfully receives data. If data.error is undefined, the widget will render successfully.
SIR("addWidget", "#sr-widget-1", "match.scoreboard", {
matchId: 41960917,
onTrack: onTrack
});Add CSS to hide the widget element until data loads.
/* Hide widget until data loads */
#sr-widget-1 {
display: none;
}View Complete Working Example
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Widget with Loading Indicator</title>
<style>
body {
display: flex;
justify-content: center;
}
.widgets {
max-width: 360px;
width: 100%;
}
.box {
border: rgba(0,0,0,0.12) solid 1px;
min-height: 160px;
}
#your-content {
display: flex;
align-items: center;
justify-content: center;
}
.loader {
border: 16px solid #f3f3f3;
border-radius: 50%;
border-top: 16px solid #3498db;
width: 40px;
height: 40px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Hide widget until data loads */
#sr-widget-1 {
display: none;
}
</style>
</head>
<body>
<script>
(function(a,b,c,d,e,f,g,h,i){a[e]||(i=a[e]=function(){(a[e].q=a[e].q||[]).push(arguments)},i.l=1*new Date,i.o=f,
g=b.createElement(c),h=b.getElementsByTagName(c)[0],g.async=1,g.src=d,g.setAttribute("n",e),h.parentNode.insertBefore(g,h)
)})(window,document,"script", "https://widgets.sir.sportradar.com/sportradar/widgetloader", "SIR", {
language: "en"
});
let widgetHasInitialized = false;
function onTrack(target, data) {
if (target === "data_change" && !data.error && !widgetHasInitialized) {
document.getElementById('sr-widget-1').style.display = 'block';
document.getElementById('your-content').style.display = 'none';
widgetHasInitialized = true;
}
}
SIR("addWidget", "#sr-widget-1", "match.scoreboard", {
matchId: 41960917,
onTrack: onTrack
});
</script>
<div class="widgets">
<div id="sr-widget-1" class="box"></div>
<div id="your-content" class="box">
<div class="loader"></div>
</div>
</div>
</body>
</html>Optimization: The widgetHasInitialized flag ensures we only change display once, even if multiple data_change events fire.
Symptom: The loading indicator vanishes before the widget displays, leaving a blank space.
Cause: The loading indicator was placed inside the widget's target element.

Never add content to the widget's target element. When addWidget executes, it replaces ALL content in the target element with widget code.
Incorrect Implementation:
<!-- ❌ DON'T DO THIS -->
<div id="sr-widget-1" class="box">
<!-- This will be removed when widget initializes! -->
<div id="your-content" class="box">
<div class="loader"></div>
</div>
</div>Correct Implementation:
<!-- ✅ DO THIS -->
<div class="widgets">
<!-- Widget target - empty, will be filled by widget -->
<div id="sr-widget-1" class="box"></div>
<!-- Loader in separate element -->
<div id="your-content" class="box">
<div class="loader"></div>
</div>
</div>Cause: Forgot to hide the widget element initially with CSS.
Solution: Add CSS to hide the widget until data_change fires:
#sr-widget-1 {
display: none;
}Cause: The onTrack callback isn't firing or has incorrect logic.
Solution: Verify:
onTrack is passed to widget configurationtarget === "data_change"You can extend the onTrack callback to handle loading errors:
function onTrack(target, data) {
if (target === "data_change" && !widgetHasInitialized) {
widgetHasInitialized = true;
if (data.error) {
// Handle error state
document.getElementById('your-content').innerHTML =
'<p style="color: red;">Failed to load widget</p>';
} else {
// Show widget on success
document.getElementById('sr-widget-1').style.display = 'block';
document.getElementById('your-content').style.display = 'none';
}
}
}Always keep the loading indicator in a separate element from the widget's target. The widget replaces its target element's content.
The onTrack callback with data_change event is the reliable way to detect when a widget has loaded and is ready to display.
Start with the widget hidden (display: none) and only show it after the data_change event confirms successful loading.