32 CSS Floating Action Button Designs 12 / 32

Draggable FAB

Fully draggable floating action button with corner snapping, touch and mouse support, particle trail effect, and position indicator.

CSS + JS MIT licensed
Live Demo Open in tab
Open in playground

The code

<div class="fb12">
<!-- corner snap zones -->
<div class="fb12-snap-zone" id="fb12-sz-tl">TL</div>
<div class="fb12-snap-zone" id="fb12-sz-tr">TR</div>
<div class="fb12-snap-zone" id="fb12-sz-bl">BL</div>
<div class="fb12-snap-zone" id="fb12-sz-br">BR</div>

<div id="fb12-pos-label"></div>

<div class="fb12-page-mock">
  <h1>Draggable<br>Floating Button</h1>
  <p>Grab the indigo FAB and drag it anywhere on screen. It snaps to the nearest corner when released. Touch and mouse supported.</p>

  <div class="fb12-hint-grid">
    <div class="fb12-hint-card">
      <div class="fb12-icon">✋</div>
      <span>Drag anywhere on screen</span>
    </div>
    <div class="fb12-hint-card">
      <div class="fb12-icon">🎯</div>
      <span>Snaps to nearest corner</span>
    </div>
    <div class="fb12-hint-card">
      <div class="fb12-icon">📱</div>
      <span>Touch &amp; mouse support</span>
    </div>
  </div>
</div>

<!-- DRAGGABLE FAB -->
<div id="fb12-drag-fab" role="button" aria-label="Draggable action button" tabindex="0">
  <div class="fb12-drag-btn">
    <svg viewBox="0 0 24 24"><path d="M19 11h-6V5h-2v6H5v2h6v6h2v-6h6z"/></svg>
  </div>
