在專案開發的過程中,設計師不免會做一些動畫效果來提升使用者體驗。如果當前效果不需要互動,只是用來展示的話,我們完全可以利用 GIF 或者 APNG 來實現效果。不瞭解 APNG 小夥伴可以看看這篇部落格 APNG 歷史、特性簡介以及 APNG 製作演示。
但是如果當前動畫除了展示還需要其他互動,甚至是一個元件需要動畫效果,使用圖片格式就不合理了。於是我寫一個極其簡單的 css 動畫庫 rc-css-animate。這裡直接使用 animate.css 作為 css 動畫的依賴庫。 animate.css 不但提供了很多互動動畫樣式類,也提供了動畫執行速度,延遲,以及重複次數等樣式類。
可以看到,預設的 animate.css 構建動畫都需要攜帶字首 “animate__”。
<h1 class="animate__animated animate__bounce">An animated element</h1>
當然,該庫是對 css 動畫進行了一層封裝,依然支援其他動畫庫以及自己手寫的 css 動畫,但如果開發者需要對動畫進行各種複雜控制,不推薦使用此庫。
使用
可以利用如下方式使用:
import React, { useRef } from "react";
import ReactCssAnimate from "rc-css-animate";
// 引入 animate.css 作為動畫依賴
import "animate.css";
function App() {
const animateRef = useRef(null);
return (
<div className="App">
<ReactCssAnimate
// 定義當前展示動畫的元件
// 預設使用 div
tag="div"
// 當前元件的 className
className=""
// 當前元件的 style
style={{}}
// 當前元件的 ref
ref={animateRef}
// 動畫字首
clsPrefix="animate__"
// 當前動畫的 className
animateCls="animated backInDown infinite"
// 動畫開始時候是否處於展示狀態
initialVisible={false}
// 獲取動畫結束是否處理展示狀態
getVisibleWhenAnimateEnd={(cls) => {
// 如果當前 animateCls 中有 Out
// 返回 false 則會在動畫結束後不再顯示
if (cls.includes("Out")) {
return false;
}
return true;
}}
// 動畫結束回撥
onAnimationEnd={() => {
console.log("done");
}}
>
<div>
測試動畫
</div>
</ReactCssAnimate>
</div>
);
}
ReactCssAnimate 使用了 React hooks,但是也提供了相容的類元件。同時也提供了全域性的字首設定。
import React from "react";
import {
// 使用類元件相容之前的版本
CompatibleRAnimate as ReactCssAnimate,
setPrefixCls,
} from "rc-css-animate";
// 引入 animate.css 作為動畫依賴
import "animate.css";
// 設定全域性 prefix,會被當前元件覆蓋
setPrefixCls("animate__");
/** 構建動畫塊元件 */
function BlockWrapper(props) {
// 需要獲取並傳入 className, children, style
const { className, children, style } = props;
return (
<div
className={className}
style={{
background: "red",
padding: 100,
...style,
}}
>
{children}
</div>
);
}
function App() {
return (
<div className="App">
<ReactCssAnimate
tag={BlockWrapper}
// 當前動畫的 className
animateCls="animated backInDown infinite"
>
<div>
測試動畫
</div>
</ReactCssAnimate>
</div>
);
}
原始碼解析
原始碼較為簡單,是基於 createElment 和 forwardRef 構建完成。其中 forwardRef 會將當前設定的 ref 轉發到內部元件中去。對於 forwardRef 不熟悉的同學可以檢視一下官網中關於 Refs 轉發的文件。
import React, {
createElement,
forwardRef,
useCallback,
useEffect,
useState,
} from "react";
import { getPrefixCls } from "./prefix-cls";
import { AnimateProps } from "./types";
// 全域性的動畫字首
let prefixCls: string = "";
const getPrefixCls = (): string => prefixCls;
// 設定全域性的動畫字首
export const setPrefixCls = (cls: string) => {
if (typeof cls !== "string") {
return;
}
prefixCls = cls;
};
const Animate = (props: AnimateProps, ref: any) => {
const {
tag = "div",
clsPrefix = "",
animateCls,
style,
initialVisible,
onAnimationEnd,
getVisibleWhenAnimateEnd,
children,
} = props;
// 透過 initialVisible 獲取元件的顯隱,如果沒有則預設為 true
const [visible, setVisible] = useState<boolean>(initialVisible ?? true);
// 當前不需要展示,返回 null 即可
if (!visible) {
return null;
}
// 沒有動畫類,直接返回子元件
if (!animateCls || typeof animateCls !== "string") {
return <>{children}</>;
}
useEffect(() => {
// 當前沒獲取請求結束的設定顯示隱藏,直接返回,不進行處理
if (!getVisibleWhenAnimateEnd) {
return;
}
const visibleWhenAnimateEnd = getVisibleWhenAnimateEnd(animateCls);
// 如果動畫結束後需要展示並且當前沒有展示,直接進行展示
if (visibleWhenAnimateEnd && !visible) {
setVisible(true);
}
}, [animateCls, visible, getVisibleWhenAnimateEnd]);
const handleAnimationEnd = useCallback(() => {
if (!getVisibleWhenAnimateEnd) {
onAnimationEnd?.();
return;
}
// 當前處於展示狀態,且動畫結束後需要隱藏,直接設定 visible 為 false
if (visible && !getVisibleWhenAnimateEnd(animateCls)) {
setVisible(false);
}
onAnimationEnd?.();
}, [getVisibleWhenAnimateEnd]);
let { className = "" } = props;
if (typeof className !== "string") {
className = "";
}
let animateClassName = animateCls;
// 獲取最終的動畫字首
const finalClsPrefix = clsPrefix || getPrefixCls();
// 沒有或者動畫字首不是字串,不進行處理
if (!finalClsPrefix || typeof finalClsPrefix !== "string") {
animateClassName = animateCls.split(" ").map((item) =>
`${finalClsPrefix}${item}`
).join(" ");
}
// 建立並返回 React 元素
return createElement(
tag,
{
ref,
onAnimationEnd: handleAnimationEnd,
// 將傳遞的 className 和 animateClassName 合併
className: className.concat(` ${animateClassName}`),
style,
},
children,
);
};
// 利用 forwardRef 轉發 ref
// 第一個引數是 props,第二個引數是 ref
export default forwardRef(Animate);
以上程式碼全部在 rc-css-animate 中。這裡也歡迎各位小夥伴提出 issue 和 pr。
鼓勵一下
如果你覺得這篇文章不錯,希望可以給與我一些鼓勵,在我的 github 部落格下幫忙 star 一下。