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
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.
You’ll have to use
none
if you’re moving the element by setting theanimation
.Checking the docs on
none
:That’s exactly what you need. The
animation.onfinish
is also not needed.The second drag will still do the
performSnapback
tox=0
andy=0
since you’re not yet updatinglastPos
.