About this snippet

Press on the product image and drag it toward the cart. A semi transparent copy follows your cursor, and dropping it on the cart bumps the counter.

The copy is a real img element created on pointerdown and positioned with fixed coordinates, so it can travel anywhere on the page without being clipped by a parent. The original fades while the drag runs, and the source image disables the browser's native image dragging so it never fights the script.

Dropping is a bounding box test. On pointerup the drop point is compared against the cart's getBoundingClientRect, and only a hit inside those bounds runs the shrink and fly in animation. A miss removes the copy and leaves the count alone. Everything uses pointer events rather than mouse events, so touch works without a second code path.

Size
5.4 KB
Uses
HTML, CSS, JavaScript
Loads
placehold.co
Views
456
<div class="store-layout">
  
  <div class="cart-header">
    <h2>Shop</h2>
    <div class="cart-icon" id="cart-icon">
      <svg viewBox="0 0 24 24" width="28" height="28" fill="none" stroke="currentColor" stroke-width="2"><circle cx="9" cy="21" r="1"></circle><circle cx="20" cy="21" r="1"></circle><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"></path></svg>
      <span class="cart-count" id="cart-count">0</span>
    </div>
  </div>
  
  <div class="product-shelf">
    <div class="product-item">
      <img src="https://placehold.co/200x200/3b82f6/ffffff?text=Sneaker" alt="Shoe" class="draggable-img" id="shoe-img">
      <h3>Air Runners</h3>
      <p>$120.00</p>
      <div class="drag-hint">Drag to cart</div>
    </div>
  </div>
  
</div>
*, *::before, *::after {
  box-sizing: border-box;
}

body {
  font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
  margin: 0;
  background: #f8fafc;
  min-height: 100vh;
  padding: 40px;
}

.store-layout {
  max-width: 800px;
  margin: 0 auto;
}

.cart-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #ffffff;
  padding: 20px 40px;
  border-radius: 20px;
  box-shadow: 0 10px 20px rgba(0,0,0,0.05);
  margin-bottom: 60px;
}

.cart-header h2 {
  margin: 0;
  color: #0f172a;
}

.cart-icon {
  position: relative;
  background: #f1f5f9;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #0f172a;
  transition: transform 0.2s, background 0.2s;
}

.cart-icon.bounce {
  animation: cart-bounce 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
  background: #dbeafe;
  color: #3b82f6;
}

@keyframes cart-bounce {
  0% { transform: scale(1); }
  50% { transform: scale(1.2); }
  100% { transform: scale(1); }
}

.cart-count {
  position: absolute;
  top: -5px;
  right: -5px;
  background: #ef4444;
  color: #ffffff;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
  font-weight: 700;
  font-size: 0.8rem;
  box-shadow: 0 2px 5px rgba(239, 68, 68, 0.4);
}

.product-shelf {
  display: flex;
  justify-content: center;
}

.product-item {
  background: #ffffff;
  padding: 30px;
  border-radius: 20px;
  box-shadow: 0 20px 40px rgba(0,0,0,0.08);
  text-align: center;
  width: 260px;
}

.draggable-img {
  width: 100%;
  height: auto;
  border-radius: 12px;
  cursor: grab;
  margin-bottom: 20px;
  user-select: none;
  /* Prevent native browser drag to avoid bugs */
  -webkit-user-drag: none;
}

.draggable-img:active {
  cursor: grabbing;
}

.product-item h3 {
  margin: 0 0 10px 0;
  color: #0f172a;
}

.product-item p {
  color: #3b82f6;
  font-weight: 700;
  font-size: 1.25rem;
  margin: 0 0 20px 0;
}

.drag-hint {
  background: #f1f5f9;
  color: #64748b;
  padding: 8px 16px;
  border-radius: 30px;
  font-size: 0.9rem;
  font-weight: 600;
  display: inline-block;
}

/* The Ghost image safely generated by JS */
.ghost-item {
  position: fixed;
  pointer-events: none;
  z-index: 9999;
  opacity: 0.8;
  border-radius: 12px;
  box-shadow: 0 20px 40px rgba(0,0,0,0.3);
  transition: none; /* Instant tracking */
}
const sourceImg = document.getElementById("shoe-img");
const cartIcon = document.getElementById("cart-icon");
const cartCount = document.getElementById("cart-count");

let isDragging = false;
let ghost = null;
let currentCount = 0;

function startDrag(e) {
  isDragging = true;
  sourceImg.style.opacity = "0.3";
  
  /* Create exact visual clone flawlessly */
  ghost = document.createElement("img");
  ghost.src = sourceImg.src;
  ghost.className = "ghost-item";
  
  const rect = sourceImg.getBoundingClientRect();
  ghost.style.width = rect.width + "px";
  ghost.style.height = rect.height + "px";
  
  document.body.appendChild(ghost);
  moveGhost(e);
}

function moveGhost(e) {
  if (!ghost) return;
  const rect = ghost.getBoundingClientRect();
  const x = e.clientX - (rect.width / 2);
  const y = e.clientY - (rect.height / 2);
  ghost.style.left = x + "px";
  ghost.style.top = y + "px";
}

function endDrag(e) {
  if (!isDragging) return;
  isDragging = false;
  sourceImg.style.opacity = "1";
  
  if (ghost) {
    const cartRect = cartIcon.getBoundingClientRect();
    const dropX = e.clientX;
    const dropY = e.clientY;
    
    /* Check collision boundaries cleanly */
    if (dropX > cartRect.left && dropX < cartRect.right && dropY > cartRect.top && dropY < cartRect.bottom) {
      /* Trajectory animation directly into cart */
      ghost.style.transition = "all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)";
      ghost.style.left = (cartRect.left + 15) + "px";
      ghost.style.top = (cartRect.top + 15) + "px";
      ghost.style.width = "30px";
      ghost.style.height = "30px";
      ghost.style.opacity = "0";
      
      currentCount++;
      
      setTimeout(function() {
        cartCount.innerText = currentCount;
        cartIcon.classList.add("bounce");
        ghost.remove();
        ghost = null;
        
        setTimeout(function() {
          cartIcon.classList.remove("bounce");
        }, 400);
      }, 400);
    } else {
      /* Drop failed, remove ghost safely */
      ghost.remove();
      ghost = null;
    }
  }
}

sourceImg.addEventListener("pointerdown", startDrag);
window.addEventListener("pointermove", function(e) {
  if (isDragging) moveGhost(e);
});
window.addEventListener("pointerup", endDrag);
window.addEventListener("pointercancel", endDrag);
Esc
↑↓ moveEnter select

Start from a template

Open an .html file, or HTML, CSS, JS, Markdown, Less or TypeScript files. You can also drop them here. Styles and scripts go into their panes.

Share

Source

Keyboard shortcuts

Save