Blog / JavaScript

Interactive Elements in JavaScript: Drag, Magnetic and Morph

Learn to create engaging interactive elements that react to the cursor: drag and drop, magnetic effects, elastic animations and shape morphing. Techniques that transform your interfaces.

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.

💡
Good to know

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:

Drag me
draggable.js
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.

magnetic.js
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)';
  });
}
🎯
Adjustable parameters

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.

Elastic
elastic-drag.js
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.

Move the mouse
tilt-3d.js
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)';
  });
}
⚠️
Performance

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.css
.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%;
  }
}
morph.js
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:

Drag + Tilt + Morph
combo-interactive.js
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
accessibility.css
@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:

touch-support.js
// 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.

🎨
Go further

Find 50+ interactive effects ready to use in our effects library, with copyable and customizable code.