Search code examples
javascripthtmlcssdraggableweb-animations

Web animation api fill mode confusion, (affecting draggable element subsequent interaction)


so i have this bare bones example which imitates draggable piece in a chess board.

On pointerup i want to perform snapback animation to piece starting position. And ok, it works if i don't specify fill mode (use fill: 'none') and manually translate the element when animation finishes.

Why does it not work when i have fill: 'forwards' specified? I also tried using commitStyles() after animation finishes to commit those styles to inline css but drag still does not work afterwards. You can see in devtools that piece should be moving but it stays in the place. Fill forwards should keep the state of the final animation frame which is basically where i want my piece to be, not sure why it makes it non draggable after that.

I assume there is some conflict with web animation api styles and my styles, but i can't figure out exactly what is the problem. Is this documented somewhere, i can't find anything about this.

const board = document.querySelector('section')
const piece = document.querySelector('div')

const pieceOffsetSize = 35 // half piece size
const lastPos = { x: 0, y: 0 }
let dragging = false

function translateElement(elm, dx, dy) {
  elm.style.transform = `translate(${dx}px, ${dy}px)`
}

function performSnapback(elm) {
  const opts = { duration: 300 } // no fill mode (fill: 'none')
  const keyframes = [{ transform: `translate(${lastPos.x}px, ${lastPos.y}px)` }]
  const animation = elm.animate(keyframes, opts)

  animation.onfinish = () => {
    translateElement(elm, lastPos.x, lastPos.y) // after finish, i translate element manually
  }
}

function performSnapback2(elm) {
  const opts = { duration: 300, fill: 'forwards' } // tried fill: 'both' also and does not work
  const keyframes = [{ transform: `translate(${lastPos.x}px, ${lastPos.y}px)` }]  
  const animation = elm.animate(keyframes, opts)

  animation.onfinish = () => {
    animation.commitStyles() // tried with and without this and does not work
  }
}

piece.onpointerdown = e => {
  dragging = true
  e.target.setPointerCapture(e.pointerId)
  e.target.style.userSelect = 'none'
  const deltaX = e.clientX - board.offsetLeft - pieceOffsetSize
  const deltaY = e.clientY - board.offsetTop - pieceOffsetSize
  translateElement(e.target, deltaX, deltaY)
}

piece.onpointerup = e => {
  dragging = false
  e.target.releasePointerCapture(e.pointerId)
  e.target.style.userSelect = 'auto'
  // performSnapback(e.target) // WORKS
  performSnapback2(e.target)   // DOES NOT WORK 
}

piece.onpointermove = e => {
  if (!dragging) return
  const deltaX = e.clientX - board.offsetLeft - pieceOffsetSize
  const deltaY = e.clientY - board.offsetTop - pieceOffsetSize
  translateElement(e.target, deltaX, deltaY)
}
body { padding: 0; margin: 0; }
section { width: 400px; height: 400px; outline: 2px solid darkviolet; margin: 100px; position: relative; }
div { position: absolute; background: plum; width: 70px; height: 70px; }
<section>
  <div style="transform: translate(0px, 0px)"></div>
</section>


Solution

  • The answer to the question why this does not work is... nobody knows, like with anything else on the web unlike with things in real world. Because apis are designed with this logic: try and see what happens, imagine buying a washing machine and docs say: press buttons and see what happens (you can't imagine because there is documentation unlike with 1 liners on MDN).

    So using fill forwards blocks subsequent drag functionality because it simply does, i tried and got lucky with other solution.