Modern JavaScript UI Tricks for Frontend Developers

Beyond CSS transitions — explore JavaScript-powered UI effects that create genuinely delightful experiences. Magnetic buttons, parallax scrolling, cursor trails, ripple effects, and scroll-triggered animations.

U

UIXplor Team

March 11, 2026 · 8 min read

01Magnetic Button Effect

A magnetic button that attracts toward the cursor creates a satisfying, premium feel used by Awwwards-winning sites:

javascript
const magneticButtons = document.querySelectorAll('.btn-magnetic');

magneticButtons.forEach(btn => {
  btn.addEventListener('mousemove', (e) => {
    const rect = btn.getBoundingClientRect();
    const x = e.clientX - rect.left - rect.width / 2;
    const y = e.clientY - rect.top - rect.height / 2;

    // Attract button toward cursor (30% of distance)
    btn.style.transform = `translate(${x * 0.3}px, ${y * 0.3}px)`;

    // Move text more aggressively for depth (50%)
    const text = btn.querySelector('span');
    if (text) {
      text.style.transform = `translate(${x * 0.5}px, ${y * 0.5}px)`;
    }
  });

  btn.addEventListener('mouseleave', () => {
    btn.style.transform = '';
    const text = btn.querySelector('span');
    if (text) text.style.transform = '';
  });
});

Add smooth transitions in CSS:

css
.btn-magnetic {
  transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1);
}

02Ripple Effect on Click

The Material Design ripple effect provides instant tactile feedback. Here's a clean, framework-free implementation:

javascript
function createRipple(event) {
  const button = event.currentTarget;
  const circle = document.createElement('span');
  const diameter = Math.max(button.clientWidth, button.clientHeight);
  const radius = diameter / 2;

  const rect = button.getBoundingClientRect();
  circle.style.cssText = `
    width: ${diameter}px;
    height: ${diameter}px;
    left: ${event.clientX - rect.left - radius}px;
    top: ${event.clientY - rect.top - radius}px;
    position: absolute;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.3);
    transform: scale(0);
    animation: ripple 600ms linear;
    pointer-events: none;
  `;

  // Remove old ripples
  const existing = button.querySelector('.ripple');
  if (existing) existing.remove();

  circle.classList.add('ripple');
  button.appendChild(circle);
  setTimeout(() => circle.remove(), 700);
}

document.querySelectorAll('.btn-ripple').forEach(btn => {
  btn.style.position = 'relative';
  btn.style.overflow = 'hidden';
  btn.addEventListener('click', createRipple);
});
css
@keyframes ripple {
  to { transform: scale(4); opacity: 0; }
}

03Smooth Scroll Progress Indicator

A reading progress bar that tracks scroll position — useful for blog posts and long pages:

javascript
const progressBar = document.querySelector('.progress-bar');

window.addEventListener('scroll', () => {
  const scrollTop = window.scrollY;
  const docHeight = document.documentElement.scrollHeight - window.innerHeight;
  const progress = (scrollTop / docHeight) * 100;

  // Use requestAnimationFrame to batch DOM writes
  requestAnimationFrame(() => {
    progressBar.style.width = `${Math.min(progress, 100)}%`;
  });
}, { passive: true });
css
.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  height: 3px;
  background: linear-gradient(90deg, #6C63FF, #a78bfa);
  transition: width 0.1s linear;
  z-index: 9999;
}

04Intersection Observer Animations

Replace scroll-event listeners with the modern Intersection Observer API for performant scroll-triggered animations:

javascript
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      entry.target.classList.add('is-visible');
      // Optionally unobserve after first trigger
      observer.unobserve(entry.target);
    }
  });
}, {
  threshold: 0.15,    // Trigger when 15% is visible
  rootMargin: '0px 0px -50px 0px'  // Trigger 50px before the bottom
});

document.querySelectorAll('[data-animate]').forEach(el => {
  observer.observe(el);
});
css
[data-animate] {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}

[data-animate].is-visible {
  opacity: 1;
  transform: translateY(0);
}

Add `data-animate` to any element to give it a smooth fade-up on scroll. The Intersection Observer has zero performance impact on scroll — it uses native browser APIs instead of polling.

05Typewriter Text Effect

A typewriter effect that cycles through multiple strings — great for hero sections:

javascript
class Typewriter {
  constructor(element, strings, options = {}) {
    this.el = element;
    this.strings = strings;
    this.speed = options.speed || 80;
    this.deleteSpeed = options.deleteSpeed || 40;
    this.pause = options.pause || 2000;
    this.currentIndex = 0;
    this.type();
  }

  async type() {
    const str = this.strings[this.currentIndex % this.strings.length];
    this.el.textContent = '';

    // Type forward
    for (let i = 0; i <= str.length; i++) {
      this.el.textContent = str.slice(0, i);
      await this.sleep(this.speed);
    }

    await this.sleep(this.pause);

    // Delete
    for (let i = str.length; i >= 0; i--) {
      this.el.textContent = str.slice(0, i);
      await this.sleep(this.deleteSpeed);
    }

    this.currentIndex++;
    this.type();
  }

  sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
}

// Usage
new Typewriter(
  document.querySelector('.typewriter'),
  ['Build faster.', 'Design better.', 'Ship confidently.']
);