</div>
</div>
.fb12, .fb12 *, .fb12 *::before, .fb12 *::after { box-sizing: border-box; margin: 0; padding: 0; }
.fb12 {
    font-family: 'Inter', sans-serif;
    background: #f1f5f9;
    min-height: 100vh;
    overflow: hidden;
    color: #334155;
    display: grid;
    place-items: center;
    position: relative;
  }

  /* ── page mockup ── */
  .fb12-page-mock {
    width: min(720px, 100%);
    padding: 32px 24px;
    text-align: center;
  }
  .fb12-page-mock h1 {
    font-size: clamp(1.8rem, 5vw, 2.6rem);
    font-weight: 700;
    letter-spacing: -.025em;
    color: #0f172a;
    margin-bottom: 14px;
  }
  .fb12-page-mock p {
    color: #64748b;
    font-size: .95rem;
    line-height: 1.65;
    max-width: 46ch;
    margin: 0 auto 32px;
  }

  .fb12-hint-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 14px;
    max-width: 480px;
    margin: 0 auto;
  }
  .fb12-hint-card {
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 14px;
    padding: 16px 12px;
    text-align: center;
    box-shadow: 0 1px 4px rgba(0,0,0,.05);
  }
  .fb12-hint-card .fb12-icon { font-size: 1.6rem; margin-bottom: 8px; }
  .fb12-hint-card span { font-size: .76rem; color: #64748b; font-weight: 500; display: block; }

  /* ── DRAGGABLE FAB ── */
  #fb12-drag-fab {
    position: fixed;
    bottom: 32px;
    right: 32px;
    z-index: 1000;
    touch-action: none;
    user-select: none;
    cursor: grab;
  }
  #fb12-drag-fab.fb12-dragging { cursor: grabbing; }

  .fb12-drag-btn {
    width: 64px;
    height: 64px;
    border-radius: 50%;
    background: linear-gradient(135deg, #6366f1, #4f46e5);
    border: none;
    display: grid;
    place-items: center;
    box-shadow:
      0 8px 28px rgba(99,102,241,.45),
      inset 0 1px 0 rgba(255,255,255,.2);
    transition: transform .18s cubic-bezier(.34,1.56,.64,1), box-shadow .18s ease;
    pointer-events: none;
    position: relative;
  }
  #fb12-drag-fab:not(.fb12-dragging):hover .fb12-drag-btn {
    transform: scale(1.08);
    box-shadow: 0 12px 36px rgba(99,102,241,.6), inset 0 1px 0 rgba(255,255,255,.2);
  }
  #fb12-drag-fab.fb12-dragging .fb12-drag-btn {
    transform: scale(1.12);
    box-shadow: 0 18px 48px rgba(99,102,241,.55), 0 4px 8px rgba(0,0,0,.2), inset 0 1px 0 rgba(255,255,255,.2);
    cursor: grabbing;
  }
  .fb12-drag-btn svg { width: 26px; height: 26px; fill: #fff; pointer-events: none; }

  /* drag handle dots */
  .fb12-drag-btn::before {
    content: '⋮⋮';
    position: absolute;
    top: -5px;
    left: 50%;
    transform: translateX(-50%);
    font-size: .6rem;
    color: rgba(255,255,255,.5);
    letter-spacing: 2px;
    pointer-events: none;
  }

  /* ripple trail */
  .fb12-trail {
    position: fixed;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: rgba(99,102,241,.35);
    pointer-events: none;
    transform: translate(-50%,-50%);
    animation: fb12-trail-fade .6s ease-out forwards;
    z-index: 999;
  }
  @keyframes fb12-trail-fade {
    0%  { transform: translate(-50%,-50%) scale(1); opacity: .5; }
    100% { transform: translate(-50%,-50%) scale(.1); opacity: 0; }
  }

  /* position indicator */
  #fb12-pos-label {
    position: fixed;
    top: 16px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(15,23,42,.85);
    backdrop-filter: blur(8px);
    color: rgba(255,255,255,.7);
    font-size: .72rem;
    font-weight: 600;
    letter-spacing: .04em;
    padding: 7px 16px;
    border-radius: 20px;
    z-index: 1001;
    font-family: monospace;
    opacity: 0;
    transition: opacity .2s;
  }
  #fb12-pos-label.fb12-visible { opacity: 1; }

  /* snap zones hint (corners) */
  .fb12-snap-zone {
    position: fixed;
    width: 80px;
    height: 80px;
    border: 2px dashed rgba(99,102,241,.2);
    border-radius: 50%;
    pointer-events: none;
    z-index: 998;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: .6rem;
    color: rgba(99,102,241,.3);
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    transition: border-color .2s, color .2s;
  }
  .fb12-snap-zone.fb12-active { border-color: rgba(99,102,241,.6); color: rgba(99,102,241,.7); }
  #fb12-sz-tl { top: 16px;    left: 16px; }
  #fb12-sz-tr { top: 16px;    right: 16px; }
  #fb12-sz-bl { bottom: 16px; left: 16px; }
  #fb12-sz-br { bottom: 16px; right: 16px; }

  @media (prefers-reduced-motion: reduce) {
    .fb12-drag-btn { transition: none; }
    .fb12-trail { animation: none; }
  }
