组件
视口进入动效 In View
当元素滚动进入视口可见范围时,自动触发淡入、位移或自定义变体动画的交叉观察(Intersection Observer)组件。
基础用法
向下滚动页面,元素在进入视口交叉范围时平滑淡入并向上浮现:
Loading…
安装与引入
通过 CLI 自动添加组件,或手动复制源码至项目中:
pnpm dlx @wui-design/cli@latest add @wui/in-view安装基础依赖与动效库
pnpm add motion lucide-react class-variance-authority clsx tailwind-merge复制组件源码到
components/ui/in-view.tsx"use client"
import * as React from "react"
import {
motion,
useInView as useMotionInView,
useReducedMotion,
type HTMLMotionProps,
type Transition,
type UseInViewOptions,
type Variants,
} from "motion/react"
const defaultVariants: Variants = {
hidden: { opacity: 0, y: 16 },
visible: { opacity: 1, y: 0 },
}
export interface InViewProps extends Omit<
HTMLMotionProps<"div">,
"children" | "variants"
> {
/** Content animated when it enters the viewport. */
children: React.ReactNode
/** Hidden and visible animation states. */
variants?: Variants
/** Motion transition used between states. */
transition?: Transition
/** Intersection observer options such as `margin` and `amount`. */
viewOptions?: UseInViewOptions
/** Only play the entrance animation once. @default true */
once?: boolean
/** HTML element rendered by the component. @default "div" */
as?: React.ElementType
}
/** Animates content when it enters the viewport. */
function InView({
children,
variants = defaultVariants,
transition = { duration: 0.45, ease: "easeOut" },
viewOptions,
once = true,
as = "div",
...props
}: InViewProps) {
const ref = React.useRef<HTMLElement>(null)
const reduceMotion = useReducedMotion()
const isInView = useMotionInView(ref, { ...viewOptions, once })
const MotionComponent = React.useMemo(() => motion.create(as), [as])
return (
<MotionComponent
ref={ref}
data-slot="in-view"
initial="hidden"
animate={reduceMotion || isInView ? "visible" : "hidden"}
variants={variants}
transition={reduceMotion ? { duration: 0 } : transition}
{...props}
>
{children}
</MotionComponent>
)
}
export { InView }
属性 Props
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| children | React.ReactNode | — | 受视口交叉状态触发进场动效的子内容。 |
| variants | Variants | { hidden: { opacity: 0, y: 16 }, visible: { opacity: 1, y: 0 } } | 包含 hidden(初始离场状态)与 visible(进场状态)的 Motion 变体对象。 |
| transition | Transition | { duration: 0.45, ease: "easeOut" } | 在 hidden 与 visible 状态之间过渡时所使用的动画参数。 |
| viewOptions | UseInViewOptions | — | IntersectionObserver 配置选项,包含 `margin`(视口边距阈值,如 `-10% 0px`)和 `amount`(可见比例,如 0.5)。 |
| once | boolean | true | 是否仅在第一次进入视口时执行一次动画(为 false 时每次进出视口均重新触发)。 |
| as | React.ElementType | "div" | 渲染的 HTML 标签类型(如 `div`、`section`、`article` 等)。 |
| className | string | — | 应用于外层动画容器的 CSS 类名。 |
事件 Events
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| onAnimationStart | () => void | — | 进场动效开始执行时触发。 |
| onAnimationComplete | () => void | — | 进场动效执行完成时触发。 |
使用场景与设计规范
InView 适用于落地页特性卡片流式呈现、数据图表首屏揭示、长文章图片渐进加载:
- 避免页面重入重复闪烁:建议大多数业务展示使用默认的
once={true},保证用户向上回滚时不重复触发进场动效。 - 配置视口提前量(margin):通过设置
viewOptions={{ margin: "-10% 0px" }},让元素在距离视口还有一定距离时就开始计算并自然进场,避免突兀。
场景示例
图片揭示
自定义 clipPath 与 scale 变体,让大图进入视口时自下而上揭开并从轻微放大回落:
Loading…
瀑布流/阶梯式卡片进场(Stagger)
为列表项赋予递增的 delay,配合模糊对焦变体实现阶梯式浮现,as="li" 保持列表语义:
Loading…
无障碍与交互 Accessibility
- 减少动效环境处理:当系统启用
prefers-reduced-motion: reduce时,组件保持与服务端一致的初始结构,挂载后立即以零时长切换到visible终态,不播放位移过程。 - SEO 友好:组件服务端渲染(SSR)默认包含完整 DOM 树与文字内容,不会对搜索引擎爬虫造成不可见问题。
