在 React 中关闭有条件渲染的组件时动画流畅
Smooth animation when turning off a conditionally rendered component in React
我的App.js如下
export const App = () => {
const [toggled, setToggled] = useState(false);
const [ordering, setOrdering] = useState(false);
const handleColorModeClick = () => {
setToggled((s) => !s);
};
const handleOrdering = () => {
setOrdering((s) => !s);
};
return (
<Ordering.Provider value={{ ordering: ordering }}>
<div className={`app ${toggled ? "theme-dark" : "theme-light"}`}>
<Switch>
<Route path="/" exact>
<HeaderComponent toggled={toggled} onClick={handleColorModeClick} />
<div>components2</div>
<EateryInfo toggled={toggled} />
{/* <CategoryItems toggled={toggled} /> */}
<MenuButton toggled={toggled} />
</Route>
<Route path="/menu">
<HeaderComponent toggled={toggled} onClick={handleColorModeClick} />
<CategoryItems toggled={toggled} />
<CheckBox
text="Start Ordering"
standAlone={true}
handleOrdering={handleOrdering}
/>
<MenuButton toggled={toggled} />
</Route>
</Switch>
</div>
</Ordering.Provider>
);
};
我使用复选框
设置ordering
变量的状态
然后我用它来有条件地渲染 QuantityChange
组件,就像这样
export const MenuEntry = ({ mealData, toggled }: MenuEntryProps) => {
const orderingEnabled = useContext(Ordering);
return (
<div className="menu-entry">
<MenuItem oneMenuItem={mealData} toggled={toggled} />
{orderingEnabled.ordering ? <QuantityChange toggled={toggled} /> : ""}
</div>
);
};
所有这些工作正常,组件按需要渲染。
我想平稳过渡此组件的进入和退出。进入时的动画效果很好,但我不知道如何让退出动画正常工作。
视频是现在正在发生的事情,可以在此处的视频中找到 https://youtu.be/5kl1wCBwR_U(复选框位于右下角)
我查看了几个在线论坛来找到这个问题的答案,但我无法弄清楚。
我也试过使用 react-transition-group
但没有成功
export const QuantityChange = ({ toggled }: QuantityChangeProps) => {
const orderingEnabled = useContext(Ordering);
const duration = 500;
return (
<Transition in={orderingEnabled.ordering} timeout={duration} appear>
{(status) => (
<div
className={`quantity-change flex ${
toggled ? "theme-dark" : "theme-light"
} fade-${status}`}
>
<span className="add-quantity">+</span>
<span className="quantity">0</span>
<span className="subtract-quantity">-</span>
</div>
)}
</Transition>
);
};
我查看了 onAnimationEnd
,但无法理解。
看来您需要一个简单的 Accordion 东西。
您可以尝试类似的操作(下面的片段)。
此处的主要时刻之一是将 height
设置为 auto
值。它允许更改内容,并且不会限制其尺寸。
AccordionItem
有条件地呈现其 children。如果它应该关闭并且动画结束,则不会呈现 children。
const AccordionItem = (props) => {
const { className, headline, open, children } = props
const [height, setHeight] = React.useState(0)
const [isOver, setOver] = React.useState(false)
const bodyRef = React.useRef(null)
const getDivHeight = React.useCallback(() => {
const { height } = bodyRef.current ? bodyRef.current.getBoundingClientRect() : {}
return height || 0
}, [])
// set `auto` to allow an inner content to change
const handleTransitionEnd = React.useCallback(
(e) => {
if (e.propertyName === 'height') {
setHeight(open ? 'auto' : 0)
if (!open) {
setOver(true)
}
}
},
[open]
)
React.useEffect(() => {
setHeight(getDivHeight())
setOver(false)
if (!open) {
requestAnimationFrame(() => {
requestAnimationFrame(() => setHeight(0))
})
}
}, [getDivHeight, open])
const shouldHide = !open && isOver
return (
<div style={{overflow: 'hidden'}}>
<div
style={{ height, transition: "all 2s" }}
onTransitionEnd={handleTransitionEnd}
>
<div ref={bodyRef}>
{shouldHide ? null : children}
</div>
</div>
</div>
)
}
const App = () => {
const [open, setOpen] = React.useState(false)
return (
<div>
<button onClick={() => setOpen(isOpen => !isOpen)}>toggle</button>
<table style={{width: '100%'}}>
<tr>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
</tr>
<tr>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
</tr>
<tr>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
</tr>
</table>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<div id="root"></div>
我的App.js如下
export const App = () => {
const [toggled, setToggled] = useState(false);
const [ordering, setOrdering] = useState(false);
const handleColorModeClick = () => {
setToggled((s) => !s);
};
const handleOrdering = () => {
setOrdering((s) => !s);
};
return (
<Ordering.Provider value={{ ordering: ordering }}>
<div className={`app ${toggled ? "theme-dark" : "theme-light"}`}>
<Switch>
<Route path="/" exact>
<HeaderComponent toggled={toggled} onClick={handleColorModeClick} />
<div>components2</div>
<EateryInfo toggled={toggled} />
{/* <CategoryItems toggled={toggled} /> */}
<MenuButton toggled={toggled} />
</Route>
<Route path="/menu">
<HeaderComponent toggled={toggled} onClick={handleColorModeClick} />
<CategoryItems toggled={toggled} />
<CheckBox
text="Start Ordering"
standAlone={true}
handleOrdering={handleOrdering}
/>
<MenuButton toggled={toggled} />
</Route>
</Switch>
</div>
</Ordering.Provider>
);
};
我使用复选框
设置ordering
变量的状态
然后我用它来有条件地渲染 QuantityChange
组件,就像这样
export const MenuEntry = ({ mealData, toggled }: MenuEntryProps) => {
const orderingEnabled = useContext(Ordering);
return (
<div className="menu-entry">
<MenuItem oneMenuItem={mealData} toggled={toggled} />
{orderingEnabled.ordering ? <QuantityChange toggled={toggled} /> : ""}
</div>
);
};
所有这些工作正常,组件按需要渲染。
我想平稳过渡此组件的进入和退出。进入时的动画效果很好,但我不知道如何让退出动画正常工作。
视频是现在正在发生的事情,可以在此处的视频中找到 https://youtu.be/5kl1wCBwR_U(复选框位于右下角)
我查看了几个在线论坛来找到这个问题的答案,但我无法弄清楚。
我也试过使用 react-transition-group
但没有成功
export const QuantityChange = ({ toggled }: QuantityChangeProps) => {
const orderingEnabled = useContext(Ordering);
const duration = 500;
return (
<Transition in={orderingEnabled.ordering} timeout={duration} appear>
{(status) => (
<div
className={`quantity-change flex ${
toggled ? "theme-dark" : "theme-light"
} fade-${status}`}
>
<span className="add-quantity">+</span>
<span className="quantity">0</span>
<span className="subtract-quantity">-</span>
</div>
)}
</Transition>
);
};
我查看了 onAnimationEnd
,但无法理解。
看来您需要一个简单的 Accordion 东西。 您可以尝试类似的操作(下面的片段)。
此处的主要时刻之一是将 height
设置为 auto
值。它允许更改内容,并且不会限制其尺寸。
AccordionItem
有条件地呈现其 children。如果它应该关闭并且动画结束,则不会呈现 children。
const AccordionItem = (props) => {
const { className, headline, open, children } = props
const [height, setHeight] = React.useState(0)
const [isOver, setOver] = React.useState(false)
const bodyRef = React.useRef(null)
const getDivHeight = React.useCallback(() => {
const { height } = bodyRef.current ? bodyRef.current.getBoundingClientRect() : {}
return height || 0
}, [])
// set `auto` to allow an inner content to change
const handleTransitionEnd = React.useCallback(
(e) => {
if (e.propertyName === 'height') {
setHeight(open ? 'auto' : 0)
if (!open) {
setOver(true)
}
}
},
[open]
)
React.useEffect(() => {
setHeight(getDivHeight())
setOver(false)
if (!open) {
requestAnimationFrame(() => {
requestAnimationFrame(() => setHeight(0))
})
}
}, [getDivHeight, open])
const shouldHide = !open && isOver
return (
<div style={{overflow: 'hidden'}}>
<div
style={{ height, transition: "all 2s" }}
onTransitionEnd={handleTransitionEnd}
>
<div ref={bodyRef}>
{shouldHide ? null : children}
</div>
</div>
</div>
)
}
const App = () => {
const [open, setOpen] = React.useState(false)
return (
<div>
<button onClick={() => setOpen(isOpen => !isOpen)}>toggle</button>
<table style={{width: '100%'}}>
<tr>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
</tr>
<tr>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
</tr>
<tr>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
<td>
Hot Pongal
<AccordionItem open={open}>
<button>-</button>
<input />
<button>+</button>
</AccordionItem>
</td>
</tr>
</table>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<div id="root"></div>