抖动提示
元素在水平方向快速抖动,常用于表单校验失败或错误提示的视觉反馈。
交互预览
动效定义与代码
{
"slug": "shake",
"name": "抖动提示",
"nameEn": "Shake",
"description": "元素在水平方向快速抖动,常用于表单校验失败或错误提示的视觉反馈。",
"descriptionEn": "Element shakes horizontally with decreasing amplitude. Commonly used for form validation errors or attention-grabbing feedback.",
"category": "micro-interaction",
"tags": [
"shake",
"error",
"feedback",
"attention",
"click"
],
"trigger": "on-click",
"difficulty": "beginner",
"duration": "500ms",
"easing": "ease-in-out",
"cssProperties": [
"transform"
],
"isGPUAccelerated": true,
"previewBg": "light",
"keywords": [
"shake",
"vibrate",
"error",
"validation",
"feedback",
"attention"
],
"useCases": [
"Form validation error feedback",
"Wrong password indication",
"Attention-grabbing notification",
"Interactive button feedback"
],
"relatedAnimations": [
"hover-lift",
"bounce-in"
],
"recommendedStyles": [
"neo-brutalist",
"modern-gradient",
"minimalist-flat"
],
"codeSnippets": [
{
"label": "CSS Keyframes",
"language": "css",
"code": "@keyframes shake {\n 0%, 100% {\n transform: translateX(0);\n }\n 10%, 50% {\n transform: translateX(-10px);\n }\n 20%, 60% {\n transform: translateX(10px);\n }\n 30%, 70% {\n transform: translateX(-8px);\n }\n 40%, 80% {\n transform: translateX(6px);\n }\n 90% {\n transform: translateX(-4px);\n }\n}\n\n.shake {\n will-change: transform;\n animation: shake 500ms ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .shake {\n animation: none;\n }\n}"
},
{
"label": "Tailwind CSS",
"language": "css",
"code": "/* Add to your global CSS or Tailwind v4 theme */\n@keyframes shake {\n 0%, 100% {\n transform: translateX(0);\n }\n 10%, 50% {\n transform: translateX(-10px);\n }\n 20%, 60% {\n transform: translateX(10px);\n }\n 30%, 70% {\n transform: translateX(-8px);\n }\n 40%, 80% {\n transform: translateX(6px);\n }\n 90% {\n transform: translateX(-4px);\n }\n}\n\n@utility animate-shake {\n will-change: transform;\n animation: shake 500ms ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .shake {\n animation: none;\n }\n}"
},
{
"label": "AnimeJS",
"language": "tsx",
"code": "\"use client\";\n\nimport { useEffect, useRef, 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 ShakeFeedback({ children }: { children: ReactNode }) {\n const targetRef = 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 triggerShake() {\n const target = targetRef.current;\n if (!target || prefersReducedMotion()) return;\n\n const { animate } = await import(\"animejs\");\n if (targetRef.current !== target) return;\n\n animationRef.current?.cancel();\n target.style.transform = \"\";\n\n animationRef.current = animate(target, {\n x: [0, -10, 10, -8, 8, -6, 4, -2, 0],\n duration: 500,\n ease: \"inOut(2)\",\n });\n }\n\n return (\n <div>\n <div ref={targetRef} className=\"will-change-transform\">\n {children}\n </div>\n <button type=\"button\" onClick={() => void triggerShake()}>\n Trigger shake\n </button>\n </div>\n );\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// Skip shake animation entirely when reduced is true\n\n<motion.div\n animate={{\n x: [0, -10, 10, -8, 6, -4, 0],\n }}\n transition={{\n duration: 0.5,\n ease: \"easeInOut\",\n times: [0, 0.15, 0.3, 0.45, 0.6, 0.75, 1],\n }}\n>\n Content\n</motion.div>"
}
]
}