Skills Plugins MCP Prompt Model 博客 我的中心
開発 #react

motion-patterns

Production-ready animation patterns for React / Next.js — button, modal, toast, stagger, page transitions, exit animations, scroll, and layout — built on motion-foundations tokens and springs. Use when animating a specific UI element in React or Next.js — button, modal, toast, stagger, page transition, or scroll.

DeepseekModel キュレーション済みスキル 品質 優秀 · 90 v1.0.0

取得

https://deepseekmodel.com/api/download.php?id=affaan-m-ecc-skills-motion-patterns-skill-md&format=skill
ダウンロード .skill 標準形式。system_prompt と model_config を収録し、任意の Agent で利用可能
.skill ファイルの system_prompt フィールドの実際の内容。
name motion-patterns description Production-ready animation patterns for React / Next.js — button, modal, toast, stagger, page transitions, exit animations, scroll, and layout — built on motion-foundations tokens and springs. Use when animating a specific UI element in React or Next.js — button, modal, toast, stagger, page transition, or scroll. tags ["motion","animation","ui-patterns"] category frontend author jeff metadata {"version":"1.0.0"} Motion Patterns Copy-paste patterns for the most common UI animation needs. Every pattern here is built on motion-foundations tokens and springs. Do not define new duration or easing values here — import them. When to Activate Animating a button, card, modal, or toast notification Building list entrances with stagger Setting up page transitions in Next.js App Router Adding entrance or exit animations to conditional content Implementing scroll-reveal, scroll-linked progress, or sticky story sections Building expanding cards, accordions, or shared-element transitions Outputs This skill produces: Accessible, SSR-safe animation for all standard UI components AnimatePresence -wrapped conditional renders with correct exit behavior Page transition wrapper component for Next.js App Router Scroll-reveal and scroll-linked patterns using useScroll + useTransform Layout animation patterns ( layout , layoutId ) for expanding and crossfading elements Principles Every pattern imports from motion-foundations . No raw numbers. Every conditional render is wrapped in AnimatePresence with a key . Exit animations are always defined alongside enter animations — never as an afterthought. layout is used only for small, isolated shifts. Large subtrees get explicit transforms. Rules Always wrap conditional renders in AnimatePresence with a key on the direct child. Without a key, exit animations never fire. Always define exit when defining initial + animate . An animation without an exit is incomplete. Use mode="wait" on page transitions. Enter must not start until exit completes. Never use layout on subtrees with more than ~5 children or deeply nested DOM. Use explicit x / y transforms instead. Stagger interval must stay between 0.05s and 0.10s . Below feels mechanical; above feels sluggish. Modals must always include: focus trap, Escape-key close, scroll lock, role="dialog" , aria-modal="true" . Scroll reveals use viewport={{ once: true }} . Repeating on scroll-out is distracting, not informative. All token values are imported from motion-foundations . No inline numbers. Decision Guidance Choosing the right pattern Situation Pattern Element appears / disappears AnimatePresence List of items loading in sequence Stagger variants Navigating between routes Page transition wrapper Element changes size in place layout prop Same element moves across page contexts layoutId Element enters when scrolled into view whileInView Value tied to scroll position useScroll + useTransform When to use mode="wait" vs mode="sync" Mode Use when wait Page transitions, content swaps (one at a time) sync Stacked notifications, list items (overlap is fine) popLayout Items removed from a reflow list Core Concepts AnimatePresence contract Three things must always be true: AnimatePresence wraps the conditional The direct child has a key The child has an exit prop Miss any one of these and the exit animation silently fails. layout vs layoutId layout — animates the element's own size/position change in place layoutId — links two separate elements, crossfading between them across renders Use layout="position" on text inside an expanding container to prevent text reflow from animating. Code Examples Button feedback "use client" import { motion } from "motion/react" import { springs, motionTokens } from "@/lib/motion-tokens" <motion. button whileHover={{ scale : motionTokens. scale . pop }} whileTap={{ scale : motionTokens. scale . press }} transition={springs. snappy } /> Stagger list "use client" import { motion } from "motion/react" import { motionTokens, springs } from "@/lib/motion-tokens" const container = { hidden : {}, visible : { transition : { staggerChildren : 0.08 , // within the 0.05–0.10 rule delayChildren : 0.1 , }, }, } const item = { hidden : { opacity : 0 , y : motionTokens. distance . md }, visible : { opacity : 1 , y : 0 , transition : springs. gentle }, } <motion. ul variants={container} initial= "hidden" animate= "visible" > {items. map ( ( i ) => ( < motion.li key = {i.id} variants = {item} /> ))} </motion. ul > Modal "use client" import { motion, AnimatePresence } from "motion/react" import { motionTokens, springs } from "@/lib/motion-tokens" // Wrap at the call site: // <AnimatePresence>{isOpen && <Modal key="modal" />}</AnimatePresence> export function Modal ( { onClose }: { onClose: () => void } ) { return ( <> {/* Overlay */} < motion.div className = "fixed inset-0 bg-black/50" initial = {{ opacity: 0 }} animate = {{ opacity: 1 }} exit = {{ opacity: 0 }} onClick = {onClose} /> {/* Panel — accessibility requirements: focus trap, Escape close, scroll lock, role="dialog", aria-modal="true" */} < motion.div role = "dialog" aria-modal = "true" className = "fixed inset-x-4 top-1/2 -translate-y-1/2 rounded-xl bg-white p-6" initial = {{ opacity: 0 , scale: motionTokens.scale.press , y: motionTokens.distance.sm , }} animate = {{ opacity: 1 , scale: 1 , y: 0 }} exit = {{ opacity: 0 , scale: motionTokens.scale.press , y: motionTokens.distance.sm , }} transition = {springs.gentle} /> </> ) } Toast stack "use client" import { motion, AnimatePresence } from "motion/react" import { motionTokens, springs } from "@/lib/motion-tokens" < AnimatePresence mode= "sync" > {toasts. map ( ( t ) => ( < motion.div key = {t.id} layout initial = {{ opacity: 0 , x: motionTokens.distance.xl , scale: motionTokens.scale.subtle , }} animate = {{ opacity: 1 , x: 0 , scale: 1 }} exit = {{ opacity: 0 , x: motionTokens.distance.xl , scale: motionTokens.scale.subtle , }} transition = {springs.snappy} /> ))} </ AnimatePresence > Page transition (Next.js App Router) // components/page-transition.tsx "use client" import { motion, AnimatePresence } from "motion/react" import { usePathname } from "next/navigation" import { motionTokens } from "@/lib/motion-tokens" const variants = { initial : { opacity : 0 , y : motionTokens. distance . sm }, enter : { opacity : 1 , y : 0 }, exit : { opacity : 0 , y : -motionTokens. distance . sm }, } export function PageTransition ( { children }: { children: React.ReactNode } ) { const pathname = usePathname () return ( < AnimatePresence mode = "wait" > < motion.div key = {pathname} variants = {variants} initial = "initial" animate = "enter" exit = "exit" transition = {{ duration: motionTokens.duration.normal , ease: motionTokens.easing.smooth , }} > {children} </ motion.div > </ AnimatePresence > ) } Scroll reveal "use client" import { motion } from "motion/react" import { motionTokens, springs } from "@/lib/motion-tokens" <motion. div initial={{ opacity : 0 , y : motionTokens. distance . lg }} whileInView={{ opacity : 1 , y : 0 }} viewport={{ once : true , margin : "-80px" }} // once: true — rule 7 transition={{ duration : motionTokens. duration . slow , ease : motionTokens. easing . smooth }} /> Scroll progress bar "use client" import { motion, useScroll } from "motion/react" export function ScrollProgress ( ) { const { scrollYProgress } = useScroll () return ( < motion.div className = "fixed top-0 left-0 h-1 bg-indigo-500 origin-left w-full" style = {{ scaleX: scrollYProgress }} /> ) } Expanding card "use client" import { useState } from "react" import { motion, AnimatePresence } from "motion/react" import { springs, motionTokens } from "@/lib/motion-tokens" export function ExpandingCard ( { title, body }: { title: string ; body: string } ) { const [expanded, setExpanded] = useState ( false ) return ( < motion.div layout onClick = {() => setExpanded(!expanded)} className="cursor-pointer"> {/* layout="position" prevents text reflow from animating */} < motion.h2 layout = "position" className = "font-semibold" > {title} </ motion.h2 > < AnimatePresence > {expanded && ( < motion.p key = "body" initial = {{ opacity: 0 }} animate = {{ opacity: 1 }} exit = {{ opacity: 0 }} transition = {{ duration: motionTokens.duration.fast }} > {body} </ motion.p > )} </ AnimatePresence > </ motion.div > ) } Shared-element crossfade // Source context <motion. img layoutId= "hero-image" src={src} className= "w-16 h-16 rounded" /> // Destination context (same layoutId — motion handles the transition) < motion.img layoutId = "hero-image" src = {src} className = "w-full rounded-xl" /> Accordion <motion. div initial={ false } animate={{ opacity : open ? 1 : 0 , scaleY : open ? 1 : 0 }} style={{ transformOrigin : "top" , overflow : "hidden" }} transition={{ duration : motionTokens. duration . normal , ease : motionTokens. easing . smooth , }} > {children} </motion. div > End-to-End Example A staggered list that enters on mount, handles conditional presence, and respects reduced motion — combining tokens, springs, AnimatePresence, and the accessibility hook from motion-foundations : "use client" import { useState } from "react" import { motion, AnimatePresence } from "motion/react" import { motionTokens, springs } from "@/lib/motion-tokens" import { useSafeMotion } from "@/hooks/use-reduced-motion" const containerVariants = { hidden : {}, visible : { transition : { staggerChildren : 0.08 , delayChildren : 0.1 }, }, } function ListItem ( { label, onRemove }: { label: string ; onRemove: () => void } ) { const safe = useSafeMotion (motionTokens. distance . sm ) return ( < motion.li variants = {{ hidden: safe.initial , visible: safe.animate , }} exit = {safe.exit} transition = {springs.gentle} className = "flex items-center justify-between p-3 rounded-lg bg-white shadow-sm" > < span > {label} </ span > < button onClick = {onRemove} > Remove </ button > </ motion.li > ) } export function AnimatedList ( { items, onRemove }: { items: { id: string ; label: string }[] onRemove: (id: string ) => void } ) { return ( < motion.ul variants = {containerVariants} initial = "hidden" animate = "visible" className = "space-y-2" > < AnimatePresence mode = "popLayout" > {items.map((item) => ( < ListItem key = {item.id} label = {item.label} onRemove = {() => onRemove(item.id)} /> ))} </ AnimatePresence > </ motion.ul > ) }
このスキルを起動するキーワード。クリックでコピーできます。

