CSS 宽度转换速度不够快,与 translateX 结合时会导致抖动 UI
CSS width transition not fast enough and causes jittery UI when joined with translateX
代码
上面的codesandbox里面有很多代码,所以我会尽量把相关的部分贴在这里:
基于拖动/平移手势的总 X 方向增量系列,我正在做:
// 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`);
我想要的行为
当我左右拖动内容(使用鼠标或触摸事件)时,我希望内容的右边缘会粘在 window 的右边缘。我知道单独使用 translateX 可以实现超级流畅的动画,但是动画宽度很棘手,因为它会导致回流,影响性能。
我看到的行为
当我左右拖动内容时,内容的右边缘不会粘在window的右边缘。这部分是因为我根据场景使用动态 css-transitions。我有一个用于打开的过渡,一个用于关闭(显示/隐藏侧边栏)。这样做的原因是,如果我根本没有过渡,动画会感觉不稳定,而且非常不流畅。现在的方式更好,但仍然不理想。
我试过的
无 CSS 转换
- 右边的内容贴在window的右边,如我所愿
- 但是我手指后内容左侧的动画是抖动的(我认为发出的触摸事件之间没有补间)
向转换添加过渡 属性
- 左边的内容像黄油一样光滑
- 显示侧边栏时,右侧内容被推到视口外
- 隐藏侧边栏(向左拖动)时,右侧内容被拉入视口,显示背景元素/正文
向变换和宽度属性添加过渡
- 必须根据开/关情况而定
- 我目前做事的方式(如代码框和我应用程序中的实际代码所示)
- translateX 动画不够流畅
- 宽度动画仍然稍微超出屏幕,但不会显示正文/背景
其他注意事项
我目前正在使用 hammerjs 来获取触摸事件/手势——我不必使用这个库,所以如果有更好的方法来实现我想要的,我会洗耳恭听。
我想我最后没试过的是 requestAnimationFrame
?根据我读过的内容,不确定这是否对这里有帮助——但在这一点上,我愿意尝试任何事情。
在继续寻找解决方案后想通了,最终绊倒在这个页面上:MDN's "Using the Web Animations API"。
要点是 css 动画在 css 文件中使用 @keyframes
声明,Web 动画 API 使我们能够调用 .animate
在一个元素上,并允许许多与 css 动画方式相同的选项。
我很高兴我发现了这个 API,因为它具有播放、触发和取消功能,我没有将其用于此边栏动画,但这可能意味着在评估一个动画库,例如 ember-animated,我可以检查它是否使用动画 API 来实现取消,从而减少负载大小。
有效的方法
主要区别是:
- 我摆脱了任何设置
transition
属性 的尝试。 (回到 我试过的方法 下的第一件事)
快速打开和快速关闭行为太快了,因此,使用 Web 动画 API,我能够平滑地设置 translateX 和 width 的动画——即使在我的非高端老款 phone,Sony XPeria XZ1(紧凑型)
// 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`);
}
};
}
代码
上面的codesandbox里面有很多代码,所以我会尽量把相关的部分贴在这里:
基于拖动/平移手势的总 X 方向增量系列,我正在做:
// 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`);
我想要的行为
当我左右拖动内容(使用鼠标或触摸事件)时,我希望内容的右边缘会粘在 window 的右边缘。我知道单独使用 translateX 可以实现超级流畅的动画,但是动画宽度很棘手,因为它会导致回流,影响性能。
我看到的行为
当我左右拖动内容时,内容的右边缘不会粘在window的右边缘。这部分是因为我根据场景使用动态 css-transitions。我有一个用于打开的过渡,一个用于关闭(显示/隐藏侧边栏)。这样做的原因是,如果我根本没有过渡,动画会感觉不稳定,而且非常不流畅。现在的方式更好,但仍然不理想。
我试过的
无 CSS 转换
- 右边的内容贴在window的右边,如我所愿
- 但是我手指后内容左侧的动画是抖动的(我认为发出的触摸事件之间没有补间)
向转换添加过渡 属性
- 左边的内容像黄油一样光滑
- 显示侧边栏时,右侧内容被推到视口外
- 隐藏侧边栏(向左拖动)时,右侧内容被拉入视口,显示背景元素/正文
向变换和宽度属性添加过渡
- 必须根据开/关情况而定
- 我目前做事的方式(如代码框和我应用程序中的实际代码所示)
- translateX 动画不够流畅
- 宽度动画仍然稍微超出屏幕,但不会显示正文/背景
其他注意事项
我目前正在使用 hammerjs 来获取触摸事件/手势——我不必使用这个库,所以如果有更好的方法来实现我想要的,我会洗耳恭听。
我想我最后没试过的是 requestAnimationFrame
?根据我读过的内容,不确定这是否对这里有帮助——但在这一点上,我愿意尝试任何事情。
在继续寻找解决方案后想通了,最终绊倒在这个页面上:MDN's "Using the Web Animations API"。
要点是 css 动画在 css 文件中使用 @keyframes
声明,Web 动画 API 使我们能够调用 .animate
在一个元素上,并允许许多与 css 动画方式相同的选项。
我很高兴我发现了这个 API,因为它具有播放、触发和取消功能,我没有将其用于此边栏动画,但这可能意味着在评估一个动画库,例如 ember-animated,我可以检查它是否使用动画 API 来实现取消,从而减少负载大小。
有效的方法
主要区别是:
- 我摆脱了任何设置
transition
属性 的尝试。 (回到 我试过的方法 下的第一件事) 快速打开和快速关闭行为太快了,因此,使用 Web 动画 API,我能够平滑地设置 translateX 和 width 的动画——即使在我的非高端老款 phone,Sony XPeria XZ1(紧凑型)
// 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`); } }; }