There is a lot of code in that above codesandbox, so I'll try to paste the relevant parts here:
Based on the series of drag / pan gesture's total X-direction delta, I'm doing:
// before setting transform and width, I set the transition
// this way, there will be some tweening that occurs when I do
// change the properties -- relevant to the specific open /
// close situation
if (this.isOpening) {
this.content.style.transition = `width 0.1s linear, transform 0.1s linear`;
} else {
this.content.style.transition = `width 0.0s linear, transform 0.1s linear`;
}
this.content.style.setProperty("--dx", `${nextX}`);
this.content.style.transform = `translateX(${nextX}px)`;
this.resize();
// and then in that resize function
let nextX = parseInt(this.content.style.getPropertyValue("--dx"), 10);
this.content.style.setProperty("width", `${window.innerWidth - nextX}px`);
When I drag the content to the right and left (either with the mouse, or a touch event), I expect that the right edge of the content will stick to the right edge of the window. I know that doing translateX by itself has super smooth animation, but animating width is tricky, as it causes reflows, harming performance.
When I drag the content to the right and left, the right edge of the content does not stick to the right edge of the window. This is partially do to the fact that I'm using dynamic css-transitions depending on the scenario. I have one transition in use for opening, and one for closing (revealing / hiding the sidebar). The reason for this is that if I don't have transitions at all, the animation feels jerky, and very unsmooth. The way it is now is better, but still not not ideal.
No CSS Transitions
Add transition to the transform property
Add transition to the transform and width properties
I'm currently using hammerjs to get touch events / gestures -- I don't have to use this library, so if there is a better way to achieve what I want, I'm all ears.
I guess the last thing I haven't tried is requestAnimationFrame
? not sure if that would help here based on what I've read -- but at this point I'm willing to try anything.
Figured it out after continuing to search for solutions, and eventually stumbling on this page: MDN's "Using the Web Animations API".
The gist of it is that while css animations use the @keyframes
declaration in a css file, the Web Animations API enables us to call .animate
on an element, and allows many of the same options as the css way of animating.
I'm very glad I discovered this API, as it has play, cause, and cancel abilities, which I don't be using for this sidebar animation, but it might mean that when evaluating an animation library, such as ember-animated, I could check to see if it uses the animations API to achieve cancellation, resulting in reduced payload size.
The key differences are:
transition
property. (went back to the first thing under What I've Tried) the snap open and snap closed behavior was too quick, so, using the Web Animations API, I was able to smoothly animate both translateX and width -- even on my non-high end older phone, a Sony XPeria XZ1 (Compact)
// e is a hammer.js Pan event
handleDrag(e) {
// setup and various math omitted
if (e.isFinal) {
// code determining if a "snap" open or shut should happen
// and what that x-axis value should be
// are omitted
// reset state omitted
// call the Web Animations API
return this.finish(nextX);
}
// fallback code for when the gesture is still active
// directly updates the transform property
// (though, I should probably look in to using requestAnimationFrame
// to improve performance on lower-end devices)
this.content.style.transform = `translateX(${nextX}px)`;
this.resize();
}
finish(nextX: number) {
let prevX = this.currentLeft;
let keyFrames = [
{ transform: `translateX(${prevX}px)` },
{ transform: `translateX(${nextX}px)` },
];
// this covers the main scenario from the question here
// adding a width animation to the translate X
// (for medium sized screens and larger, it's important to have both)
// NOTE: isPushing is a boolean that checks for a certain
// screen size -- if the screen is small enough,
// no width animating occurs
if (!this.isPushing) {
keyFrames[0].width = `${window.innerWidth - prevX}px`;
keyFrames[1].width = `${window.innerWidth - nextX}px`;
}
let easing = 'cubic-bezier(0.215, 0.610, 0.355, 1.000)';
let animation = this.content.animate(keyFrames as any /* :( */, { duration: 200, easing });
// without the onfinish, the content element will reappear
// back in its pre-animation position / state.
animation.onfinish = () => {
this.content.style.transform = `translateX(${nextX}px)`;
if (!this.isPushing) {
this.content.style.setProperty('width', `${window.innerWidth - nextX}px`);
}
};
}