边框描边
边框像被画笔描绘一样逐渐显现,从一角开始沿四边绘制完成。
交互预览
动效定义与代码
{
"slug": "border-trace",
"name": "边框描边",
"nameEn": "Border Trace",
"description": "边框像被画笔描绘一样逐渐显现,从一角开始沿四边绘制完成。",
"descriptionEn": "Border draws itself progressively like a pen tracing the outline from one corner.",
"category": "hover",
"tags": [
"border",
"draw",
"trace",
"outline",
"svg"
],
"trigger": "on-hover",
"difficulty": "advanced",
"duration": "600ms",
"easing": "ease-in-out",
"cssProperties": [
"stroke-dashoffset"
],
"isGPUAccelerated": false,
"previewBg": "light",
"keywords": [
"border",
"draw",
"trace",
"outline",
"stroke",
"pen"
],
"useCases": [
"Button hover effect",
"Card focus state",
"Navigation link highlight",
"Feature box reveal"
],
"relatedAnimations": [
"hover-glow",
"flip-card"
],
"recommendedStyles": [
"minimalist-flat",
"line-art",
"neo-brutalist"
],
"codeSnippets": [
{
"label": "CSS Keyframes",
"language": "css",
"code": "/* Approach 1: Gradient border with background-size animation */\n@keyframes border-trace {\n 0% {\n background-size: 0 2px, 2px 0, 0 2px, 2px 0;\n }\n 25% {\n background-size: 100% 2px, 2px 0, 0 2px, 2px 0;\n }\n 50% {\n background-size: 100% 2px, 2px 100%, 0 2px, 2px 0;\n }\n 75% {\n background-size: 100% 2px, 2px 100%, 100% 2px, 2px 0;\n }\n 100% {\n background-size: 100% 2px, 2px 100%, 100% 2px, 2px 100%;\n }\n}\n\n.border-trace {\n position: relative;\n background:\n linear-gradient(currentColor, currentColor) top left / 0 2px no-repeat,\n linear-gradient(currentColor, currentColor) top right / 2px 0 no-repeat,\n linear-gradient(currentColor, currentColor) bottom right / 0 2px no-repeat,\n linear-gradient(currentColor, currentColor) bottom left / 2px 0 no-repeat;\n transition: background-size 600ms ease-in-out;\n}\n\n.border-trace:hover {\n background-size: 100% 2px, 2px 100%, 100% 2px, 2px 100%;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .border-trace {\n transition: none;\n border: 2px solid currentColor;\n }\n .border-trace:hover {\n background: none;\n }\n}"
},
{
"label": "Tailwind CSS",
"language": "css",
"code": "/* Add to your global CSS or Tailwind v4 theme */\n\n/* Approach 2: SVG stroke-dashoffset for precise control */\n@utility animate-border-trace {\n & svg rect {\n stroke-dasharray: 1000;\n stroke-dashoffset: 1000;\n transition: stroke-dashoffset 600ms ease-in-out;\n }\n}\n\n/* Usage with SVG overlay:\n <div class=\"relative animate-border-trace group\">\n <svg class=\"absolute inset-0 w-full h-full pointer-events-none\">\n <rect\n x=\"1\" y=\"1\" rx=\"8\"\n width=\"calc(100% - 2px)\" height=\"calc(100% - 2px)\"\n fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"\n class=\"group-hover:[stroke-dashoffset:0]\"\n />\n </svg>\n Content\n </div>\n*/\n\n@media (prefers-reduced-motion: reduce) {\n .border-trace svg rect {\n transition: none;\n stroke-dashoffset: 0;\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// if (reduced) return <div style={{ border: \"2px solid\" }}>{children}</div>;\n\nfunction BorderTrace({ children }: { children: React.ReactNode }) {\n const pathVariants = {\n hidden: { pathLength: 0, opacity: 0 },\n visible: { pathLength: 1, opacity: 1 },\n };\n\n return (\n <div style={{ position: \"relative\", padding: \"1rem\" }}>\n <svg\n style={{ position: \"absolute\", inset: 0, width: \"100%\", height: \"100%\" }}\n >\n <motion.rect\n x=\"1\" y=\"1\" rx=\"8\"\n width=\"calc(100% - 2px)\"\n height=\"calc(100% - 2px)\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n variants={pathVariants}\n initial=\"hidden\"\n whileHover=\"visible\"\n transition={{ duration: 0.6, ease: \"easeInOut\" }}\n />\n </svg>\n {children}\n </div>\n );\n}"
}
]
}