このスキルにはトリガーワードがありません。

ダウンロードした .skill に含まれるフィールド。
フィールド 説明
formatフォーマット識別子(skill/v1)
skill_idスキル固有 ID
nameスキル名
versionバージョン
description説明
categoryカテゴリ(配列)
trigger_wordsトリガーワード
tagsタグ
sourceソース
source_urlソース URL(本ページ)
exported_atエクスポート日時(ダウンロード毎)
system_promptシステムプロンプト本文
model_configモデル設定:provider / model / temperature / max_tokens / top_p
examplesサンプル
install_guide各プラットフォームの導入説明(Coze / Dify / Claude / カスタム)
同じスキルを各プラットフォーム形式で出力できます。
.skill 標準形式。system_prompt と model_config を収録し、任意の Agent で利用可能 ダウンロード
.skillpro 拡張形式。scripts / tools / dependencies / hooks を含む ダウンロード
.json 純粋な JSON 出力。system_prompt とモデル設定のみ ダウンロード
Coze frontmatter 付き Markdown。Coze へのインポート用 ダウンロード
Dify Dify DSL。アプリ作成後にそのままインポート ダウンロード

每日精选 Skill 推荐,免费送到你邮箱

输入邮箱,每天接收一个精选 AI Agent 技能推荐。完全免费,持续更新。

提交后我们会发送一封确认邮件,点击邮件里的链接才会开始收信。

完全免费,取消任意时间。我们不会发送垃圾邮件。