skip to Main Content

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>

2

Answers


  1. Chosen as BEST ANSWER

    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.


  2. You’ll have to use none if you’re moving the element by setting the animation.

    Checking the docs on none:

    none

    The animation will not apply any styles to the target when it’s not executing. The element will instead be displayed using any other CSS rules applied to it. This is the default value.

    That’s exactly what you need. The animation.onfinish is also not needed.

    The second drag will still do the performSnapback to x=0 and y=0 since you’re not yet updating lastPos.

    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 performSnapback2(elm) {
      const opts = { duration: 300, fill: 'none' };
      const keyframes = [{ transform: `translate(${lastPos.x}px, ${lastPos.y}px)` }]  
      elm.animate(keyframes, opts)
    }
    
    piece.onpointerdown = function(e) {
      dragging = true
      e.target.setPointerCapture(e.pointerId)
      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'
      performSnapback2(e.target)
    }
    
    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; z-index: 1; }
    <section>
      <div style="transform: translate(0px, 0px)"></div>
    </section>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search