AnimatePresence
AnimatePresence
使退出动画变得简单。通过包裹一个或多个 motion
组件使用 AnimatePresence
,我们可以访问 exit
动画属性。
<AnimatePresence> {show && <motion.div key="modal" exit={{ opacity: 0 }} />} </AnimatePresence>
用法
导入
import { AnimatePresence } from "motion/react"
退出动画
AnimatePresence
通过检测其直接子元素何时从 React 树中移除来工作。
这可能是由于组件挂载/重新挂载
<AnimatePresence> {show && <Modal key="modal" />} </AnimatePresence>
其 key
更改
<AnimatePresence> <Slide key={activeItem.id} /> </AnimatePresence>
或者当列表中的子元素被添加/移除时
<AnimatePresence> {items.map(item => ( <motion.li key={item.id} exit={{ opacity: 1 }} layout /> ))} </AnimatePresence>
退出组件中的任何 motion
组件都将触发在其 exit
属性上定义的动画,然后在组件从 DOM 中移除之前执行。
function Slide({ img, description }) { return ( <motion.div exit={{ opacity: 0 }}> <img src={img.src} /> <motion.p exit={{ y: 10 }}>{description}</motion.p> </motion.div> ) }
注意:直接子元素必须各自具有唯一的 key
属性,以便 AnimatePresence
可以跟踪它们在树中的存在。
像 initial
和 animate
一样,exit
可以定义为值对象,也可以定义为变体标签。
const modalVariants = { visible: { opacity: 1, transition: { when: "beforeChildren" } }, hidden: { opacity: 0, transition: { when: "afterChildren" } } } function Modal({ children }) { return ( <motion.div initial="hidden" animate="visible" exit="hidden"> {children} </motion.div> ) }
更改 key
更改 key
属性使 React 创建一个全新的组件。因此,通过更改 AnimatePresence
的单个子元素的 key
,我们可以轻松制作像幻灯片一样的组件。
export const Slideshow = ({ image }) => ( <AnimatePresence> <motion.img key={image.src} src={image.src} initial={{ x: 300, opacity: 0 }} animate={{ x: 0, opacity: 1 }} exit={{ x: -300, opacity: 0 }} /> </AnimatePresence> )
访问 presence 状态
AnimatePresence
的任何子元素都可以使用 useIsPresence
hook 访问 presence 状态。
import { useIsPresent } from "motion/react" function Component() { const isPresent = useIsPresent() return isPresent ? "Here!" : "Exiting..." }
这允许您在组件不再渲染时更改内容或样式。
访问 presence 数据
当组件已从 React 树中移除时,其 props 将无法再更新。我们可以使用 AnimatePresence
的 custom
属性将新数据向下传递到树中,甚至传递到退出的组件中。
<AnimatePresence custom={swipeDirection}> <Slide key={activeSlideId}>
然后稍后我们可以使用 usePresenceData
提取该数据。
import { AnimatePresence, usePresenceData } from "motion/react" function Slide() { const isPresent = useIsPresent() const direction = usePresenceData() return ( <motion.div exit={{ opacity: 0 }}> {isPresent ? "Here!" : "Exiting " + direction} </motion.div> ) }
手动用法
也可以手动告知 AnimatePresence
何时可以使用 usePresence
hook 安全地移除组件。
这会返回 isPresent
状态和一个回调 safeToRemove
,当您准备好从 DOM 中移除组件时(例如在手动动画或其他超时后),应调用该回调。
import { usePresence } from "motion/react" function Component() { const [isPresent, safeToRemove] = usePresence() useEffect(() => { // Remove from DOM 1000ms after being removed from React !isPresent && setTimeout(safeToRemove, 1000) }, [isPresent]) return <div /> }
传播退出动画
默认情况下,AnimatePresence
控制其所有子元素的 exit
动画,直到渲染另一个 AnimatePresence
组件。
<AnimatePresence> {show ? ( <motion.section exit={{ opacity: 0 }}> <AnimatePresence> {/* * When `show` becomes `false`, exit animations * on these children will not fire. */} {children} </AnimatePresence> </motion.section> ) : null} </AnimatePresence>
通过将 AnimatePresence
组件的 propagate
属性设置为 true
,当它从另一个 AnimatePresence
中移除时,它将触发其所有子元素的退出动画。
<AnimatePresence> {show ? ( <motion.section exit={{ opacity: 0 }}> <AnimatePresence propagate> {/* * When `show` becomes `false`, exit animations * on these children **will** fire. */} {children} </AnimatePresence> </motion.section> ) : null} </AnimatePresence>
属性
initial
通过传递 initial={false}
,AnimatePresence
将禁用组件首次渲染时存在的子元素的任何初始动画。
<AnimatePresence initial={false}> <Slide key={activeItem.id} /> </AnimatePresence>
custom
当组件被移除时,不再有机会更新其 props(因为它不再在 React 树中)。因此,我们无法使用移除组件的相同渲染来更新其退出动画。
通过 AnimatePresence
的 custom
属性传递一个值,我们可以使用动态变体来更改 exit
动画。
const variants = { hidden: (direction) => ({ opacity: 0, x: direction === 1 ? -300 : 300 }), visible: { opacity: 1, x: 0 } } export const Slideshow = ({ image, direction }) => ( <AnimatePresence custom={direction}> <motion.img key={image.src} src={image.src} variants={variants} initial="hidden" animate="visible" exit="hidden" /> </AnimatePresence> )
子元素可以通过 usePresenceData
访问此数据。
mode
默认值:"sync"
决定 AnimatePresence
如何处理进入和退出的子元素。
"sync"
:子元素在添加/移除后立即进行动画进入/退出。"wait"
:进入的子元素将等待直到退出的子元素动画退出。注意:目前一次只渲染一个子元素。"popLayout"
:退出的子元素将从页面布局中“弹出”。这允许周围的元素立即移动到它们的新布局。
自定义组件注意:当使用 popLayout
模式时,AnimatePresence 的任何直接子元素,如果是一个自定义组件,必须被 React 的 forwardRef
函数包裹,并将提供的 ref
转发到您希望从布局中弹出的 DOM 节点。
onExitComplete
当所有退出的节点都完成动画退出后触发。
propagate
默认值: false
如果设置为 true
,则当此 AnimatePresence
从父级 AnimatePresence
退出时,也会触发子元素的退出动画。
<AnimatePresence> {show ? ( <motion.section exit={{ opacity: 0 }}> <AnimatePresence propagate> {/* This exit prop will now fire when show is false */} <motion.div exit={{ x: -100 }} /> </AnimatePresence> </motion.section> ) : null} </AnimatePresence>
故障排除
退出动画不工作
确保所有直接子元素都获得唯一的 key
属性,该属性在每次渲染中对于该组件保持不变。
例如,将 index
作为 key
提供是不好的,因为如果项目重新排序,则 index
将不会与 item
匹配
<AnimatePresence> {items.map((item, index) => ( <Component key={index} /> ))} </AnimatePresence>
最好传递对该项目唯一的内容,例如 ID
<AnimatePresence> {items.map((item) => ( <Component key={item.id} /> ))} </AnimatePresence>
还要确保 AnimatePresence
位于取消挂载元素的代码之外。如果 AnimatePresence
自身被取消挂载,那么它就无法控制退出动画!
例如,这将不起作用
isVisible && ( <AnimatePresence> <Component /> </AnimatePresence> )
相反,条件应该位于 AnimatePresence
的根部
<AnimatePresence> {isVisible && <Component />} </AnimatePresence>
布局动画在 mode="sync"
下不工作
当混合布局和退出动画时,可能需要将组包裹在 LayoutGroup
中,以确保 AnimatePresence
之外的组件知道何时执行布局动画。
<LayoutGroup> <motion.ul layout> <AnimatePresence> {items.map(item => ( <motion.li layout key={item.id} /> ))} </AnimatePresence> </motion.ul> </LayoutGroup>
布局动画在 mode="popLayout"
下不工作
当任何 HTML 元素具有活动的 transform
时,它会暂时成为偏移父元素其子元素。这可能会导致具有 position: "absolute"
的子元素未出现在您期望的位置。mode="popLayout"
通过使用 position: "absolute"
工作。因此,为了确保布局动画期间的一致且预期的定位,请确保动画父元素具有除 "static"
之外的 position
。
<motion.ul layout style={{ position: "relative" }}> <AnimatePresence mode="popLayout"> {items.map(item => ( <motion.li layout key={item.id} /> ))} </AnimatePresence> </motion.ul>
AnimatePresence
使退出动画变得简单。通过包裹一个或多个 motion
组件使用 AnimatePresence
,我们可以访问 exit
动画属性。
<AnimatePresence> {show && <motion.div key="modal" exit={{ opacity: 0 }} />} </AnimatePresence>
用法
导入
import { AnimatePresence } from "motion/react"
退出动画
AnimatePresence
通过检测其直接子元素何时从 React 树中移除来工作。
这可能是由于组件挂载/重新挂载
<AnimatePresence> {show && <Modal key="modal" />} </AnimatePresence>
其 key
更改
<AnimatePresence> <Slide key={activeItem.id} /> </AnimatePresence>
或者当列表中的子元素被添加/移除时
<AnimatePresence> {items.map(item => ( <motion.li key={item.id} exit={{ opacity: 1 }} layout /> ))} </AnimatePresence>
退出组件中的任何 motion
组件都将触发在其 exit
属性上定义的动画,然后在组件从 DOM 中移除之前执行。
function Slide({ img, description }) { return ( <motion.div exit={{ opacity: 0 }}> <img src={img.src} /> <motion.p exit={{ y: 10 }}>{description}</motion.p> </motion.div> ) }
注意:直接子元素必须各自具有唯一的 key
属性,以便 AnimatePresence
可以跟踪它们在树中的存在。
像 initial
和 animate
一样,exit
可以定义为值对象,也可以定义为变体标签。
const modalVariants = { visible: { opacity: 1, transition: { when: "beforeChildren" } }, hidden: { opacity: 0, transition: { when: "afterChildren" } } } function Modal({ children }) { return ( <motion.div initial="hidden" animate="visible" exit="hidden"> {children} </motion.div> ) }
更改 key
更改 key
属性使 React 创建一个全新的组件。因此,通过更改 AnimatePresence
的单个子元素的 key
,我们可以轻松制作像幻灯片一样的组件。
export const Slideshow = ({ image }) => ( <AnimatePresence> <motion.img key={image.src} src={image.src} initial={{ x: 300, opacity: 0 }} animate={{ x: 0, opacity: 1 }} exit={{ x: -300, opacity: 0 }} /> </AnimatePresence> )
访问 presence 状态
AnimatePresence
的任何子元素都可以使用 useIsPresence
hook 访问 presence 状态。
import { useIsPresent } from "motion/react" function Component() { const isPresent = useIsPresent() return isPresent ? "Here!" : "Exiting..." }
这允许您在组件不再渲染时更改内容或样式。
访问 presence 数据
当组件已从 React 树中移除时,其 props 将无法再更新。我们可以使用 AnimatePresence
的 custom
属性将新数据向下传递到树中,甚至传递到退出的组件中。
<AnimatePresence custom={swipeDirection}> <Slide key={activeSlideId}>
然后稍后我们可以使用 usePresenceData
提取该数据。
import { AnimatePresence, usePresenceData } from "motion/react" function Slide() { const isPresent = useIsPresent() const direction = usePresenceData() return ( <motion.div exit={{ opacity: 0 }}> {isPresent ? "Here!" : "Exiting " + direction} </motion.div> ) }
手动用法
也可以手动告知 AnimatePresence
何时可以使用 usePresence
hook 安全地移除组件。
这会返回 isPresent
状态和一个回调 safeToRemove
,当您准备好从 DOM 中移除组件时(例如在手动动画或其他超时后),应调用该回调。
import { usePresence } from "motion/react" function Component() { const [isPresent, safeToRemove] = usePresence() useEffect(() => { // Remove from DOM 1000ms after being removed from React !isPresent && setTimeout(safeToRemove, 1000) }, [isPresent]) return <div /> }
传播退出动画
默认情况下,AnimatePresence
控制其所有子元素的 exit
动画,直到渲染另一个 AnimatePresence
组件。
<AnimatePresence> {show ? ( <motion.section exit={{ opacity: 0 }}> <AnimatePresence> {/* * When `show` becomes `false`, exit animations * on these children will not fire. */} {children} </AnimatePresence> </motion.section> ) : null} </AnimatePresence>
通过将 AnimatePresence
组件的 propagate
属性设置为 true
,当它从另一个 AnimatePresence
中移除时,它将触发其所有子元素的退出动画。
<AnimatePresence> {show ? ( <motion.section exit={{ opacity: 0 }}> <AnimatePresence propagate> {/* * When `show` becomes `false`, exit animations * on these children **will** fire. */} {children} </AnimatePresence> </motion.section> ) : null} </AnimatePresence>
属性
initial
通过传递 initial={false}
,AnimatePresence
将禁用组件首次渲染时存在的子元素的任何初始动画。
<AnimatePresence initial={false}> <Slide key={activeItem.id} /> </AnimatePresence>
custom
当组件被移除时,不再有机会更新其 props(因为它不再在 React 树中)。因此,我们无法使用移除组件的相同渲染来更新其退出动画。
通过 AnimatePresence
的 custom
属性传递一个值,我们可以使用动态变体来更改 exit
动画。
const variants = { hidden: (direction) => ({ opacity: 0, x: direction === 1 ? -300 : 300 }), visible: { opacity: 1, x: 0 } } export const Slideshow = ({ image, direction }) => ( <AnimatePresence custom={direction}> <motion.img key={image.src} src={image.src} variants={variants} initial="hidden" animate="visible" exit="hidden" /> </AnimatePresence> )
子元素可以通过 usePresenceData
访问此数据。
mode
默认值:"sync"
决定 AnimatePresence
如何处理进入和退出的子元素。
"sync"
:子元素在添加/移除后立即进行动画进入/退出。"wait"
:进入的子元素将等待直到退出的子元素动画退出。注意:目前一次只渲染一个子元素。"popLayout"
:退出的子元素将从页面布局中“弹出”。这允许周围的元素立即移动到它们的新布局。
自定义组件注意:当使用 popLayout
模式时,AnimatePresence 的任何直接子元素,如果是一个自定义组件,必须被 React 的 forwardRef
函数包裹,并将提供的 ref
转发到您希望从布局中弹出的 DOM 节点。
onExitComplete
当所有退出的节点都完成动画退出后触发。
propagate
默认值: false
如果设置为 true
,则当此 AnimatePresence
从父级 AnimatePresence
退出时,也会触发子元素的退出动画。
<AnimatePresence> {show ? ( <motion.section exit={{ opacity: 0 }}> <AnimatePresence propagate> {/* This exit prop will now fire when show is false */} <motion.div exit={{ x: -100 }} /> </AnimatePresence> </motion.section> ) : null} </AnimatePresence>
故障排除
退出动画不工作
确保所有直接子元素都获得唯一的 key
属性,该属性在每次渲染中对于该组件保持不变。
例如,将 index
作为 key
提供是不好的,因为如果项目重新排序,则 index
将不会与 item
匹配
<AnimatePresence> {items.map((item, index) => ( <Component key={index} /> ))} </AnimatePresence>
最好传递对该项目唯一的内容,例如 ID
<AnimatePresence> {items.map((item) => ( <Component key={item.id} /> ))} </AnimatePresence>
还要确保 AnimatePresence
位于取消挂载元素的代码之外。如果 AnimatePresence
自身被取消挂载,那么它就无法控制退出动画!
例如,这将不起作用
isVisible && ( <AnimatePresence> <Component /> </AnimatePresence> )
相反,条件应该位于 AnimatePresence
的根部
<AnimatePresence> {isVisible && <Component />} </AnimatePresence>
布局动画在 mode="sync"
下不工作
当混合布局和退出动画时,可能需要将组包裹在 LayoutGroup
中,以确保 AnimatePresence
之外的组件知道何时执行布局动画。
<LayoutGroup> <motion.ul layout> <AnimatePresence> {items.map(item => ( <motion.li layout key={item.id} /> ))} </AnimatePresence> </motion.ul> </LayoutGroup>
布局动画在 mode="popLayout"
下不工作
当任何 HTML 元素具有活动的 transform
时,它会暂时成为偏移父元素其子元素。这可能会导致具有 position: "absolute"
的子元素未出现在您期望的位置。mode="popLayout"
通过使用 position: "absolute"
工作。因此,为了确保布局动画期间的一致且预期的定位,请确保动画父元素具有除 "static"
之外的 position
。
<motion.ul layout style={{ position: "relative" }}> <AnimatePresence mode="popLayout"> {items.map(item => ( <motion.li layout key={item.id} /> ))} </AnimatePresence> </motion.ul>
AnimatePresence
使退出动画变得简单。通过包裹一个或多个 motion
组件使用 AnimatePresence
,我们可以访问 exit
动画属性。
<AnimatePresence> {show && <motion.div key="modal" exit={{ opacity: 0 }} />} </AnimatePresence>
用法
导入
import { AnimatePresence } from "motion/react"
退出动画
AnimatePresence
通过检测其直接子元素何时从 React 树中移除来工作。
这可能是由于组件挂载/重新挂载
<AnimatePresence> {show && <Modal key="modal" />} </AnimatePresence>
其 key
更改
<AnimatePresence> <Slide key={activeItem.id} /> </AnimatePresence>
或者当列表中的子元素被添加/移除时
<AnimatePresence> {items.map(item => ( <motion.li key={item.id} exit={{ opacity: 1 }} layout /> ))} </AnimatePresence>
退出组件中的任何 motion
组件都将触发在其 exit
属性上定义的动画,然后在组件从 DOM 中移除之前执行。
function Slide({ img, description }) { return ( <motion.div exit={{ opacity: 0 }}> <img src={img.src} /> <motion.p exit={{ y: 10 }}>{description}</motion.p> </motion.div> ) }
注意:直接子元素必须各自具有唯一的 key
属性,以便 AnimatePresence
可以跟踪它们在树中的存在。
像 initial
和 animate
一样,exit
可以定义为值对象,也可以定义为变体标签。
const modalVariants = { visible: { opacity: 1, transition: { when: "beforeChildren" } }, hidden: { opacity: 0, transition: { when: "afterChildren" } } } function Modal({ children }) { return ( <motion.div initial="hidden" animate="visible" exit="hidden"> {children} </motion.div> ) }
更改 key
更改 key
属性使 React 创建一个全新的组件。因此,通过更改 AnimatePresence
的单个子元素的 key
,我们可以轻松制作像幻灯片一样的组件。
export const Slideshow = ({ image }) => ( <AnimatePresence> <motion.img key={image.src} src={image.src} initial={{ x: 300, opacity: 0 }} animate={{ x: 0, opacity: 1 }} exit={{ x: -300, opacity: 0 }} /> </AnimatePresence> )
访问 presence 状态
AnimatePresence
的任何子元素都可以使用 useIsPresence
hook 访问 presence 状态。
import { useIsPresent } from "motion/react" function Component() { const isPresent = useIsPresent() return isPresent ? "Here!" : "Exiting..." }
这允许您在组件不再渲染时更改内容或样式。
访问 presence 数据
当组件已从 React 树中移除时,其 props 将无法再更新。我们可以使用 AnimatePresence
的 custom
属性将新数据向下传递到树中,甚至传递到退出的组件中。
<AnimatePresence custom={swipeDirection}> <Slide key={activeSlideId}>
然后稍后我们可以使用 usePresenceData
提取该数据。
import { AnimatePresence, usePresenceData } from "motion/react" function Slide() { const isPresent = useIsPresent() const direction = usePresenceData() return ( <motion.div exit={{ opacity: 0 }}> {isPresent ? "Here!" : "Exiting " + direction} </motion.div> ) }
手动用法
也可以手动告知 AnimatePresence
何时可以使用 usePresence
hook 安全地移除组件。
这会返回 isPresent
状态和一个回调 safeToRemove
,当您准备好从 DOM 中移除组件时(例如在手动动画或其他超时后),应调用该回调。
import { usePresence } from "motion/react" function Component() { const [isPresent, safeToRemove] = usePresence() useEffect(() => { // Remove from DOM 1000ms after being removed from React !isPresent && setTimeout(safeToRemove, 1000) }, [isPresent]) return <div /> }
传播退出动画
默认情况下,AnimatePresence
控制其所有子元素的 exit
动画,直到渲染另一个 AnimatePresence
组件。
<AnimatePresence> {show ? ( <motion.section exit={{ opacity: 0 }}> <AnimatePresence> {/* * When `show` becomes `false`, exit animations * on these children will not fire. */} {children} </AnimatePresence> </motion.section> ) : null} </AnimatePresence>
通过将 AnimatePresence
组件的 propagate
属性设置为 true
,当它从另一个 AnimatePresence
中移除时,它将触发其所有子元素的退出动画。
<AnimatePresence> {show ? ( <motion.section exit={{ opacity: 0 }}> <AnimatePresence propagate> {/* * When `show` becomes `false`, exit animations * on these children **will** fire. */} {children} </AnimatePresence> </motion.section> ) : null} </AnimatePresence>
属性
initial
通过传递 initial={false}
,AnimatePresence
将禁用组件首次渲染时存在的子元素的任何初始动画。
<AnimatePresence initial={false}> <Slide key={activeItem.id} /> </AnimatePresence>
custom
当组件被移除时,不再有机会更新其 props(因为它不再在 React 树中)。因此,我们无法使用移除组件的相同渲染来更新其退出动画。
通过 AnimatePresence
的 custom
属性传递一个值,我们可以使用动态变体来更改 exit
动画。
const variants = { hidden: (direction) => ({ opacity: 0, x: direction === 1 ? -300 : 300 }), visible: { opacity: 1, x: 0 } } export const Slideshow = ({ image, direction }) => ( <AnimatePresence custom={direction}> <motion.img key={image.src} src={image.src} variants={variants} initial="hidden" animate="visible" exit="hidden" /> </AnimatePresence> )
子元素可以通过 usePresenceData
访问此数据。
mode
默认值:"sync"
决定 AnimatePresence
如何处理进入和退出的子元素。
"sync"
:子元素在添加/移除后立即进行动画进入/退出。"wait"
:进入的子元素将等待直到退出的子元素动画退出。注意:目前一次只渲染一个子元素。"popLayout"
:退出的子元素将从页面布局中“弹出”。这允许周围的元素立即移动到它们的新布局。
自定义组件注意:当使用 popLayout
模式时,AnimatePresence 的任何直接子元素,如果是一个自定义组件,必须被 React 的 forwardRef
函数包裹,并将提供的 ref
转发到您希望从布局中弹出的 DOM 节点。
onExitComplete
当所有退出的节点都完成动画退出后触发。
propagate
默认值: false
如果设置为 true
,则当此 AnimatePresence
从父级 AnimatePresence
退出时,也会触发子元素的退出动画。
<AnimatePresence> {show ? ( <motion.section exit={{ opacity: 0 }}> <AnimatePresence propagate> {/* This exit prop will now fire when show is false */} <motion.div exit={{ x: -100 }} /> </AnimatePresence> </motion.section> ) : null} </AnimatePresence>
故障排除
退出动画不工作
确保所有直接子元素都获得唯一的 key
属性,该属性在每次渲染中对于该组件保持不变。
例如,将 index
作为 key
提供是不好的,因为如果项目重新排序,则 index
将不会与 item
匹配
<AnimatePresence> {items.map((item, index) => ( <Component key={index} /> ))} </AnimatePresence>
最好传递对该项目唯一的内容,例如 ID
<AnimatePresence> {items.map((item) => ( <Component key={item.id} /> ))} </AnimatePresence>
还要确保 AnimatePresence
位于取消挂载元素的代码之外。如果 AnimatePresence
自身被取消挂载,那么它就无法控制退出动画!
例如,这将不起作用
isVisible && ( <AnimatePresence> <Component /> </AnimatePresence> )
相反,条件应该位于 AnimatePresence
的根部
<AnimatePresence> {isVisible && <Component />} </AnimatePresence>
布局动画在 mode="sync"
下不工作
当混合布局和退出动画时,可能需要将组包裹在 LayoutGroup
中,以确保 AnimatePresence
之外的组件知道何时执行布局动画。
<LayoutGroup> <motion.ul layout> <AnimatePresence> {items.map(item => ( <motion.li layout key={item.id} /> ))} </AnimatePresence> </motion.ul> </LayoutGroup>
布局动画在 mode="popLayout"
下不工作
当任何 HTML 元素具有活动的 transform
时,它会暂时成为偏移父元素其子元素。这可能会导致具有 position: "absolute"
的子元素未出现在您期望的位置。mode="popLayout"
通过使用 position: "absolute"
工作。因此,为了确保布局动画期间的一致且预期的定位,请确保动画父元素具有除 "static"
之外的 position
。
<motion.ul layout style={{ position: "relative" }}> <AnimatePresence mode="popLayout"> {items.map(item => ( <motion.li layout key={item.id} /> ))} </AnimatePresence> </motion.ul>