hanav
Version:
A React Navigation Menu Component Library.
103 lines (93 loc) • 3.84 kB
JavaScript
import React, { Children, cloneElement, useContext, useState, useRef, useEffect, useMemo } from "react";
import { ContextForContent } from "../context";
/** 自定义 X 轴切换动画的 Items */
export default function CustomMotionItems({ children, trans, transRunning }) {
const {
openedMenuIdx,
dur,
prevMenuIdxRef,
collapsePrevMenuIdx2Ref,
} = useContext(ContextForContent);
const [f, forceRender] = useState(); // 触发后,重新渲染,用于面板切换过渡动画
const startCustomTransRef = useRef(false); // 为 true 时,开始进行结束状态动画,开始后设为 false
useEffect(() => {
if (openedMenuIdx > -1) {
if (prevMenuIdxRef.current > -1 || collapsePrevMenuIdx2Ref.current > -1) {
// 确保浏览器渲染初始状态
requestAnimationFrame(() => {
forceRender({});
startCustomTransRef.current = true;
})
}
}
}, [openedMenuIdx]);
/** 缓存动画属性,避免由于外部渲染,同时 genItemStyle 依赖的 startCustomTransRef 不变,导致生成错误样式 */
const itemsStyle = useMemo(() => Array.from({ length: Children.count(children) }, (_, i) => i).map(genItemStyle), [openedMenuIdx, f]);
return Children.map(
children,
(child, i) => cloneElement(child, { type: "C", orderI: i, contentItemStyle: itemsStyle[i], transRunning }));
function genItemStyle(orderI) {
let transStyles = {};
const openedMenu = openedMenuIdx === orderI;
const { transition, ...otherTrans } = trans;
const hasTransition = transition !== false;
if (hasTransition) transStyles.transition = genCustomTransition(transition, orderI, Object.keys(otherTrans || {}).filter(p => Array.isArray(otherTrans[p])));
for (const p in otherTrans) {
const v = trans[p];
const aryV = [].concat(v);
const legalAryV = [aryV[0], aryV[1], aryV[2]].filter(n => n != null);
transStyles = {
...transStyles,
[p]: legalAryV[2] ? genCustom3State(...legalAryV, orderI) : legalAryV[1] ? genCustom2State(...legalAryV, orderI) : legalAryV[0],
}
}
transStyles.position = "absolute";
transStyles.zIndex = openedMenu ? 1 : null;
if (openedMenu && startCustomTransRef.current) startCustomTransRef.current = false;
return transStyles;
}
function genCustom2State(start, end, orderI) {
if (orderI === prevMenuIdxRef.current && openedMenuIdx < 0) return end;
const isLeaveI = prevMenuIdxRef.current === orderI;
if (isLeaveI) return start;
const openedMenu = openedMenuIdx === orderI;
if (openedMenu) {
if (prevMenuIdxRef.current < 0) return end;
if (startCustomTransRef.current) {
return end;
}
}
return start;
}
function genCustom3State(init, forward, backward, orderI) {
const openedMenu = openedMenuIdx === orderI;
const curI = openedMenuIdx;
const prevI = prevMenuIdxRef.current;
const prevI2 = collapsePrevMenuIdx2Ref.current;
const commonExpand = curI > -1 && prevI < 0 && prevI2 < 0;
const collapse = curI < 0;
const isInitState = commonExpand || collapse;
if (isInitState) return init;
const isBackward = prevI2 > 0 ? curI < prevI2 : curI < prevI;
if (openedMenu) {
if (startCustomTransRef.current) {
return init;
}
return isBackward ? forward : backward;
}
return isBackward ? backward : forward;
}
function genCustomTransition(v, orderI, transProps) {
const finalV = v || transProps.map(p => `${p} ${dur}s`).join();
const isLeaveI = prevMenuIdxRef.current === orderI;
if (isLeaveI) return finalV;
const openedMenu = openedMenuIdx === orderI;
if (openedMenu) {
if (startCustomTransRef.current) {
return finalV;
}
return null;
}
return finalV;
}
}