animation是个好东西,实现了一些常用的方法。最近集成到uni-app中,发现网上的通常都是使用绑定变量的方式来实现。少了通过指定ref触发的模式,总感觉有那么别扭。撸了一上午代码,把这个功能实现了出来。
集成animation.css
集成animation.css没啥好说的,在网上下载4.1.1版的animate.min.css。然后修改头部,添加小程序的兼容补丁代码:
page {
--animate-duration: 1s;
--animate-delay: 1s;
--animate-repeat: 1;
}
然后放到statics/css/animate.min.css
引入animation.css
在系统的全局文件导入即可,我使用uni-best的模板,导入的位置就是style/index.scss
css
// 增补字体库
@import '@/static/css/fonts.css';
// 增补动画库
@import '@/static/css/animate.min.css';
.test {
// 可以通过 @apply 多个样式封装整体样式
@apply mt-4 ml-4;
padding-top: 4px;
color: red;
}
/** 增补快速样式 **/
.x-items-left {
// 水平排列
@apply flex flex-row;
}
.x-items-center {
// 水平排列,垂直居中
@apply flex flex-row items-center;
}
.x-items-stretch {
// 水平排列,垂直拉伸
@apply flex flex-row items-stretch;
}
.x-items-between {
// 水平排列,垂直居中,分散对齐
@apply flex flex-row items-center justify-between;
}
.y-items-center {
// 垂直排列,水平居中
@apply flex flex-col items-center;
}
.y-items-stretch {
// 垂直排列,水平拉伸
@apply flex flex-col items-stretch;
}
.flex-center {
// 完全居中,使用flex定位
@apply flex justify-center items-center;
}
.absolute-center {
// 完全居中,使用 absolute定位
@apply absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2;
}
编写执行方法
这里有个神坑,就是app(Android)下addEventListener后,removeEventListener相当不好使。移除一次后再添加就监测不到了,索性添加一个对象cache,手动判断保证只添加一次
下面代码也是在unibest环境下的
TypeScript
const ANIMATE_CSS_PERFIX = 'animate__'
const ANIMATE_CSS_CLASS = ANIMATE_CSS_PERFIX + 'animated'
const switchAnimateClass: (className: string, animationTypes?: string[]) => string = (
className,
animationTypes
) => {
const classNames: string[] = className.split(/\s+/)
if (!classNames.includes(ANIMATE_CSS_CLASS)) {
// 没有指定动画,不处理
return className
}
const filtered = classNames.filter(
(item) => !(item.startsWith(ANIMATE_CSS_PERFIX) && item !== ANIMATE_CSS_CLASS)
)
if (animationTypes) {
animationTypes.forEach((item) => filtered.push(ANIMATE_CSS_PERFIX + item))
}
return filtered.join(' ')
}
const instanceCache = new Set<any>()
/**
* animatecss集成动画效果执行一次,如果循环执行直接设置样式即可
* @param ref vue的对象ref
* @param animationType 动画类型以及时间配置等,例如animate__headShake只需要输入headShake
* @param callback 动画执行结束后的回调,可以实现连续动画(APP不支持)
*/
export function animatecss(ref: Ref, animationType: string | string[], callback?: () => void) {
if (ref && ref.value) {
if (process.env.UNI_PLATFORM === 'app') {
const instance = ref.value
// 如果提供了回调函数,则监听动画结束事件
const animationTypes: string[] = [].concat(animationType)
instance.className = switchAnimateClass(instance.className, animationTypes)
const onAnimationEnd = () => {
try {
if (callback) {
callback()
}
} finally {
// 移除事件监听器,避免多次绑定
const afterClass: string[] = instance.className.split(' ')
nextTick(() => {
instance.className = switchAnimateClass(instance.className)
})
}
}
if (!instanceCache.has(instance)) {
instance.addEventListener('animationend', onAnimationEnd)
instanceCache.add(instance)
}
} else {
const instance = ref.value.$el
const classList: DOMTokenList = instance.classList
const animationTypes = [].concat(animationType).map((item) => ANIMATE_CSS_PERFIX + item)
// 如果提供了回调函数,则监听动画结束事件
classList.add(...animationTypes)
const onAnimationEnd = () => {
try {
if (callback) {
callback()
}
} finally {
// 移除事件监听器,避免多次绑定
instance.removeEventListener('animationend', onAnimationEnd)
classList.remove(...animationTypes)
}
}
instance.addEventListener('animationend', onAnimationEnd)
}
}
}
使用方法:元素预先添加animate__animated样式,再在调用时传入ref对象和要执行的动画方法即可
html
<view class="x-items-left mb-60rpx">
<view class="flex-1" />
<view ref="tips" class="w-120rpx text-teal animate__animated">密码登录</view>
</view>
执行动画
TypeScript
const tips = ref(null)
const getCode = (type: string = 'headShake') => {
utils.animatecss(tips, type)
}
由于支持callback,你还可以把多个动画组合起来,例如这个:
摆一摆,再跳一跳(在安卓/IOS下不可用)
TypeScript
const getCode = (type: string = 'animate__headShake') => {
utils.animatecss(tips, 'headShake', () => {
utils.animatecss(tips, 'bounce')
})
}
还可以添加多个控制参数指定,例如慢慢摇,快快跳
TypeScript
utils.animatecss(tips, ['headShake', 'slower'], () => {
utils.animatecss(tips, ['bounce', 'faster'])
})
控制参数组合见animate.css官方网站