滚动显现
元素进入视口时淡入显现,利用 IntersectionObserver 实现滚动触发。
交互预览
动效定义与代码
{
"slug": "scroll-reveal",
"name": "滚动显现",
"nameEn": "Scroll Reveal",
"description": "元素进入视口时淡入显现,利用 IntersectionObserver 实现滚动触发。",
"descriptionEn": "Element fades in when entering the viewport, powered by IntersectionObserver for scroll-triggered reveals.",
"category": "scroll",
"tags": [
"scroll",
"reveal",
"intersection",
"viewport"
],
"trigger": "on-scroll",
"difficulty": "intermediate",
"duration": "600ms",
"easing": "cubic-bezier(0.16, 1, 0.3, 1)",
"cssProperties": [
"opacity",
"transform"
],
"isGPUAccelerated": true,
"previewBg": "light",
"keywords": [
"scroll",
"reveal",
"viewport",
"intersection observer",
"lazy",
"on-scroll"
],
"useCases": [
"Landing page sections",
"Content-heavy pages",
"Portfolio project reveals",
"Feature list staggered entrance"
],
"relatedAnimations": [
"fade-in-up",
"parallax-float",
"stagger-children"
],
"recommendedStyles": [
"full-page-scroll",
"hero-fullscreen",
"editorial"
],
"codeSnippets": [
{
"label": "CSS Keyframes",
"language": "css",
"code": "@keyframes scroll-reveal {\n from {\n opacity: 0;\n transform: translateY(30px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.scroll-reveal {\n --sk-duration: 600ms;\n --sk-delay: 0ms;\n --sk-ease: cubic-bezier(0.16, 1, 0.3, 1);\n will-change: transform, opacity;\n opacity: 0;\n}\n\n.scroll-reveal.is-visible {\n animation: scroll-reveal var(--sk-duration) var(--sk-ease) var(--sk-delay) both;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .scroll-reveal {\n animation: none;\n opacity: 1;\n transform: none;\n }\n}"
},
{
"label": "Tailwind CSS",
"language": "tsx",
"code": "// Hook + Tailwind approach\nimport { useEffect, useRef, useState } from \"react\";\n\nfunction useScrollReveal() {\n const ref = useRef<HTMLDivElement>(null);\n const [isVisible, setIsVisible] = useState(false);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const observer = new IntersectionObserver(\n ([entry]) => {\n if (entry.isIntersecting) {\n setIsVisible(true);\n observer.unobserve(el);\n }\n },\n { threshold: 0.1 }\n );\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return { ref, isVisible };\n}\n\n// Usage:\n// const { ref, isVisible } = useScrollReveal();\n// <div ref={ref}\n// className={`transition-all duration-600 ease-out\n// ${isVisible\n// ? \"opacity-100 translate-y-0\"\n// : \"opacity-0 translate-y-8\"}`}\n// />"
},
{
"label": "Framer Motion",
"language": "tsx",
"code": "import { motion } from \"framer-motion\";\n\n// Tip: respect user's motion preferences\n// import { useReducedMotion } from \"framer-motion\";\n// const reduced = useReducedMotion();\n// Use reduced ? { opacity: 0 } : { opacity: 0, y: 30 } for initial\n\n<motion.div\n initial={{ opacity: 0, y: 30 }}\n whileInView={{ opacity: 1, y: 0 }}\n viewport={{ once: true, margin: \"-10%\" }}\n transition={{ duration: 0.6, ease: [0.16, 1, 0.3, 1] }}\n>\n Scroll to reveal\n</motion.div>"
}
]
}