How to Add the JavaScript to Your Storyline Project
- Go to the Triggers panel in Storyline
- Create a new trigger:
- Action: Execute JavaScript -# How to Create an Animated Walking Character in Articulate Storyline: Complete Step-by-Step Tutorial
Learn how to build a smooth, keyboard-controlled animated character using PNG frames, JavaScript, and Storyline’s built-in features. This comprehensive guide will teach you everything from preparing sprite frames to implementing movement logic.
What You’ll Build
By the end of this Articulate Storyline tutorial, you’ll have:
- A character that walks smoothly when you press the arrow keys
- Proper animations that don’t jitter or jump around
- Speed that gradually increases the longer you hold a key
- Different character states for walking left, walking right, and standing still
- Professional-looking animations suitable for e-learning courses
Why This Animation Approach Works
- No jittery movement – We align every frame perfectly to prevent sliding
- Smooth animations – APNG format keeps transparency and timing intact
- Easy to manage – Storyline handles the visual states while JavaScript handles the movement
- Professional feel – GSAP library makes everything buttery smooth
- Reusable – Once set up, you can easily swap in different character sprites
What You Need Before Starting
- Basic knowledge of Articulate Storyline (creating states, adding images, setting up triggers)
- Character sprite frames (we’ll show you where to get free ones)
- A tool to create animated PNGs (free options available)
- The GSAP animation library (already included in modern Storyline versions)
Free Character Sprites for Your Animation
For this tutorial, we’ll use a free cat character sprite that works perfectly for learning. You can download it from: Hand-drawn Animation Frames Collection on Freepik
This collection includes six walking frames that we’ll use to create our animated character. Once you’re comfortable with the process, you can apply the same techniques to any character sprite.
Step 1: Prepare Your Character Sprite Frames
Download and Organize Your Character Files
- Download the character sprite collection from the Freepik link above or use your own six PNG files showing different walking poses
- Create a dedicated folder called “block-cycle” and extract all six images inside
- Rename the files consistently – Use names like walk-right-01.png through walk-right-06.png
- Identify the longest frame – Look through all six frames and find the one that extends furthest (usually frame 6 has the most extended pose)
Align Every Frame Perfectly (Critical Step)
This is the most important step for smooth Storyline character animation. If your frames aren’t perfectly aligned, your character will appear to slide or jitter during movement.
- Open your graphics editor (Photoshop, Illustrator, GIMP, etc.) and load all six character frames
- Start with the longest frame – This is usually frame 6, which often has the most extended leg pose
- Center this frame in your workspace as your reference point
- Enable the shadow layer – Most sprite collections include a shadow beneath the character. We’ll use this as our alignment guide
- Zoom in closely (Ctrl+Y in most editors) to see individual pixels clearly
- Identify your reference point – Look for a consistent spot like:
- A specific point on the shadow
- The character’s foot position
- A dot or marking on the character
- Align each remaining frame:
- Open frames 1-5 one by one
- Drag each frame so the reference point snaps to exactly the same position
- Use your editor’s rulers or grid to ensure pixel-perfect alignment
- Remove the shadow layer once all frames are aligned
- Verify consistent dimensions – All frames must be exactly the same width (e.g., 240px)
Pro Tip: If one frame exports at 239px while others are 240px, your entire animation will jitter. Always double-check dimensions before proceeding.
Step 2: Create Your APNG Animation
Export Individual PNG Frames
- Use your editor’s batch export feature (File → Export → Export for Screens in Adobe products)
- Select all six aligned frames
- Set the width to exactly 240px (or whatever consistent size you chose)
- Export to your “block-cycle” folder
- Name them sequentially: walk-right-01.png through walk-right-06.png
- Verify dimensions – Right-click each PNG and confirm they’re all exactly 240px wide
Important: Sometimes editors will export at 239px due to rounding. If this happens, manually set the width to 240px to prevent animation issues.
Download and Use APNG Assembler
For creating our animated PNG, we’ll use a free, reliable tool:
Download APNG Assembler from: https://sourceforge.net/projects/apngasm/
This tool converts your individual PNG frames into a single animated PNG file that maintains transparency and smooth playback.
Assemble Your Walking Animation
- Launch APNG Assembler after installation
- Add your six PNGs in order (01 through 06) – the sequence is crucial for proper animation flow
- Set frame timing:
- Select “Delay all frames”
- Set to 2/24 seconds per frame (or 1/12) for smooth, natural walking speed
- This timing works well for most character animations
- Export your APNG:
- Choose your “block-cycle” folder as the destination
- Name it something clear like “walk-right-block.png”
- Test the animation – Drag the APNG into a browser tab to verify it loops smoothly without jumping
Quality Check: Your character should walk in place smoothly with no visible jumping or sliding between frames.
Step 3: Set Up Storyline Character States
Create the Character Container Object
- Open your Storyline project and navigate to your target slide
- Insert a shape (Insert → Shapes → Rectangle) – this will be your character container
- Position the rectangle where you want your character to appear on screen
- Rename the object in the Selection Pane to “Character” (we’ll need this name later)
- Add a temporary text box inside the rectangle that says “Character State” so you can see its position
- Delete the text box once you’ve positioned the container correctly
Positioning Tip: Don’t worry about perfect horizontal centering – the JavaScript will handle final positioning. Focus on getting the vertical position right.
Set Up the Storyline Character State Variable
Before adding our character images, we need to create the variable that will control state changes:
- Open the Variables panel (usually in the bottom-right)
- Create a new variable:
- Name: characterState (exactly as shown – case sensitive)
- Type: Text
- Default value: Leave blank
- Click OK to save the variable
Import Character Images as Multiple States
Now we’ll add our character images directly as states of our container object:
- Select your “Character” rectangle
- Go to Format → States → Edit States in the ribbon
- Click “Insert Pictures as States” (this imports multiple images as different states)
- Select your character images:
- Choose your individual PNG frames (not the APNG)
- You’ll want at least: idle-right, idle-left, walk-right, walk-left
- Click “Done” to finish creating states
Note: The state names must match exactly what we’ll use in the JavaScript code later.
Step 4: Configure Storyline State-Change Triggers
These triggers are what make your character animation work. They listen for changes to your characterState variable and automatically switch the visual appearance of your character.
Create the State-Change Triggers
You need to create four separate triggers – one for each character state. Here’s how to set up each one:
Trigger 1 – Walking Right:
- Action: Change state of Character to WalkRight
- When: Variable changes → characterState
- Condition: When characterState equals “WalkRight”
Trigger 2 – Walking Left:
- Action: Change state of Character to WalkLeft
- When: Variable changes → characterState
- Condition: When characterState equals “WalkLeft”
Trigger 3 – Idle Right:
- Action: Change state of Character to IdleRight
- When: Variable changes → characterState
- Condition: When characterState equals “IdleRight”
Trigger 4 – Idle Left:
- Action: Change state of Character to IdleLeft
- When: Variable changes → characterState
- Condition: When characterState equals “IdleLeft”
Important Trigger Setup Notes
- Trigger Location: Make sure these triggers are at the slide level, not attached to specific objects
- Exact Naming: The condition values (“WalkRight”, “IdleRight”, etc.) must match exactly what the JavaScript will send
- Variable Name: Ensure all triggers reference the same variable name: “characterState”
Testing Tip: After setting up triggers, you can test them by manually changing the characterState variable value in Storyline’s preview mode to see if states switch correctly.
Step 5: Add the JavaScript
The Complete JavaScript Code for Character Animation
Create an “Execute JavaScript” trigger and paste this complete code:
(function () { try { // ─── Constants for movement ─────────────────────────────────────── const moveSpeedBase = 2; // Starting pixels/frame const accelerationStep = 0.5; // Increase in speed each step const maxSpeed = 15; // Cap on pixels/frame // ─── State variables ──────────────────────────────────────────── let currentSpeed = moveSpeedBase; let moveInterval = null; let lastDirection = 'right'; let isMoving = false; // ─── Storyline Variable Name ──────────────────────────────────── // (Must match exactly the Text variable you created in Storyline) const STATE_VAR_NAME = 'characterState'; // ─── Data-model-id for the character sprite ───────────────────── // Replace '5mGJNQeqNPN' with your actual character object's data-model-id const MODEL_IDS = { character: '6TZmBakju4p' }; // ─── Grab the character element from DOM ───────────────────────── const character = document.querySelector(`[data-model-id="${MODEL_IDS.character}"]`); // ─── Dependency Checks ─────────────────────────────────────────── if (!character) { console.error(`❌ Character element not found (data-model-id='${MODEL_IDS.character}').`); return; } if (!window.gsap) { console.error("❌ GSAP library not found. Make sure GSAP is loaded before this script runs."); return; } // ─── Initial CSS Setup ─────────────────────────────────────────── character.style.position = 'absolute'; character.style.visibility = 'hidden'; character.style.left = '50%'; // Temporary centering; final centering happens after layout stabilizes // ─── Find a valid “stage” container (ancestor with nonzero width) ── let container = character.parentElement; let stageContainer = null; let depth = 0; while (container && depth < 15) { if (container.offsetWidth > 0) { stageContainer = container; break; } container = container.parentElement; depth++; } if (!stageContainer) { console.error("❌ No valid stage container found."); return; } // ─── Function: Wait until Storyline’s layout is ready ───────────── function waitForLayout(callback) { let tries = 0; function check() { if (character.offsetWidth > 0 && stageContainer.offsetWidth > 0) { // Optionally confirm that GetPlayer exists before invoking SetVar if (window.GetPlayer && typeof window.GetPlayer().SetVar === 'function') { callback(); } else if (tries++ < 30) { setTimeout(check, 50); } else { console.error("❌ Storyline player never initialized. Proceeding anyway."); callback(); } } else if (tries++ < 30) { setTimeout(check, 50); } else { console.error("❌ Layout never stabilized."); } } check(); } // ─── After layout stabilizes, center character and initialize state ─ waitForLayout(() => { const stageWidth = stageContainer.offsetWidth; const charWidth = character.offsetWidth; const centerX = stageWidth / 2 - charWidth / 2; // Center character horizontally and make it visible gsap.set(character, { left: centerX }); character.style.visibility = 'visible'; // Set initial Storyline variable: characterState = 'idleRight' if (window.GetPlayer && typeof window.GetPlayer().SetVar === 'function') { try { GetPlayer().SetVar(STATE_VAR_NAME, 'idleRight'); } catch (err) { console.warn(`⚠️ Failed to set initial ${STATE_VAR_NAME}: ${err.message}`); } } // ─── Helper: get current left position of character (in px) ───── function getCharacterLeft() { return parseFloat(character.style.left || '0') || 0; } // ─── handleMove(direction): updates position & acceleration ───── function handleMove(direction) { const charLeft = getCharacterLeft(); // If we just started moving or changed direction → update storyboard state if (!isMoving || direction !== lastDirection) { if (window.GetPlayer && typeof window.GetPlayer().SetVar === 'function') { try { const state = direction === 'left' ? 'walkLeft' : 'walkRight'; GetPlayer().SetVar(STATE_VAR_NAME, state); } catch (err) { console.warn(`⚠️ Failed to set ${STATE_VAR_NAME} to ${direction}: ${err.message}`); } } lastDirection = direction; isMoving = true; } // Compute new left value const newCharLeft = direction === 'left' ? charLeft - currentSpeed : charLeft + currentSpeed; // Animate the character’s left property over 0.05s (for smoothing) gsap.to(character, { duration: 0.05, left: newCharLeft, overwrite: true }); // Increase speed (up to maxSpeed) currentSpeed = Math.min(maxSpeed, currentSpeed + accelerationStep); } // ─── stopMove(): clear interval, reset speed, switch to idle pose ── function stopMove() { if (moveInterval) { clearInterval(moveInterval); moveInterval = null; currentSpeed = moveSpeedBase; if (isMoving) { if (window.GetPlayer && typeof window.GetPlayer().SetVar === 'function') { try { const idleState = lastDirection === 'left' ? 'idleLeft' : 'idleRight'; GetPlayer().SetVar(STATE_VAR_NAME, idleState); } catch (err) { console.warn(`⚠️ Failed to set idle ${STATE_VAR_NAME}: ${err.message}`); } } isMoving = false; lastDirection = lastDirection || 'right'; } } } // ─── Keydown listener: start moveInterval on ← or → ─────────────── document.addEventListener('keydown', function (e) { const key = e.key.toLowerCase(); let direction = null; if (key === 'arrowleft') direction = 'left'; if (key === 'arrowright') direction = 'right'; if (direction && !moveInterval) { // Run handleMove(direction) every 20ms → smooth continuous movement moveInterval = setInterval(() => handleMove(direction), 20); e.preventDefault(); } }); // ─── Keyup listener: stop movement on key release ──────────────── document.addEventListener('keyup', function (e) { const key = e.key.toLowerCase(); if (key === 'arrowleft' || key === 'arrowright') { stopMove(); e.preventDefault(); } }); }); } catch (err) { console.error(`❌ Script failed during initialization: ${err.message}`); console.error(err.stack); } })();
Key Customization Points in the Code
The JavaScript includes several settings you can easily adjust:
- moveSpeedBase = 2: Starting movement speed (lower = slower start)
- accelerationStep = 0.5: How quickly speed increases (lower = more gradual)
- maxSpeed = 15: Maximum movement speed (higher = faster top speed)
- STATE_VAR_NAME = ‘characterState’: Must match your Storyline variable name exactly
- MODEL_IDS.character: Replace with your actual data-model-id
Important: Don’t forget to replace ‘PASTE_YOUR_ID_HERE’ with the actual data-model-id you copied from your character object!
Step 5: Add the JavaScript for Character Movement
Get Your Character’s Data-Model-ID
Storyline assigns a unique identifier to each object. To make our JavaScript work, we need to find your character’s ID:
- Right-click your “Character” object in Storyline
- Look at the top of the context menu – you’ll see the data-model-id displayed
- Click the copy option next to the ID to copy it to your clipboard
- Keep this ID handy – you’ll paste it into the JavaScript code
This new Storyline feature makes it much easier than the old method of inspecting published HTML!
How to Add the JavaScript
- Go to the Triggers panel in Storyline
- Create a new trigger:
- Action: Execute JavaScript
- When: Timeline starts on this slide
- Script: Paste the complete code above
- Save and publish your project
Step 6: Test and Troubleshoot
Quick Test Checklist
- Publish and open your slide in Chrome or Firefox
- Check the console (F12) for any error messages
- Verify the character appears centered on the slide in idle pose
- Press the right arrow – character should start walking right and speed up
- Release the key – character should stop and return to idle
- Try the left arrow – same behavior but facing left
Common Issues and Fixes
Character not appearing:
- Check that your data-model-id matches exactly
- Make sure GSAP is loaded before the script runs
- Verify your character container is visible on the slide
States not changing:
- Double-check that your variable is named “characterState” exactly
- Make sure all four state-change triggers are set up correctly
- Confirm your states are named IdleRight, IdleLeft, WalkRight, WalkLeft
Jerky animation:
- Verify all PNG frames are exactly the same width
- Check that your APNG loops smoothly when viewed alone
- Make sure frame alignment was done correctly
Keys not working:
- Ensure the slide has focus (click on it first)
- Check browser console for JavaScript errors
- Try refreshing the page
Final Checklist
Before showing your finished character to others:
- All PNG frames are exactly the same width
- APNG loops smoothly when viewed alone
- Storyline variable “characterState” exists
- Four state-change triggers are set up correctly
- Data-model-id in JavaScript matches your character
- GSAP library is loaded before your script
- Character appears centered and responds to arrow keys
- States change properly (walking when moving, idle when stopped)
- No console errors appear in browser developer tools
What’s Next?
Once you have this basic setup working, you can expand it with:
- Background scrolling that moves opposite to the character
- Jump and attack animations triggered by other keys
- Touch controls for mobile devices
- Multiple characters or interactive objects
- Collision detection with boundaries or obstacles
The foundation you’ve built here – combining Storyline’s state management with JavaScript’s movement logic – can support much more complex interactive experiences.
Happy animating!