Example
Race Back To Base
Hello!
Can you make it back to base before the sandstorm hits?
Play now.
The thing I really love about working with vectors is how scalable they are. Even the tiniest details remain crystal clear when you scale them up. I built this demo using elements from a single vector pack created by Macrovector on Freepik.
To create the illusion of movement, I used PowerPoint to recolour the wheels on the Mars Rover vehicle, and isolated parts of the tyre track.
I then made a GIF using these shapes.
And then placed the GIF over the modified SVG image in Storyline.
This allowed me to turn the motion on/off using state changes.
Pretty much everything that moves in this demo is a GIF, tucked away in a state change until it's needed.
Having access to high-quality, easy-to-use graphics allowed me to focus on the instructional elements and the scoring mechanic. The 'wronger' your answer, the quicker your oxygen will deplete. Even if you reach the third question, if your oxygen hits zero, you will have to start again.
This is controlled by JavaScript, which adjusts the value of the variable by a set amount when triggered.
var player = GetPlayer();
var penalty = 35; // Change to 45 or 55 depending on the button
var battery = player.GetVar("Battery");
var elapsed = 0;
var interval = 100;
var steps = 10000 / interval;
var amountPerStep = penalty / steps;
var timer = setInterval(function() {
elapsed++;
battery = Math.max(0, battery - amountPerStep);
if (battery === 0) {
player.SetVar("Battery", 0);
clearInterval(timer);
} else if (elapsed >= steps) {
player.SetVar("Battery", Math.round(battery));
clearInterval(timer);
} else {
player.SetVar("Battery", Math.round(battery));
}
}, interval);
The eagle-eyed among you will have also noticed that when this demo runs on a desktop, the closed captions that accompany the narration have been repositioned and have a slight 'glitch' effect as they appear.
This is also achieved with JavaScript. Despite the recent improvements to the closed caption feature in Storyline, sometimes I prefer to manually override the position of the captions to better suit my layout. This code only reliably works in desktop view, though.
const mobileView = window.innerWidth < 768 || window.innerHeight < 500;
var storyW = 1280;
var storyH = 720;
var boxLeft = 850;
var boxTop = 430;
var boxWidth = 350;
var boxHeight = 200;
var captionFontSize = 20;
function positionCaptions() {
if (mobileView === true) {
console.log("Mobile view detected - caption positioning skipped.");
return;
}
var leftPct = (boxLeft / storyW * 100).toFixed(2) + "%";
var topPct = (boxTop / storyH * 100).toFixed(2) + "%";
var widthPct = (boxWidth / storyW * 100).toFixed(2) + "%";
const css = `
.caption-container {
position: absolute !important;
transform: none !important;
}
.caption {
position: absolute !important;
left: ${leftPct} !important;
top: ${topPct} !important;
width: ${widthPct} !important;
font-size: ${captionFontSize}pt !important;
transform: none !important;
z-index: 1000 !important;
}
@keyframes glitchstutter {
0% { opacity: 0; transform: translate(-10px, 4px) skewX(-8deg); filter: blur(6px); }
8% { opacity: 0.8; transform: translate( 10px, -4px) skewX( 9deg); filter: blur(3px); }
12% { opacity: 0; transform: translate(-8px, 2px) skewX(-6deg); filter: blur(7px); }
18% { opacity: 0.9; transform: translate( 8px, 3px) skewX( 6deg); filter: blur(1px); }
22% { opacity: 0.1; transform: translate(-12px, -2px) skewX(-10deg); filter: blur(5px); }
28% { opacity: 1; transform: translate( 6px, 0px) skewX( 4deg); filter: blur(1px); }
35% { opacity: 0.3; transform: translate(-6px, 4px) skewX(-6deg); filter: blur(3px); }
42% { opacity: 1; transform: translate( 4px, -2px) skewX( 3deg); filter: blur(1px); }
50% { opacity: 0.6; transform: translate(-3px, 1px) skewX(-2deg); filter: blur(1px); }
60% { opacity: 1; transform: translate( 2px, 0px) skewX( 1deg); filter: blur(0px); }
75% { opacity: 0.95; transform: translate(-1px, 0px) skewX( 0deg); filter: blur(0px); }
100% { opacity: 1; transform: translate( 0px, 0px) skewX( 0deg); filter: blur(0px); }
}
.caption-glitch {
animation: glitchstutter 0.6s ease-out forwards !important;
}
`;
let style = document.getElementById('custom-caption-style');
if (!style) {
style = document.createElement('style');
style.id = 'custom-caption-style';
document.head.appendChild(style);
}
style.textContent = css;
var capEl = document.querySelector('.caption');
if (capEl) {
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
capEl.classList.remove('caption-glitch');
void capEl.offsetWidth;
capEl.classList.add('caption-glitch');
});
});
observer.observe(capEl, {
childList: true,
subtree: true,
characterData: true
});
capEl.classList.add('caption-glitch');
}
console.log(`Captions positioned at left:${leftPct} top:${topPct} font:${captionFontSize}pt`);
}
document.addEventListener('DOMContentLoaded', positionCaptions);
positionCaptions();
I had a lot of fun making this. It still surprises how much mileage you can get from a small number of visual assets.
If you have any more questions about this demo, please ask.
1 Reply
- Jonathan_HillSuper Hero
Oh, and I forgot to mention. Similar to last week's Accordion Interaction demo, I have also used JavaScript to change the z-index position of the question text and the selected option.
I also had to do this because the storm GIF that overlays the slide also blocks clicks on any items beneath it 😄
Once triggered, these elements pass behind the rover vehicle, contributing to the sense of motion.
