One common challenge in Storyline development is randomizing the position of drag and drop objects. While Storyline has built-in randomization for question banks and quiz questions, there isn't a native way to randomize the position of drag and drop objects. This can be particularly challenging when dealing with many drag items - imagine manually repositioning 95 objects every time you want a different arrangement!
In this post, I'll share a JavaScript solution that automatically randomizes the position of drag objects while maintaining their drag and drop functionality.
A community member recently described this challenge:
"I have a drag and drop quiz with 95 drag options that are then added to a chart. This is all working beautifully and tiling onto the chart very nicely, but if I want to randomize the order that the 95 options appear in (on the drag sequence menu) then I have to manually drag them around to make them appear randomized."
We can solve this using JavaScript to:
window.randomizeDraggablePositions = function() {
// Get all draggable elements
const draggables = document.querySelectorAll('.draggable');
// Extract current positions and store elements
const positions = Array.from(draggables).map(el => {
console.log(el);
// Extract transform values using regex
const transform = el.style.transform;
const translateMatch = transform.match(/translate\((\d+)px,\s*(\d+)px\)/);
if (!translateMatch) return null;
return {
element: el,
x: parseInt(translateMatch[1]),
y: parseInt(translateMatch[2]),
// Preserve other transform values
rotation: (transform.match(/rotate\(([-\d.]+)deg\)/) || ['', '0'])[1],
scale: (transform.match(/scale\(([-\d.]+),\s*([-\d.]+)\)/) || ['', '1'])[1]
};
}).filter(pos => pos !== null);
// Create array of positions
const availablePositions = positions.map(pos => ({
x: pos.x,
y: pos.y
}));
// Shuffle positions array
for (let i = availablePositions.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[availablePositions[i], availablePositions[j]] =
[availablePositions[j], availablePositions[i]];
}
// Apply new positions while maintaining other transform properties
positions.forEach((pos, index) => {
const newPos = availablePositions[index];
console.log("newPos", newPos);
const newTransform = `translate(${newPos.x}px, ${newPos.y}px) rotate(${pos.rotation}deg) scale(${pos.scale}, ${pos.scale})`;
pos.element.style.transform = newTransform;
});
}
function initializeDraggable() {
const draggables = document.querySelectorAll('.draggable');
draggables.forEach(draggable => {
let offsetX, offsetY;
let currentX, currentY;
let rotation, scale;
function handleDragStart(e) {
e.preventDefault();
// Extract current transform values
const transform = draggable.style.transform;
const translateMatch = transform.match(/translate\((\d+)px,\s*(\d+)px\)/);
rotation = (transform.match(/rotate\(([-\d.]+)deg\)/) || ['', '0'])[1];
scale = (transform.match(/scale\(([-\d.]+),\s*([-\d.]+)\)/) || ['', '1'])[1];
if (translateMatch) {
currentX = parseInt(translateMatch[1]);
currentY = parseInt(translateMatch[2]);
}
// Calculate offset between mouse and element position
offsetX = e.clientX - currentX;
offsetY = e.clientY - currentY;
// Add move and up listeners
document.addEventListener('mousemove', handleDragMove);
document.addEventListener('mouseup', handleDragEnd);
// Add grabbing cursor
draggable.style.cursor = 'grabbing';
}
function handleDragMove(e) {
e.preventDefault();
// Calculate new position based on mouse position minus offset
const newX = e.clientX - offsetX;
const newY = e.clientY - offsetY;
// Update element position while maintaining rotation and scale
draggable.style.transform = `translate(${newX}px, ${newY}px) rotate(${rotation}deg) scale(${scale}, ${scale})`;
}
function handleDragEnd() {
// Remove move and up listeners
document.removeEventListener('mousemove', handleDragMove);
document.removeEventListener('mouseup', handleDragEnd);
// Reset cursor
draggable.style.cursor = 'grab';
}
// Add mousedown listener
draggable.addEventListener('mousedown', handleDragStart);
// Set initial grab cursor
draggable.style.cursor = 'grab';
});
}
// Initialize everything
initializeDraggable();
window.randomizeDraggablePositions();
window.randomizeDraggablePositions()
The solution has two main components:
This solution provides an efficient way to randomize drag object positions in Storyline, saving significant development time and enabling more dynamic interactions. Whether you're creating assessments, interactive charts, or sorting activities, this code can help create more engaging and varied experiences for your learners.
Remember to test thoroughly with your specific use case, as drag and drop behaviors can be complex and may need adjustments based on your particular requirements.
Feel free to reach out if you need help implementing this solution or run into any issues!