<ol id="$demo">
  <li draggable="true" style="view-transition-name: card-1">CSS</li>
  <li draggable="true" style="view-transition-name: card-2">HTML</li>
  <li draggable="true" style="view-transition-name: card-3">JS</li>
  <li draggable="true" style="view-transition-name: yes-this-keeps-animating-during-morph">
    <picture>
      <source srcset="https://fonts.gstatic.com/s/e/notoemoji/latest/1f92f/512.webp" type="image/webp">
      <img src="https://fonts.gstatic.com/s/e/notoemoji/latest/1f92f/512.gif" alt="🤯" width="125" height="125">
    </picture>
  </li>
  <li draggable="true" style="view-transition-name: a-really-long-list-item-for-funsies">If you're gonna build a time machine into a car, why not do it with some style?</li>
</ol> 
@import "https://unpkg.com/open-props" layer(design.system);
@import "https://unpkg.com/open-props/normalize.min.css" layer(demo.support);

@layer demo {
  @keyframes drop-zone {
    from { box-shadow: 0 0 0 0 var(--link) }
    to   { box-shadow: 0 0 0 25px #0000 }
  }
  
  li.over {
    outline: 2px dashed var(--link);
    
    @media (prefers-reduced-motion: no-preference) {
      animation: drop-zone 1.5s var(--ease-out-5) infinite;
    }
    
    > * {
      pointer-events: none;
    }
  }
  
  [draggable] {
    cursor: grab;
    user-select: none;
    
    &:active {
      cursor: grabbing;
    }
  }
  
  /* 
    optimization: 
    disguises the crossfade on the gif, 
    show the live gif always 
  */
  ::view-transition-old(yes-this-keeps-animating-during-morph) {
    display: none;
  }
  ::view-transition-new(yes-this-keeps-animating-during-morph) {
    animation: none;
  }
  html {
    view-transition-name: none;
  }
}

@layer demo.support {
  body {
    display: grid;
    place-content: center;
    padding: var(--size-5);
    gap: var(--size-5);
  }
  
  ol {
    display: grid;
    gap: var(--size-3);
    justify-items: start;
  }
  
  ol > li {
    background: var(--surface-2);
    border: 1px solid transparent;
    padding-block: var(--size-3);
    padding-inline: var(--size-4);
    border-radius: var(--radius-3);
    box-shadow: var(--shadow-4);
    
    @media (prefers-color-scheme: light) {
      background: white;
    }
  }
}
const drag = {
  src:   null,
  items: $demo.querySelectorAll(':scope > li'),
}

function handleDrop(e) {
  e?.stopPropagation()

  if (drag.src != this) {
    if (document.startViewTransition)
      document.startViewTransition(() => 
        swapSiblings(drag.src, this))
    else 
      swapSiblings(drag.src, this)
  }
}

function handleDragStart(e) {
  requestAnimationFrame(() => {
    this.style.opacity = '.4'
  })
  e.dataTransfer.setData("text/html", this.outerHTML)
  drag.src = this
  // e.dataTransfer.effectAllowed = 'move'
}

function handleDragOver(e) {
  e?.preventDefault()
  // e.dataTransfer.dropEffect = 'move'
}

function handleDragEnter(e) {
  this.classList.add('over')
}

function handleDragLeave(e) {
  this.classList.remove('over')
}

function handleDragEnd(e) {
  this.style.opacity = '1'

  drag.items.forEach(item => {
    item.classList.remove('over')
  })
}

function swapSiblings(sib1, sib2) {
  let p1 = sib1.previousSibling
  let p2 = sib2.previousSibling

  p1.after(sib2)
  p2.after(sib1)
}

drag.items.forEach(item => {
  item.addEventListener('dragstart', handleDragStart, false)
  item.addEventListener('dragenter', handleDragEnter, false)
  item.addEventListener('dragover', handleDragOver, false)
  item.addEventListener('dragleave', handleDragLeave, false)
  item.addEventListener('drop', handleDrop, false)
  item.addEventListener('dragend', handleDragEnd, false)
})
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.