Introduction to interactive elements
Modern interfaces no longer just display static content. Users expect smooth and engaging interactions that respond to their actions naturally and intuitively.
In this complete guide, we'll explore 6 essential techniques for creating interactive elements in pure JavaScript: from native drag and drop to magnetic effects, through elastic animations and shape morphing.
All these techniques work in vanilla JavaScript, without any external dependency. They primarily use the mousedown, mousemove and mouseup events combined with requestAnimationFrame for smooth animations.
Each example comes with an interactive demo and complete code that you can copy and adapt to your projects. The presented techniques are compatible with all modern browsers.
1. Native Drag and Drop
Drag and drop is one of the most intuitive interactions. Allowing users to move elements with the mouse creates a tactile and engaging experience, even on desktop.
Here is a simple but effective implementation that correctly handles the container boundaries:
function makeDraggable(element, container) {
let isDragging = false;
let startX, startY, initialX, initialY;
element.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
initialX = element.offsetLeft;
initialY = element.offsetTop;
element.classList.add('dragging');
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const dx = e.clientX - startX;
const dy = e.clientY - startY;
// Bounds calculation
const maxX = container.offsetWidth - element.offsetWidth;
const maxY = container.offsetHeight - element.offsetHeight;
let newX = Math.max(0, Math.min(maxX, initialX + dx));
let newY = Math.max(0, Math.min(maxY, initialY + dy));
element.style.left = newX + 'px';
element.style.top = newY + 'px';
});
document.addEventListener('mouseup', () => {
isDragging = false;
element.classList.remove('dragging');
});
}
Key implementation points
- Offset calculation: We store the initial position on mousedown to calculate the relative displacement
- Container boundaries: Math.max and Math.min ensure the element stays within its zone
- Listen on document: mousemove and mouseup on document to avoid losing the drag if the cursor leaves the element
2. Magnetic elements
The magnetic effect attracts an element toward the cursor when it approaches. It's a very popular technique for CTA buttons as it creates a unique sense of interactivity.
function makeMagnetic(element, zone, strength = 0.35, radius = 120) {
zone.addEventListener('mousemove', (e) => {
const rect = element.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const deltaX = e.clientX - centerX;
const deltaY = e.clientY - centerY;
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance < radius) {
// The closer the cursor, the stronger the attraction
const factor = 1 - (distance / radius);
const moveX = deltaX * strength * factor;
const moveY = deltaY * strength * factor;
element.style.transform = `translate(${moveX}px, ${moveY}px)`;
} else {
element.style.transform = 'translate(0, 0)';
}
});
zone.addEventListener('mouseleave', () => {
element.style.transform = 'translate(0, 0)';
});
}
strength (0.2-0.5) controls the displacement intensity. radius (80-150px) defines the activation zone. Experiment to find the right balance between spectacular effect and usability.
3. Elastic drag effect
The elastic effect adds realistic physics to drag and drop: the element follows the cursor with a slight delay and a bounce effect, as if attached by an elastic band.
function makeElasticDrag(element, container) {
let isDragging = false;
let targetX = 0, targetY = 0;
let currentX = 0, currentY = 0;
let velocityX = 0, velocityY = 0;
const spring = 0.15; // Spring force
const friction = 0.75; // Amortissement
function animate() {
// Spring physics
const dx = targetX - currentX;
const dy = targetY - currentY;
velocityX += dx * spring;
velocityY += dy * spring;
velocityX *= friction;
velocityY *= friction;
currentX += velocityX;
currentY += velocityY;
element.style.transform = `translate(${currentX}px, ${currentY}px)`;
requestAnimationFrame(animate);
}
animate();
let offsetX, offsetY;
element.addEventListener('mousedown', (e) => {
isDragging = true;
const rect = container.getBoundingClientRect();
offsetX = e.clientX - rect.left - currentX;
offsetY = e.clientY - rect.top - currentY;
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
const rect = container.getBoundingClientRect();
targetX = e.clientX - rect.left - offsetX;
targetY = e.clientY - rect.top - offsetY;
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
}
Spring physics
This technique uses a simplified spring physics model:
- spring (0.1-0.3): The higher the value, the faster the element follows
- friction (0.7-0.9): Controls damping, a low value = more bounces
- requestAnimationFrame: Ensures 60fps animation for optimal smoothness
4. 3D rotation following the cursor
The 3D rotation effect (or "tilt effect") rotates an element based on the cursor position, creating a very immersive illusion of depth.
function makeTiltable(element, maxAngle = 20) {
element.addEventListener('mousemove', (e) => {
const rect = element.getBoundingClientRect();
// Relative cursor position (-0.5 to 0.5)
const x = (e.clientX - rect.left) / rect.width - 0.5;
const y = (e.clientY - rect.top) / rect.height - 0.5;
// Inverted rotation for a natural effect
const rotateY = x * maxAngle;
const rotateX = -y * maxAngle;
element.style.transform = `
perspective(1000px)
rotateX(${rotateX}deg)
rotateY(${rotateY}deg)
scale3d(1.05, 1.05, 1.05)
`;
});
element.addEventListener('mouseleave', () => {
element.style.transform = 'perspective(1000px) rotateX(0) rotateY(0)';
});
}
3D transformations are GPU-accelerated, but avoid applying this effect to too many elements simultaneously. For long lists, activate the effect only on the hovered element.
5. Shape morphing on hover
Shape morphing allows smoothly transforming an element from one shape to another. Combined with clip-path or border-radius, you can create fascinating organic transitions.
Click on the shapes below to see them transform:
.morph-shape {
width: 100px;
height: 100px;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
cursor: pointer;
transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.morph-shape.circle {
border-radius: 50%;
}
.morph-shape.square {
border-radius: 16px;
}
.morph-shape.rounded {
border-radius: 30%;
}
.morph-shape.blob {
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
}
/* Continuous animation for the blob */
@keyframes blobMorph {
0%, 100% {
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
}
50% {
border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%;
}
}
const shapes = ['circle', 'square', 'rounded', 'blob'];
function makeMorphable(element) {
let currentIndex = shapes.findIndex(
s => element.classList.contains(s)
);
element.addEventListener('click', () => {
// Remove current class
element.classList.remove(shapes[currentIndex]);
// Move to next shape
currentIndex = (currentIndex + 1) % shapes.length;
// Add new class
element.classList.add(shapes[currentIndex]);
});
}
6. Creative combinations
The real magic happens when you combine multiple effects. Here is an element that integrates drag, 3D rotation and morphing on hover:
function makeComboInteractive(element, container) {
let isDragging = false;
let posX = 0, posY = 0;
let rotateX = 0, rotateY = 0;
function updateTransform() {
element.style.transform = `
translate(${posX}px, ${posY}px)
perspective(800px)
rotateX(${rotateX}deg)
rotateY(${rotateY}deg)
`;
}
// Drag
let startX, startY, startPosX, startPosY;
element.addEventListener('mousedown', (e) => {
isDragging = true;
startX = e.clientX;
startY = e.clientY;
startPosX = posX;
startPosY = posY;
element.style.borderRadius = '50%'; // Morph to circle
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
posX = startPosX + (e.clientX - startX);
posY = startPosY + (e.clientY - startY);
// Tilt during drag
rotateY = (e.clientX - startX) * 0.1;
rotateX = -(e.clientY - startY) * 0.1;
updateTransform();
}
});
document.addEventListener('mouseup', () => {
isDragging = false;
rotateX = 0;
rotateY = 0;
element.style.borderRadius = '20px'; // Back to square
updateTransform();
});
}
Best practices
Before deploying these effects in production, keep these recommendations in mind:
Performance
- Use transform instead of top/left for animations (GPU accelerated)
- requestAnimationFrame for continuous animations
- will-change sparingly on elements that will be animated
- Throttle mousemove events if necessary
Accessibility
- prefers-reduced-motion: Disable animations for sensitive users
- Focus visible: Ensure elements remain keyboard accessible
- ARIA: Add appropriate attributes for screen readers
@media (prefers-reduced-motion: reduce) {
.draggable,
.magnetic-btn,
.tilt-card,
.morph-shape {
transition: none !important;
animation: none !important;
}
}
Touch support
Don't forget to add touch support for mobile devices:
// Add touch events alongside mouse events
element.addEventListener('touchstart', handleStart, { passive: false });
element.addEventListener('touchmove', handleMove, { passive: false });
element.addEventListener('touchend', handleEnd);
function handleStart(e) {
e.preventDefault();
const touch = e.touches[0];
// Use touch.clientX and touch.clientY
}
Conclusion
Interactive elements transform an ordinary interface into a memorable experience. By mastering drag and drop, magnetic effects, elastic animations and morphing, you have a complete arsenal for creating unique interfaces.
The important thing is to use these effects sparingly: too many interactions can become tiring. Use them strategically on your key elements (CTAs, important cards, navigation) to maximize their impact.
Find 50+ interactive effects ready to use in our effects library, with copyable and customizable code.