Maintaining state is not really that big of a deal using the webanimation api. The element's position is always a property of the element. Even though it wasn't asked of the question to maintain the final position, this is what the method could look like if that were a concern:
Still simpler. I would think the question should have also asked for units to be passed in as an argument to make it far more versatile.
I agree with you though! I would prefer to keep all animation logic in CSS. When I first read the question, I was thinking exactly about that, but I was also wondering what Facebook's goal might be in asking this question. Are they hoping the candidate to be aware of the built-in JS methods? If so, it's a good question to see how well the candidate is keeping his/her knowledge current. Or, maybe it's an opportunity for the candidate who is current to teach the interviewer something new. Is Facebook hoping the candidate to be able to do better than the built-in or will they get worried if the candidate goes down a rabbit hole? Etc.
Well actually it's more about building re-usable function you can use in every context (as a NPM package for example) than a real "Facebook Challenge" (who knows what they are really expecting from it ?)
The problem I can see from yours (even if it's a really simple solution) :
Causing a reflow because of getBoundingClientRect() (yes, performance can be a problem in mobile development)
Has bad position calculation if the element has CSS position relative/absolute/fixed with left parameters assigned.
In fact, this one with same bugs can be rewritten with CSS transition in less lines and better browser support :
// Buggy, please do not use !functionanimate(elm,milliseconds,distance){elm.style.transition=`transform ${milliseconds}ms`;elm.style.transform=`translateX(${elm.getBoundingClientRect().x+distance}px)`;}
So yeah, WebAnimation API is powerful and awesome, but should always be used depending of the context (but It was quite fun to see an actual usage, thanks for it !)
Interesting perspective. I like the points you bring up! Fwiw, I think the API will eventually address those concerns, which I thought would have been addressed by now in 12/2019, but still being worked on (so yes, my mistake, my apologies). Future options I think will preserve state a bit better.
After giving this a bit more thought...I promise not to beat it death but...you led me on the right track and I created fully working code now (code review welcomed!). The one minor thing missed was that translateX alone won't always move the object to the right. I.e., if the object is rotated 45deg, the translateX will move along the rotated X axis, not to the right of the screen by the requested amount (i.e., 200px diagonally would mean sqrt((200)2 / 2) = 141 px to the right instead of 200px). The extra parameters need to be provided to the transformation matrix. So, they were really also testing us a bit on computational geometry, more than just the animation. With a few tweaks to the code, regardless of API used, the following code should still work regardless of initial transform:
function animate(elm, milliseconds, distance) {
console.warn(
'Running an "animate" method that should be named "moveRightOnly"'
);
return new Promise((resolve, reject) => {
// Allow animation to be chained
if (distance < 0 || milliseconds < 0) {
reject(
new Error(
"Feeling negative? Really? This method only moves things to the right. Sorry!"
)
);
}
// I think the Matrix API is neat, but the only reason I'm doing it this long-drawn out way is:
// 1. MDN warns of the API being non-standard, not production-ready
// 2. ES features make it easy enough to do without it
let computedStyle = getComputedStyle(elm).getPropertyValue("transform");
let newStyle
if (computedStyle === "none") {
elm.style.transform = "translate(0px, 0px)";
computedStyle = getComputedStyle(elm).getPropertyValue("transform");
}
const params = computedStyle
.match(/\((.+)\)/)[1]
.split(/,\s*/)
.map(s => parseFloat(s));
if (computedStyle.includes('matrix3d')) {
params[12] += distance
newStyle = `matrix3d(${params.join(', ')})`
} else {
params[4] += distance
newStyle = `matrix(${params.join(', ')})`
}
elm.animate(
[
{
transform: computedStyle
},
{
transform: newStyle
}
],
{
duration: milliseconds,
fill: "forwards"
}
).onfinish = resolve;
});
}
(Alternative, using the MatrixAPI, is here. The translateSelf method had to be replaced with setMatrixValue)
--> If the "test box" lines up with the "goal box" it passes.
I think this code addresses the key concerns that were brought up. I'd be damn impressed if someone could do this in a 15-minute phone screen! (and I'm pretty sure Facebook would be impressed by the questions you brought up!)
Awesome ! You did a really great job !
The only problem I see : they failed on 3D initial parameter (translateZ(1px)) :
The webanimation failed because you handle a 3D matrix as a 2D matrix, making it failed.
The matrix version failed because it reset the 3D matrix to a 2D matrix.
So you need to make use of matrix.is2D (or check for /matrix3d/ to change the algorithm used but its seems quite painful to do.
Here is a solution of the problem without the need of parsing the matrix data (just generate a 2D translation matrix and multiply it to the current matrix), with this solution you don't need to care about the 2D-3D stuff : jsfiddle.net/vthibault/9Lqyphkm/
I updated to include 3d, and also to account for when the animation ends, in case it's desired to chain the animation calls. I still prefer to directly set the parameters instead of performing the multiply operation (which can be a lot of multiply-add operations, and a potential source rounding errors if floating points are used).
For further actions, you may consider blocking this person and/or reporting abuse
We're a place where coders share, stay up-to-date and grow their careers.
Maintaining state is not really that big of a deal using the webanimation api. The element's position is always a property of the element. Even though it wasn't asked of the question to maintain the final position, this is what the method could look like if that were a concern:
Still simpler. I would think the question should have also asked for units to be passed in as an argument to make it far more versatile.
I agree with you though! I would prefer to keep all animation logic in CSS. When I first read the question, I was thinking exactly about that, but I was also wondering what Facebook's goal might be in asking this question. Are they hoping the candidate to be aware of the built-in JS methods? If so, it's a good question to see how well the candidate is keeping his/her knowledge current. Or, maybe it's an opportunity for the candidate who is current to teach the interviewer something new. Is Facebook hoping the candidate to be able to do better than the built-in or will they get worried if the candidate goes down a rabbit hole? Etc.
Working example on jsfiddle
Update 12/11/2019: I provide a more complete example below (thanks to Vincent's reasoning)
Well actually it's more about building re-usable function you can use in every context (as a NPM package for example) than a real "Facebook Challenge" (who knows what they are really expecting from it ?)
The problem I can see from yours (even if it's a really simple solution) :
In fact, this one with same bugs can be rewritten with CSS transition in less lines and better browser support :
So yeah, WebAnimation API is powerful and awesome, but should always be used depending of the context (but It was quite fun to see an actual usage, thanks for it !)
Interesting perspective. I like the points you bring up! Fwiw, I think the API will eventually address those concerns, which I thought would have been addressed by now in 12/2019, but still being worked on (so yes, my mistake, my apologies). Future options I think will preserve state a bit better.
Wow interesting I didn’t know about it!
With future options it’s gonna be a real game-changer!
After giving this a bit more thought...I promise not to beat it death but...you led me on the right track and I created fully working code now (code review welcomed!). The one minor thing missed was that translateX alone won't always move the object to the right. I.e., if the object is rotated 45deg, the translateX will move along the rotated X axis, not to the right of the screen by the requested amount (i.e., 200px diagonally would mean sqrt((200)2 / 2) = 141 px to the right instead of 200px). The extra parameters need to be provided to the transformation matrix. So, they were really also testing us a bit on computational geometry, more than just the animation. With a few tweaks to the code, regardless of API used, the following code should still work regardless of initial transform:
Updated working examples are here: JSFiddle
(Alternative, using the MatrixAPI, is here. The
translateSelf
method had to be replaced withsetMatrixValue
)--> If the "test box" lines up with the "goal box" it passes.
I think this code addresses the key concerns that were brought up. I'd be damn impressed if someone could do this in a 15-minute phone screen! (and I'm pretty sure Facebook would be impressed by the questions you brought up!)
Awesome ! You did a really great job !
The only problem I see : they failed on 3D initial parameter (translateZ(1px)) :
So you need to make use of
matrix.is2D
(or check for/matrix3d/
to change the algorithm used but its seems quite painful to do.Here is a solution of the problem without the need of parsing the matrix data (just generate a 2D translation matrix and multiply it to the current matrix), with this solution you don't need to care about the 2D-3D stuff : jsfiddle.net/vthibault/9Lqyphkm/
I updated to include 3d, and also to account for when the animation ends, in case it's desired to chain the animation calls. I still prefer to directly set the parameters instead of performing the multiply operation (which can be a lot of multiply-add operations, and a potential source rounding errors if floating points are used).