一般,咱们使用的Web应用大多数都只是简单地从一个视图切换到另外一个视图,致使用户体验不直观,可是咱们能够经过技术的手段把这方面的交互行为作得更得体:javascript
在建立UI时,添加合理的UI过渡动效,避免跳转和瞬间移动。若是将生活中的一些天然运动用到UI动效中来,将会给你的用户带来眼前一亮的感受。毕竟,全部与你互动的东西都源自于生活中天然的运动。css
接下来,咱们将一块儿探讨你可能熟悉的某一类有意义的加强用户体验的UI动效。这种技术有一个专业术语,即FLIP(First, Last, Invert, Play)。FLIP技术能够以一种高性能的方式来动态的改变DOM元素的位置和尺寸,而不须要管它的布局是如何计算或渲染的(好比,height、width、float、绝对定位、Flexbox和Grid等)。在改变的过程当中将赋予必定的动效,从而达到咱们所须要的目的,让UI动效更为合理,相应加强用户的体验。html
在移动应用程序中从一个视图移动到另外一个视图时,焦点元素一般会在视图之间从一个位置移动到另外一个位置。好比下面的这些场景:java
若是要让动效更流畅,就要尽可能的让样式计算的数量尽可能的少,并且尽量的快。关键是在动画中尽可能的只使用transform和opacity。只不过,只使用transform和opacity是使人沮丧的。但幸运地是,FLIP可让咱们如何只使用transform来模拟布局变化。web
FLIP是一种记忆设备和技术,最先是由@Paul Lewis提出的,FLIP是First、Last、Invert和Play四个单词首字母的缩写。这里简单的陈述一下:api
First,指的是在任何事情发生以前(过渡以前),记录当前元素的位置和尺寸。可使用getBoundingClientRect()这个API来处理,好比:ide
const first = el.getBoundingClientRect()布局
这句代码用于获取元素的边界 getBoundingClientRect()将会返回一个DOMRect对象,以下图所示:性能
Last:执行一段代码,让元素发生相应的变化,并记录元素在最后状态的位置和尺寸,好比:动画
// 经过给元素添加一个类名,设置元素最后状态的位置和大小
//(在.totes-at-the-end中添加相应的样式规则),布局发生了变化
el.classList.add('totes-at-the-end')
// 记录元素最后状态的位置和尺寸大小
const last = el.getBoundingClientRect()
复制代码
Invert:计算元素第一个位置(first)和最后一个位置(last)之间的位置变化(若是须要,还能够计算两个状态之间的尺寸大小的变化),而后使用这些数字作必定的计算,让元素进行移动(经过transform来改变元素的位置和尺寸),从而建立它位于第一个位置(初始位置)的一个错觉:
const deltaX = first.left - last.left const deltaY = first.top - last.top
const deltaW = first.width / last.width const deltaH = first.height / last.height
复制代码
Play:将元素反转(伪装在first位置),咱们能够把transform设置为none,将其移动到last位置,让元素有动画效果:
elm.animate([ { transformOrigin: 'top left', transform: ` translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH}) ` }, { transformOrigin: 'top left', transform: 'none' }], { duration: 300, easing: 'ease-in-out', fill: 'both' } );
复制代码
注意:在Play中使用了Web Animations API。若是从未接触过相关的API,点击这里了解
把全部代码合在一块儿,以下:
// 获取当前元素的边界
const first = el.getBoundingClientRect()
// 经过给元素添加一个类名,设置元素最后状态的位置和大小 (在.totes-at-the-end中添加相应的样式规则) // 布局发生了变化
el.classList.add('totes-at-the-end') // 记录元素最后状态的位置和尺寸大小
const last = el.getBoundingClientRect()
const deltaX = first.left - last.left const deltaY = first.top - last.top
const deltaW = first.width / last.width const deltaH = first.height / last.height elm.animate([ { transformOrigin: 'top left', transform: ` translate(${deltaX}px, ${deltaY}px) scale(${deltaW}, ${deltaH}) ` }, { transformOrigin: 'top left', transform: 'none' }], { duration: 300, easing: 'ease-in-out', fill: 'both' } );
复制代码
为了便于你们更好的理解FLIP技术制做的动效原理,借用下图向你们展现,或许更易于理解:
加上最后一个过程Play,实现的动画效果以下图所示:
特别声明:上面两张图来自于@davidkpiano在2017年的CSS Dev Conf分享的《FLIP ping out about animated layouts》的话题中
在App应用中,视图的过渡和元素不一样状态的过渡效果是一个很是常见的效果,其最基本的原则就是元素初始状态和最终状态不一样。在Android中,这相似于共享元素转换,只不过元素在DOM中不会像在Android中那样从一个视图“回收”到另外一个视图。
接下来咱们来看一个FLIP的过渡效果:
简单的说明一下其计算过程:
// 首先获取点被点击图片的位置和尺寸
let firstRect = itemImage.getBoundingClientRect();
// 而后获取详情的位置和尺寸
let lastRect = detailItem.getBoundingClientRect(); // 详情到图片的动效
detailItem.animate( [ { transform: ` translateX(${firstRect.left - lastRect.left}px) translateY(${firstRect.top - lastRect.top}px) scale(${firstRect.width / lastRect.width}) ` }, { transform: ` translateX(0) translateY(0) scale(1) ` } ], { duration: 600, easing: 'cubic-bezier(0.2, 0, 0.2, 1)' } );
// 详情的位置和尺寸
let itemImageRect = itemImage.getBoundingClientRect();
// 原图片的位置和尺寸
let detailItemRect = detailItem.getBoundingClientRect();
// 详情回到图片的动效
itemImage.animate( [ { zIndex: 2, transform: ` translateX(${detailItemRect.left - itemImageRect.left}px) translateY(${detailItemRect.top - itemImageRect.top}px) scale(${detailItemRect.width / itemImageRect.width}) ` }, { zIndex: 2, transform: ` translateX(0) translateY(0) scale(1) ` } ], { duration: 600, easing: 'cubic-bezier(0.2, 0, 0.2, 1)' } );
复制代码
但愿本文看了对你们的动画制做有帮助!