rc-animate
rc-animate是這個庫在npm上的名字夏漱,他在github搜索的話得用animate去搜索豪诲。(下面就都簡稱為animate了)
animate是阿里的庫,ant-design也是使用的他完成動畫的一部分內(nèi)容挂绰。
官網(wǎng)是這樣介紹的:
對單個元素根據(jù)狀態(tài)進(jìn)行動畫顯示隱藏屎篱,需結(jié)合 css 或其它第三方動畫類一起使用
代碼結(jié)構(gòu)
rc-animate的源代碼src文件夾下只有Animate,AnimateChild葵蒂,ChildrenUtils交播,Utils這四個js文件,后兩個utils是工具類js践付,用來檢測組件的props是否含有某個值這樣的工作等秦士,不過多介紹。
Animate.js是我們庫暴露給我們使用的組件永高,他會在內(nèi)部做一些檢測隧土,回調(diào)等等操作。然后調(diào)用AnimateChild組件命爬,AnimateChild是實際實現(xiàn)動畫的地方曹傀。AnimateChild中使用了css-animate這個庫,css-animate才是最核心的操作遇骑,他將給dom元素添加指定的css類名卖毁。另一個animateUtils是上一層的utils.js這個文件,我寫錯了落萎,這里說明一下亥啦。
先從rc-animate的最外層,我們最先接觸到的Animate.js講起练链。
Animate.js
簡書上寫大段的代碼太費(fèi)勁了翔脱,我就一小段一小段的解釋吧。
constructor
這個沒什么難點媒鼓,就是普通的初始化届吁。currentlyAnimatingKeys和childrenRefs在后續(xù)會比較重要,使用次數(shù)比較多
constructor(props) {
super(props);
// 這個數(shù)組用來儲存正在進(jìn)行動畫的dom節(jié)點的key值
this.currentlyAnimatingKeys = {};
// 這兩個數(shù)組就顧名思義啦
this.keysToEnter = [];
this.keysToLeave = [];
this.state = {
// 這里的toArrayChildren和getChildrenFromProps都是上面講的兩個工具類里的工具方法
children: toArrayChildren(getChildrenFromProps(props)),
};
// 儲存ref
this.childrenRefs = {};
}
componentDidMount
componentDidMount() {
const showProp = this.props.showProp;
let children = this.state.children;
// showProp是Animate組件提供的一個API绿鸣,原文解釋為:'子級動畫的類型疚沐,顯示或隱藏'
// 完全不理解他的意思,剛好官網(wǎng)的代碼示例也沒有用帶這個參數(shù)潮模,我們就暫且跳過他亮蛔,給他一個null值吧
if (showProp) {
children = children.filter((child) => {
return !!child.props[showProp];
});
}
// 遍歷子節(jié)點,并調(diào)用performAppear方法擎厢,這個方法名翻譯一下就是:‘執(zhí)行顯示’究流,所以這就是我們的css效果的第一個觸發(fā)點
// 完成組件掛載后辣吃,讓組件執(zhí)行css動畫,也就是常見的淡入效果之類的芬探。
children.forEach((child) => {
if (child) {
this.performAppear(child.key);
}
});
}
performAppear
performAppear這個方法是Animate組件自己的實例方法
performAppear = (key) => {
if (this.childrenRefs[key]) {
// 用key值找到目標(biāo)組件神得,然后對currentlyAnimatingKeys數(shù)組做一個標(biāo)識,告訴大家偷仿,這個組件正在進(jìn)行動畫
this.currentlyAnimatingKeys[key] = true;
// 調(diào)用實例上的方法哩簿,這個componentWillAppear方法,是Ref對應(yīng)的實例自己提供的一個實例方法(這里實例對應(yīng)的Class是AnimateChild)
// 可以將componentWillAppear理解為AnimateChild的一個生命周期函數(shù)炎疆,Animate就是執(zhí)行這個生命周期函數(shù)的框架
// 傳遞給可以將componentWillAppear的方法是一個回調(diào)函數(shù)
this.childrenRefs[key].componentWillAppear(
this.handleDoneAdding.bind(this, key, 'appear')
);
}
}
我想按照調(diào)用組件時代碼的執(zhí)行順序來梳理整個庫的代碼卡骂,所以接下來不一一說明Animate類里的方法了,而是講他的render函數(shù)與AnimateChild.js的內(nèi)容
render
render() {
const props = this.props;
this.nextProps = props;
const stateChildren = this.state.children;
let children = null;
// 先map拿到被AnimateChild包裝好的組件數(shù)組
if (stateChildren) {
children = stateChildren.map((child) => {
if (child === null || child === undefined) {
return child;
}
if (!child.key) {
throw new Error('must set key for <rc-animate> children');
}
// 返回的AnimateChild組件形入,childrenRefs數(shù)組里儲存的就是AnimateChild組件的實例
return (
<AnimateChild
key={child.key}
ref={node => this.childrenRefs[child.key] = node}
animation={props.animation}
transitionName={props.transitionName}
transitionEnter={props.transitionEnter}
transitionAppear={props.transitionAppear}
transitionLeave={props.transitionLeave}
>
{child}
</AnimateChild>
);
});
}
const Component = props.component;
// 這個component是暴露出來的api,用來指定需要替換的標(biāo)簽
// 這里可以看到缝左,如果沒有component的話亿遂,會只渲染組件數(shù)組的第一項
// 有component則會全部渲染,因該是為了保證有一個父級節(jié)點去包裹渺杉,以便進(jìn)行動畫
if (Component) {
let passedProps = props;
if (typeof Component === 'string') {
passedProps = {
className: props.className,
style: props.style,
...props.componentProps,
};
}
return <Component {...passedProps}>{children}</Component>;
}
return children[0] || null;
}
AnimateChild
AnimateChild組件并沒有react原生的生命周期函數(shù)包括constructor蛇数,render函數(shù)也只是簡單的返回了this.props.children。由此可見是越,AnimateChild其實是一個類耳舅,提供一些接口。下面倚评,主要了解一下他的接口都是做什么的浦徊。
先從我們上邊講的performAppear函數(shù)調(diào)用的componentWillAppear函數(shù)開始講
componentWillAppear
AnimateChild組件的componentWillAppear,componentWillEnter天梧,componentWillLeave全都一樣盔性,判斷一下是否做動畫,做就調(diào)用transition方法呢岗,不然就調(diào)用done冕香。
然后說transition,這是AnimateChild的核心方法
componentWillAppear(done) {
if (animUtil.isAppearSupported(this.props)) {
this.transition('appear', done);
} else {
done();
}
}
transition
transition(animationType, finishCallback) {
const node = ReactDOM.findDOMNode(this);
const props = this.props;
const transitionName = props.transitionName;
const nameIsObj = typeof transitionName === 'object';
this.stop();
const end = () => {
this.stopper = null;
finishCallback();
};
// props.animation是如果使用第三方動畫類庫需要傳入的對象后豫,props[transitionMap[animationType]]是一個布爾值悉尾,對應(yīng)著transitionAppear等三個api
// 這個判斷翻譯過來就是,支持css動畫或者不使用css類庫并且有自己的css類名并且的確要做這一步的動畫
if ((isCssAnimationSupported || !props.animation[animationType]) &&
transitionName && props[transitionMap[animationType]]) {
// 這一大串挫酿,都是在拼接字符串构眯,拼接出要添加的類名
const name = nameIsObj ? transitionName[animationType] : `${transitionName}-${animationType}`;
let activeName = `${name}-active`;
if (nameIsObj && transitionName[`${animationType}Active`]) {
activeName = transitionName[`${animationType}Active`];
}
// 核心中的核心,調(diào)用cssAnimate方法并傳入回調(diào)
this.stopper = cssAnimate(node, {
name,
active: activeName,
}, end);
} else {
this.stopper = props.animation[animationType](node, end);
}
}
cssAnimate
cssAnimate庫包含兩個js文件饭豹,一個index.js是提供給我們使用的鸵赖,用來觸發(fā)動畫或者過渡务漩。另一個event.js是支撐index.js的運(yùn)行的,event.js在內(nèi)部封裝了函數(shù)它褪,用來檢測瀏覽器是否執(zhí)行完了css動畫饵骨。(這是一個新的知識點,之前從未了解過茫打。上邊講的cssAnimate方法是index.js提供的居触。下面我們先說他。
index
index.js里的工具方法與細(xì)枝末節(jié)就不說了老赤,直接說cssAnimate這個函數(shù)
const cssAnimation = (node, transitionName, endCallback) => {
// 先是一大串字符串拼接轮洋,得出要添加的class類名
const nameIsObj = typeof transitionName === 'object';
const className = nameIsObj ? transitionName.name : transitionName;
const activeClassName = nameIsObj ? transitionName.active : `${transitionName}-active`;
let end = endCallback;
let start;
let active;
// 這個classes是引用的component-classes這個庫的api,用來跨瀏覽器操作dom元素的類名抬旺,理解為jq吧弊予,但是只有類名的增刪改查
const nodeClasses = classes(node);
// 如果是對象,就解構(gòu)一下开财,拿到真正的回調(diào)函數(shù)
if (endCallback && Object.prototype.toString.call(endCallback) === '[object Object]') {
end = endCallback.end;
start = endCallback.start;
active = endCallback.active;
}
// 這是cssAnimate自己定義的一個方法汉柒,動畫結(jié)束時的回調(diào)
if (node.rcEndListener) {
node.rcEndListener();
}
node.rcEndListener = (e) => {
if (e && e.target !== node) {
return;
}
if (node.rcAnimTimeout) {
clearTimeout(node.rcAnimTimeout);
node.rcAnimTimeout = null;
}
clearBrowserBugTimeout(node);
nodeClasses.remove(className);
nodeClasses.remove(activeClassName);
Event.removeEndEventListener(node, node.rcEndListener);
node.rcEndListener = null;
// Usually this optional end is used for informing an owner of
// a leave animation and telling it to remove the child.
if (end) {
end();
}
};
// 綁定動畫結(jié)束的監(jiān)聽事件
Event.addEndEventListener(node, node.rcEndListener);
// 真正開始了
if (start) {
start();
}
nodeClasses.add(className);
// 這里延時加載了實現(xiàn)動畫效果的activeClassName,還有生命周期函數(shù)active
node.rcAnimTimeout = setTimeout(() => {
node.rcAnimTimeout = null;
nodeClasses.add(activeClassName);
if (active) {
setTimeout(active, 0);
}
fixBrowserByTimeout(node);
// 30ms for firefox
}, 30);
// node.rcEndListener在自己被調(diào)用之后會將自己置為空责鳍,我認(rèn)為這里的返回是為了提供一個手動停止動畫的方法
return {
stop() {
if (node.rcEndListener) {
node.rcEndListener();
}
},
};
};