(function() {
  const fab = document.getElementById('fb12-drag-fab');
  const posLabel = document.getElementById('fb12-pos-label');
  const SNAP = 24; // px from edge
  const TRAIL = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  let isDragging = false, startX, startY, fabX, fabY, lastMoveX, lastMoveY;

  // Set initial position
  fabX = window.innerWidth - fab.offsetWidth - 32;
  fabY = window.innerHeight - fab.offsetHeight - 32;
  setPos(fabX, fabY);

  function setPos(x, y) {
    x = Math.max(SNAP, Math.min(x, window.innerWidth - fab.offsetWidth - SNAP));
    y = Math.max(SNAP, Math.min(y, window.innerHeight - fab.offsetHeight - SNAP));
    fab.style.left = x + 'px';
    fab.style.top  = y + 'px';
    fab.style.right = 'auto';
    fab.style.bottom = 'auto';
    fabX = x; fabY = y;
  }

  function snapToCorner() {
    const cx = fabX + fab.offsetWidth / 2;
    const cy = fabY + fab.offsetHeight / 2;
    const W  = window.innerWidth;
    const H  = window.innerHeight;
    const PAD = 28;
    let tx = cx < W/2 ? PAD : W - fab.offsetWidth - PAD;
    let ty = cy < H/2 ? PAD : H - fab.offsetHeight - PAD;
    // animated spring to corner
    fab.style.transition = 'left .35s cubic-bezier(.34,1.56,.64,1), top .35s cubic-bezier(.34,1.56,.64,1)';
    setPos(tx, ty);
    setTimeout(() => { fab.style.transition = ''; }, 400);
  }

  function spawnTrail(x, y) {
    if (TRAIL) return;
    const dot = document.createElement('div');
    dot.className = 'fb12-trail';
    dot.style.left = x + 'px';
    dot.style.top  = y + 'px';
    document.body.appendChild(dot);
    setTimeout(() => dot.remove(), 700);
  }

  function highlightSnap() {
    const cx = fabX + fab.offsetWidth / 2;
    const cy = fabY + fab.offsetHeight / 2;
    const W = window.innerWidth, H = window.innerHeight;
    const isLeft = cx < W/2, isTop = cy < H/2;
    document.getElementById('fb12-sz-tl').classList.toggle('fb12-active', isLeft && isTop);
    document.getElementById('fb12-sz-tr').classList.toggle('fb12-active', !isLeft && isTop);
    document.getElementById('fb12-sz-bl').classList.toggle('fb12-active', isLeft && !isTop);
    document.getElementById('fb12-sz-br').classList.toggle('fb12-active', !isLeft && !isTop);
  }

  // Mouse
  fab.addEventListener('mousedown', e => {
    e.preventDefault();
    isDragging = true;
    fab.classList.add('fb12-dragging');
    startX = e.clientX - fabX;
    startY = e.clientY - fabY;
    posLabel.classList.add('fb12-visible');
  });

  window.addEventListener('mousemove', e => {
    if (!isDragging) return;
    setPos(e.clientX - startX, e.clientY - startY);
    highlightSnap();
    posLabel.textContent = `x:${Math.round(fabX)} y:${Math.round(fabY)}`;
    if (Math.abs(e.clientX - (lastMoveX||e.clientX)) > 12 || Math.abs(e.clientY - (lastMoveY||e.clientY)) > 12) {
      spawnTrail(e.clientX, e.clientY);
      lastMoveX = e.clientX; lastMoveY = e.clientY;
    }
  });

  window.addEventListener('mouseup', () => {
    if (!isDragging) return;
    isDragging = false;
    fab.classList.remove('fb12-dragging');
    snapToCorner();
    posLabel.classList.remove('fb12-visible');
    document.querySelectorAll('.fb12-snap-zone').forEach(z => z.classList.remove('fb12-active'));
  });

  // Touch
  fab.addEventListener('touchstart', e => {
    const t = e.touches[0];
    isDragging = true;
    fab.classList.add('fb12-dragging');
    startX = t.clientX - fabX;
    startY = t.clientY - fabY;
    posLabel.classList.add('fb12-visible');
  }, { passive: true });

  window.addEventListener('touchmove', e => {
    if (!isDragging) return;
    const t = e.touches[0];
    setPos(t.clientX - startX, t.clientY - startY);
    highlightSnap();
    posLabel.textContent = `x:${Math.round(fabX)} y:${Math.round(fabY)}`;
  }, { passive: true });

  window.addEventListener('touchend', () => {
    if (!isDragging) return;
    isDragging = false;
    fab.classList.remove('fb12-dragging');
    snapToCorner();
    posLabel.classList.remove('fb12-visible');
    document.querySelectorAll('.fb12-snap-zone').forEach(z => z.classList.remove('fb12-active'));
  });
})();

Search CodeFronts

Loading…