morphing-icons
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMorphing Icons
可变形图标
Build icons that transform through actual shape transformation, not crossfades. Any icon can morph into any other because they share the same underlying 3-line structure.
构建通过实际形状变换而非淡入淡出实现过渡的图标。由于所有图标共享相同的底层三线结构,任意图标都可以变形为其他任意图标。
Core Concept
核心概念
Every icon is composed of exactly three SVG lines. Icons that need fewer lines collapse the extras to invisible center points. This constraint enables seamless morphing between any two icons.
每个图标都由恰好三条SVG线条组成。对于不需要三条线的图标,将多余的线条收缩为不可见的中心点。这一约束条件使得任意两个图标之间都能实现无缝变形。
Architecture
架构设计
1. Line Definition
1. 线条定义
Each line has coordinates and optional opacity:
ts
interface IconLine {
x1: number;
y1: number;
x2: number;
y2: number;
opacity?: number;
}每条线包含坐标和可选的透明度:
ts
interface IconLine {
x1: number;
y1: number;
x2: number;
y2: number;
opacity?: number;
}2. Collapsed Lines
2. 收缩线条
Icons needing fewer than 3 lines use collapsed lines—zero-length lines at the center:
ts
const CENTER = 7; // Center of 14x14 viewbox
const collapsed: IconLine = {
x1: CENTER,
y1: CENTER,
x2: CENTER,
y2: CENTER,
opacity: 0,
};对于不需要三条线的图标,使用收缩线条——即位于中心的零长度线条:
ts
const CENTER = 7; // 14x14视口的中心点
const collapsed: IconLine = {
x1: CENTER,
y1: CENTER,
x2: CENTER,
y2: CENTER,
opacity: 0,
};3. Icon Definition
3. 图标定义
Each icon has exactly 3 lines, optional rotation, and optional group:
ts
interface IconDefinition {
lines: [IconLine, IconLine, IconLine];
rotation?: number;
group?: string;
}每个图标包含恰好三条线条、可选的旋转角度以及可选的分组:
ts
interface IconDefinition {
lines: [IconLine, IconLine, IconLine];
rotation?: number;
group?: string;
}4. Rotation Groups
4. 旋转分组
Icons sharing a animate rotation when transitioning between them. Icons without matching groups jump to the new rotation instantly:
groupts
// These rotate smoothly between each other
{ lines: plusLines, rotation: 0, group: "plus-cross" } // plus
{ lines: plusLines, rotation: 45, group: "plus-cross" } // cross
// These rotate smoothly between each other
{ lines: arrowLines, rotation: 0, group: "arrow" } // arrow-right
{ lines: arrowLines, rotation: 90, group: "arrow" } // arrow-down
{ lines: arrowLines, rotation: 180, group: "arrow" } // arrow-left
{ lines: arrowLines, rotation: -90, group: "arrow" } // arrow-up属于同一的图标在过渡时会带动画旋转。分组不匹配的图标则会直接跳转到新的旋转角度:
groupts
// 以下图标之间可平滑旋转过渡
{ lines: plusLines, rotation: 0, group: "plus-cross" } // 加号
{ lines: plusLines, rotation: 45, group: "plus-cross" } // 叉号
// 以下图标之间可平滑旋转过渡
{ lines: arrowLines, rotation: 0, group: "arrow" } // 右箭头
{ lines: arrowLines, rotation: 90, group: "arrow" } // 下箭头
{ lines: arrowLines, rotation: 180, group: "arrow" } // 左箭头
{ lines: arrowLines, rotation: -90, group: "arrow" } // 上箭头Implementation Rules
实现规则
morphing-three-lines
morphing-three-linesmorphing-three-lines
morphing-three-linesEvery icon MUST use exactly 3 lines. No more, no fewer.
Fail:
ts
const checkIcon = {
lines: [
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
], // Only 2 lines
};Pass:
ts
const checkIcon = {
lines: [
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
collapsed, // Third line collapsed
],
};每个图标必须恰好使用三条线条,不能多也不能少。
错误示例:
ts
const checkIcon = {
lines: [
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
], // 仅使用了2条线
};正确示例:
ts
const checkIcon = {
lines: [
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
collapsed, // 第三条线为收缩线条
],
};morphing-use-collapsed
morphing-use-collapsedmorphing-use-collapsed
morphing-use-collapsedUnused lines must use the collapsed constant, not omission or null.
Fail:
ts
const minusIcon = {
lines: [
{ x1: 2, y1: 7, x2: 12, y2: 7 },
null,
null,
],
};Pass:
ts
const minusIcon = {
lines: [
{ x1: 2, y1: 7, x2: 12, y2: 7 },
collapsed,
collapsed,
],
};未使用的线条必须使用预定义的收缩线条常量,不能省略或设为null。
错误示例:
ts
const minusIcon = {
lines: [
{ x1: 2, y1: 7, x2: 12, y2: 7 },
null,
null,
],
};正确示例:
ts
const minusIcon = {
lines: [
{ x1: 2, y1: 7, x2: 12, y2: 7 },
collapsed,
collapsed,
],
};morphing-consistent-viewbox
morphing-consistent-viewboxmorphing-consistent-viewbox
morphing-consistent-viewboxAll icons must use the same viewBox (14x14 recommended).
Fail:
ts
// Mixing viewbox scales
const icon1 = { lines: [{ x1: 2, y1: 7, x2: 12, y2: 7 }, ...] }; // 14x14
const icon2 = { lines: [{ x1: 4, y1: 14, x2: 24, y2: 14 }, ...] }; // 28x28Pass:
ts
const VIEWBOX_SIZE = 14;
const CENTER = 7;
// All coordinates within 0-14 range所有图标必须使用相同的viewBox(推荐使用14x14)。
错误示例:
ts
// 混合使用不同大小的视口
const icon1 = { lines: [{ x1: 2, y1: 7, x2: 12, y2: 7 }, ...] }; // 14x14
const icon2 = { lines: [{ x1: 4, y1: 14, x2: 24, y2: 14 }, ...] }; // 28x28正确示例:
ts
const VIEWBOX_SIZE = 14;
const CENTER = 7;
// 所有坐标都在0-14范围内morphing-group-variants
morphing-group-variantsmorphing-group-variants
morphing-group-variantsIcons that are rotational variants MUST share the same group and base lines.
Fail:
ts
// Different line definitions for arrows
const arrowRight = { lines: [{ x1: 2, y1: 7, x2: 12, y2: 7 }, ...] };
const arrowDown = { lines: [{ x1: 7, y1: 2, x2: 7, y2: 12 }, ...] }; // Different!Pass:
ts
const arrowLines: [IconLine, IconLine, IconLine] = [
{ x1: 2, y1: 7, x2: 12, y2: 7 },
{ x1: 7.5, y1: 2.5, x2: 12, y2: 7 },
{ x1: 7.5, y1: 11.5, x2: 12, y2: 7 },
];
const icons = {
"arrow-right": { lines: arrowLines, rotation: 0, group: "arrow" },
"arrow-down": { lines: arrowLines, rotation: 90, group: "arrow" },
"arrow-left": { lines: arrowLines, rotation: 180, group: "arrow" },
"arrow-up": { lines: arrowLines, rotation: -90, group: "arrow" },
};属于旋转变体的图标必须共享相同的分组和基础线条定义。
错误示例:
ts
// 箭头图标使用不同的线条定义
const arrowRight = { lines: [{ x1: 2, y1: 7, x2: 12, y2: 7 }, ...] };
const arrowDown = { lines: [{ x1: 7, y1: 2, x2: 7, y2: 12 }, ...] }; // 线条定义不同!正确示例:
ts
const arrowLines: [IconLine, IconLine, IconLine] = [
{ x1: 2, y1: 7, x2: 12, y2: 7 },
{ x1: 7.5, y1: 2.5, x2: 12, y2: 7 },
{ x1: 7.5, y1: 11.5, x2: 12, y2: 7 },
];
const icons = {
"arrow-right": { lines: arrowLines, rotation: 0, group: "arrow" },
"arrow-down": { lines: arrowLines, rotation: 90, group: "arrow" },
"arrow-left": { lines: arrowLines, rotation: 180, group: "arrow" },
"arrow-up": { lines: arrowLines, rotation: -90, group: "arrow" },
};morphing-spring-rotation
morphing-spring-rotationmorphing-spring-rotation
morphing-spring-rotationRotation between grouped icons should use spring physics for natural motion.
Fail:
tsx
<motion.g animate={{ rotate: rotation }} transition={{ duration: 0.3 }} />Pass:
tsx
const rotation = useSpring(definition.rotation ?? 0, activeTransition);
<motion.g style={{ rotate: rotation, transformOrigin: "center" }} />同组图标之间的旋转过渡应使用弹簧物理动画,以实现自然的运动效果。
错误示例:
tsx
<motion.g animate={{ rotate: rotation }} transition={{ duration: 0.3 }} />正确示例:
tsx
const rotation = useSpring(definition.rotation ?? 0, activeTransition);
<motion.g style={{ rotate: rotation, transformOrigin: "center" }} />morphing-reduced-motion
morphing-reduced-motionmorphing-reduced-motion
morphing-reduced-motionRespect by disabling animations.
prefers-reduced-motionFail:
tsx
function MorphingIcon({ icon }: Props) {
return <motion.line animate={...} transition={{ duration: 0.4 }} />;
}Pass:
tsx
function MorphingIcon({ icon }: Props) {
const reducedMotion = useReducedMotion() ?? false;
const activeTransition = reducedMotion ? { duration: 0 } : transition;
return <motion.line animate={...} transition={activeTransition} />;
}应尊重设置,在用户偏好减少动画时禁用动画。
prefers-reduced-motion错误示例:
tsx
function MorphingIcon({ icon }: Props) {
return <motion.line animate={...} transition={{ duration: 0.4 }} />;
}正确示例:
tsx
function MorphingIcon({ icon }: Props) {
const reducedMotion = useReducedMotion() ?? false;
const activeTransition = reducedMotion ? { duration: 0 } : transition;
return <motion.line animate={...} transition={activeTransition} />;
}morphing-jump-non-grouped
morphing-jump-non-groupedmorphing-jump-non-grouped
morphing-jump-non-groupedWhen transitioning between icons NOT in the same group, rotation should jump instantly.
Fail:
tsx
// Always animating rotation regardless of group
useEffect(() => {
rotation.set(definition.rotation ?? 0);
}, [definition]);Pass:
tsx
useEffect(() => {
if (shouldRotate) {
rotation.set(definition.rotation ?? 0); // Animate
} else {
rotation.jump(definition.rotation ?? 0); // Instant
}
}, [definition, shouldRotate]);在不同分组的图标之间过渡时,旋转角度应直接跳转,不带动画。
错误示例:
tsx
// 无论分组是否匹配,始终带动画旋转
useEffect(() => {
rotation.set(definition.rotation ?? 0);
}, [definition]);正确示例:
tsx
useEffect(() => {
if (shouldRotate) {
rotation.set(definition.rotation ?? 0); // 带动画
} else {
rotation.jump(definition.rotation ?? 0); // 立即跳转
}
}, [definition, shouldRotate]);morphing-strokelinecap-round
morphing-strokelinecap-roundmorphing-strokelinecap-round
morphing-strokelinecap-roundLines should use for polished endpoints.
strokeLinecap="round"Fail:
tsx
<motion.line strokeLinecap="butt" />Pass:
tsx
<motion.line strokeLinecap="round" />线条应设置,以实现圆润的端点效果。
strokeLinecap="round"错误示例:
tsx
<motion.line strokeLinecap="butt" />正确示例:
tsx
<motion.line strokeLinecap="round" />morphing-aria-hidden
morphing-aria-hiddenmorphing-aria-hidden
morphing-aria-hiddenIcon SVGs should be since they're decorative.
aria-hiddenFail:
tsx
<svg width={size} height={size}>...</svg>Pass:
tsx
<svg width={size} height={size} aria-hidden="true">...</svg>由于图标仅起装饰作用,SVG图标应设置属性。
aria-hidden错误示例:
tsx
<svg width={size} height={size}>...</svg>正确示例:
tsx
<svg width={size} height={size} aria-hidden="true">...</svg>Common Icon Patterns
常见图标模式
Two-Line Icons (check, minus, equals, chevron)
两线图标(对勾、减号、等号、Chevron)
Use one or two collapsed lines:
ts
const check = {
lines: [
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
collapsed,
],
};使用一条或两条收缩线条:
ts
const check = {
lines: [
{ x1: 2, y1: 7.5, x2: 5.5, y2: 11 },
{ x1: 5.5, y1: 11, x2: 12, y2: 3 },
collapsed,
],
};Three-Line Icons (menu, asterisk, play)
三线图标(菜单、星号、播放)
Use all three lines:
ts
const menu = {
lines: [
{ x1: 2, y1: 3.5, x2: 12, y2: 3.5 },
{ x1: 2, y1: 7, x2: 12, y2: 7 },
{ x1: 2, y1: 10.5, x2: 12, y2: 10.5 },
],
};使用全部三条线条:
ts
const menu = {
lines: [
{ x1: 2, y1: 3.5, x2: 12, y2: 3.5 },
{ x1: 2, y1: 7, x2: 12, y2: 7 },
{ x1: 2, y1: 10.5, x2: 12, y2: 10.5 },
],
};Point Icons (more, grip)
点状图标(更多、 grip)
Use zero-length lines as dots:
ts
const more = {
lines: [
{ x1: 3, y1: 7, x2: 3, y2: 7 },
{ x1: 7, y1: 7, x2: 7, y2: 7 },
{ x1: 11, y1: 7, x2: 11, y2: 7 },
],
};使用零长度线条作为圆点:
ts
const more = {
lines: [
{ x1: 3, y1: 7, x2: 3, y2: 7 },
{ x1: 7, y1: 7, x2: 7, y2: 7 },
{ x1: 11, y1: 7, x2: 11, y2: 7 },
],
};Recommended Transition
推荐过渡效果
Use exponential ease-out for smooth morphing:
ts
const defaultTransition: Transition = {
ease: [0.19, 1, 0.22, 1],
duration: 0.4,
};使用指数缓出曲线实现平滑的变形效果:
ts
const defaultTransition: Transition = {
ease: [0.19, 1, 0.22, 1],
duration: 0.4,
};Output Format
输出格式
When auditing morphing icon implementations, output findings as:
file:line - [rule-id] description of issue
Example:
components/icon/index.tsx:45 - [morphing-three-lines] Icon "check" has only 2 lines, needs collapsed third
components/icon/index.tsx:78 - [morphing-group-variants] arrow-down uses different line definitions than arrow-right审核可变形图标实现时,应按以下格式输出问题:
file:line - [rule-id] 问题描述
示例:
components/icon/index.tsx:45 - [morphing-three-lines] 图标“check”仅使用了2条线,需添加第三条收缩线条
components/icon/index.tsx:78 - [morphing-group-variants] arrow-down使用的线条定义与arrow-right不同Summary Table
汇总表格
After findings, output a summary:
| Rule | Count | Severity |
|---|---|---|
| 2 | HIGH |
| 1 | HIGH |
| 1 | MEDIUM |
输出问题后,应附带汇总表格:
| 规则 | 数量 | 严重程度 |
|---|---|---|
| 2 | 高 |
| 1 | 高 |
| 1 | 中 |