折叠收起
元素高度从完整状态收缩至零并淡出,适合手风琴、FAQ 折叠面板等场景。
交互预览
动效定义与代码
{
"slug": "collapse",
"name": "折叠收起",
"nameEn": "Collapse",
"description": "元素高度从完整状态收缩至零并淡出,适合手风琴、FAQ 折叠面板等场景。",
"descriptionEn": "Element height shrinks to zero while fading out. Ideal for accordions, FAQ panels, and collapsible sections.",
"category": "exit",
"tags": [
"collapse",
"height",
"exit",
"accordion"
],
"trigger": "on-click",
"difficulty": "intermediate",
"duration": "350ms",
"easing": "cubic-bezier(0.4, 0, 0.2, 1)",
"cssProperties": [
"max-height",
"opacity",
"padding"
],
"isGPUAccelerated": false,
"previewBg": "light",
"keywords": [
"collapse",
"accordion",
"fold",
"shrink height",
"toggle",
"expand"
],
"useCases": [
"Accordion panel collapse",
"FAQ section toggle",
"Expandable card content",
"Sidebar section folding"
],
"relatedAnimations": [
"fade-out-down",
"scale-out"
],
"recommendedStyles": [
"minimalist-flat",
"corporate-clean"
],
"codeSnippets": [
{
"label": "CSS Keyframes",
"language": "css",
"code": "@keyframes collapse {\n from {\n max-height: var(--sk-content-height, 200px);\n opacity: 1;\n padding-top: var(--sk-padding, 16px);\n padding-bottom: var(--sk-padding, 16px);\n }\n to {\n max-height: 0;\n opacity: 0;\n padding-top: 0;\n padding-bottom: 0;\n }\n}\n\n.collapse {\n --sk-duration: 350ms;\n --sk-delay: 0ms;\n --sk-ease: cubic-bezier(0.4, 0, 0.2, 1);\n overflow: hidden;\n animation: collapse var(--sk-duration) var(--sk-ease) var(--sk-delay) both;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .collapse {\n animation: none;\n max-height: 0;\n opacity: 0;\n padding: 0;\n }\n}"
},
{
"label": "Tailwind CSS",
"language": "css",
"code": "@keyframes collapse {\n from {\n max-height: var(--sk-content-height, 200px);\n opacity: 1;\n padding-top: var(--sk-padding, 16px);\n padding-bottom: var(--sk-padding, 16px);\n }\n to {\n max-height: 0;\n opacity: 0;\n padding-top: 0;\n padding-bottom: 0;\n }\n}\n\n@utility animate-collapse {\n --sk-duration: 350ms;\n --sk-delay: 0ms;\n --sk-ease: cubic-bezier(0.4, 0, 0.2, 1);\n overflow: hidden;\n animation: collapse var(--sk-duration) var(--sk-ease) var(--sk-delay) both;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .collapse {\n animation: none;\n max-height: 0;\n opacity: 0;\n padding: 0;\n }\n}"
},
{
"label": "AnimeJS",
"language": "tsx",
"code": "\"use client\";\n\nimport { useEffect, useRef, useState, type ReactNode } from \"react\";\n\ntype AnimeAnimation = {\n cancel(): unknown;\n};\n\nfunction prefersReducedMotion() {\n return window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n}\n\nexport function CollapsePanel({ children }: { children: ReactNode }) {\n const [isOpen, setIsOpen] = useState(true);\n const panelRef = useRef<HTMLDivElement>(null);\n const animationRef = useRef<AnimeAnimation | null>(null);\n\n useEffect(() => {\n return () => {\n animationRef.current?.cancel();\n };\n }, []);\n\n async function toggle() {\n const panel = panelRef.current;\n if (!panel) return;\n\n const nextOpen = !isOpen;\n setIsOpen(nextOpen);\n\n if (prefersReducedMotion()) {\n panel.style.height = nextOpen ? \"auto\" : \"0px\";\n panel.style.opacity = nextOpen ? \"1\" : \"0\";\n return;\n }\n\n const { animate } = await import(\"animejs\");\n if (panelRef.current !== panel) return;\n\n animationRef.current?.cancel();\n const startHeight = panel.getBoundingClientRect().height;\n const targetHeight = nextOpen ? panel.scrollHeight : 0;\n panel.style.height = startHeight + \"px\";\n\n const animation = animate(panel, {\n height: [startHeight + \"px\", targetHeight + \"px\"],\n opacity: [Number(getComputedStyle(panel).opacity), nextOpen ? 1 : 0],\n duration: 350,\n ease: \"inOut(3)\",\n onComplete: () => {\n panel.style.height = nextOpen ? \"auto\" : \"0px\";\n panel.style.opacity = nextOpen ? \"1\" : \"0\";\n },\n });\n\n animationRef.current = animation;\n }\n\n return (\n <div>\n <button type=\"button\" aria-expanded={isOpen} onClick={() => void toggle()}>\n Toggle\n </button>\n <div ref={panelRef} className=\"overflow-hidden\">\n {children}\n </div>\n </div>\n );\n}"
},
{
"label": "Framer Motion",
"language": "tsx",
"code": "import { motion, AnimatePresence } from \"framer-motion\";\n\n<AnimatePresence>\n {isOpen && (\n <motion.div\n initial={{ height: 0, opacity: 0 }}\n animate={{ height: \"auto\", opacity: 1 }}\n exit={{ height: 0, opacity: 0 }}\n transition={{ duration: 0.35, ease: [0.4, 0, 0.2, 1] }}\n style={{ overflow: \"hidden\" }}\n >\n Content\n </motion.div>\n )}\n</AnimatePresence>"
}
]
}