转载自前端备忘录-江湖术士

我们来看看看看,搭配 anime.js ,并采用领域驱动,如何做动画吧~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function useAnime(props: AnimeParams = {}) {
const ref = useRef<any>();
const animationRef = useRef<AnimeInstance>();
useLayoutEffect(() => {
if (!ref.current) {
console.warn("please bind the anime ref while useAnime");
return;
}
animationRef.current = anime({
...props,
targets: [ref.current],
});
}, [ref, animationRef, props]);
return { ref, animationRef };
}

给两个 可变 ref 出来,为啥是 ref?因为不涉及响应式呀~

用的时候,只需要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default function Test() {
const { ref, animationRef } = useAnime({
translateX: 300,
loop: true,
duration: 2000,
});
return (
<div>
<div
ref={ref}
style={{ width: "100px", height: "100px", backgroundColor: "black" }}
></div>
</div>
);
}

如果你想要手动控制动画的运行:

1
2
3
4
5
6
7
8
9
10
11
const { ref, animationRef } = useAnime({
translateX: 300,
loop: true,
duration: 2000,
autoplay: false,
});
useEffect(() => {
setTimeout(() => {
animationRef.current?.play?.();
}, 3000);
}, [animationRef]);

改一下,按照正常逻辑来写就行了

而且 anime.js 提供的动画控制相当不讲道理:

对了,如果你想要的进出动画,那么封装两个 useAnime 作为服务:

1
2
3
4
5
6
7
8
9
10
function useSomeEnterAnimation(exist: any) {
// 进入动画
const { ref, enterAnimationRef } = useAnime({
/* ... *})
// 销毁动画 注意加入
const {ref,leaveAnimationRef} = useAnime({/* ... */
});
return { ref, animationRef, leaveAnimationRef };
}
export const SomeEnterAnimation = createContext(null);

然后封装一个组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Enter(props: PropsWithChildren<{ AnimationProvider: Context }>) {
const { ref, enterAnimationRef, leaveAnimationRef } = useContext(
AnimationProvider
);
const children = useRef<any>();
useEffect(() => {
if (props.children) {
children.current.ref = props.children;
enterAnimationRef.current?.play?.();
} else {
leaveAnimationRef.current?.play?.();
setTimeout(() => {
children.current = props.children;
}, 3000);
}
}, [props]);
return <div ref={ref}>{children.current}</div>;
}

搞定,然后把你想要实现的进出动画,随意用起来:

1
2
3
<Enter AnimationProvider={SomeEnterAnimation}>
<div>see me dance</div>
</Enter>

关键是,除了进出动画组件需要自己定义以外,其它的都不需要你去封装,而这个可以预先写好或者交给其他工具

接下来,还有更有意思的:

我们知道,animejstypescript 支持是有问题的,为啥?

这是 animejs 的类型声明

首先其带了个 [anyAnimateProperty:string] :any

怎么办?

初始化问题,类型问题,当你需要代码具有解释性的时候,想想面向对象吧~

1
2
3
4
5
6
7
8
9
10
11
12
class CustomAnimeConfig {
// 自定义全局配置
duration: 2000;
keyframes: [];
autoplay: true;
// ...
}

// 使用时
const animeProps = new CustomAnimeConfig();
animeProps.autoplay = true;
const { ref, animationRef } = useAnime(animeProps);

同理,类似这样的思想,还可以用来处理图标库,全局粘贴,二维码之类的奇葩

DDD 有一个原则:

    任何复杂问题,独立拆分开,都不复杂

只要做了状态逻辑复用(服务),所有你认为很难得问题,都可以降级到非常简单的几个模块

你对第三方的依赖程度将会大大降低

你想怎么用,你就可以怎么用

更多的时候,你在思考你自己的业务逻辑,并非其他事务

这时候,你会发现,很多第三方工具其实并没有帮你什么忙,你只是上个时代需要他,而且他们大部分都是在做跳过框架实现逻辑复用的事情