动画
技术选型
动画常被用于增强应用程序的生动性。现代浏览器对 CSS 动画提供了良好的支持,并且有各种各样的 库可以实现快速的、基于 JavaScript 的动画。如果你对新技术感兴趣,还可以关注即将推出的 Web Animations API 及其 polyfill。
Mithril.js 本身没有提供任何动画 API,因为现有的这些选项已经足以实现丰富且复杂的动画效果。然而,Mithril.js 提供了生命周期钩子函数,在某些传统上难以实现动画的特定情况下,可以简化操作。
元素创建时的动画
当元素被创建时,通过 CSS 为元素添加动画非常简单。只需像往常一样将动画添加到 CSS 类中:
.fancy {
animation: fade-in 0.5s;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}var FancyComponent = {
view: function () {
return m('.fancy', 'Hello world');
},
};
m.mount(document.body, FancyComponent);元素移除时的动画
在移除元素前添加动画的关键在于,必须等待动画结束后才能真正移除该元素。幸运的是,Mithril.js 提供了 onbeforeremove 钩子,允许我们延迟元素的移除。
让我们创建一个 exit 动画,将 opacity 从 1 渐变为 0。
.exit {
animation: fade-out 0.5s;
}
@keyframes fade-out {
from {
opacity: 1;
}
to {
opacity: 0;
}
}现在我们创建一个简单的组件,用于显示和隐藏上一节中创建的 FancyComponent。
var on = true;
var Toggler = {
view: function () {
return [
m(
'button',
{
onclick: function () {
on = !on;
},
},
'Toggle'
),
on ? m(FancyComponent) : null,
];
},
};接下来,让我们修改 FancyComponent,使其在移除时淡出:
var FancyComponent = {
onbeforeremove: function (vnode) {
vnode.dom.classList.add('exit');
return new Promise(function (resolve) {
vnode.dom.addEventListener('animationend', resolve);
});
},
view: function () {
return m('.fancy', 'Hello world');
},
};vnode.dom 指向组件的根 DOM 元素(<div class="fancy">)。我们在这里使用 classList API 将 exit 类添加到 <div class="fancy">。
然后我们返回一个 Promise,该 Promise 会在 animationend 事件触发时变为 resolved 状态。当我们从 onbeforeremove 返回一个 promise 时,Mithril.js 会等待直到 promise 被 resolve,才会移除该元素。在此例中,它会等待退出动画完成。
我们可以通过挂载 Toggler 组件来验证进入和退出动画是否都有效:
m.mount(document.body, Toggler);请注意,onbeforeremove 钩子仅在元素从 DOM 中分离时触发,即当元素失去其 parentNode 时。这种行为的设计目的是为了避免潜在的突兀用户体验,例如页面上所有可能的退出动画都在路由更改时运行。如果退出动画没有生效,请确保将 onbeforeremove 处理程序绑定到尽可能高的层级,以确保动画代码能够被执行。
性能
在创建动画时,建议仅使用 opacity 和 transform CSS 规则,因为这些规则可以由现代浏览器进行硬件加速,并且比动画 top、left、width 和 height 具有更好的性能。
还建议避免使用 box-shadow 规则以及类似 :nth-child 的选择器,因为它们都是资源密集型的选项。如果需要为 box-shadow 添加动画效果,可以考虑将 box-shadow 规则应用于一个伪元素,并修改该伪元素的 opacity 属性。其他可能造成性能开销的因素包括大型或动态缩放的图片,以及 position 属性值不同的重叠元素(例如,一个绝对定位元素覆盖在一个固定定位元素之上)。