figma-integration
تبدیل پروتوتایپهای Figma به انیمیشنهای production-ready و توکنهای موشن هماهنگ
این Skill جریان کاری تبدیل پروتوتایپهای Figma به کد انیمیشن را پوشش میدهد؛ از تحلیل transitionها و variantها تا نگاشت Smart Animate به CSS و Framer Motion.
همچنین به شما کمک میکند متغیرها و styleهای موشن را به توکنهای قابلاستفاده در کد تبدیل کنید، Motion Spec بسازید و handoff منظمتری با تیم طراحی داشته باشید.
چه چیزی میگیرید
- نگاشت Figma Variables به motion tokens برای duration و easing
- تحلیل prototype transitionها، triggerها و propertyهای متحرک
- تولید نمونه کد CSS و Framer Motion از تفاوت stateها
- تبدیل Figma variants و interactionها به stateهای قابلاستفاده در React
- چکلیست handoff، naming convention و ملاحظات reduced motion
برای چه کاری خوب است
- تیمهای محصولی که میخواهند انیمیشنهای Figma را به کد frontend منتقل کنند
- طراحان و توسعهدهندگانی که design token و motion system مشترک میسازند
- پروژههایی با component stateهای متعدد مانند hover، pressed و disabled
برای چه کاری نیست
- ساخت انیمیشنهای کاملاً مستقل از Figma یا طراحی موشن سینمایی پیچیده
- پروژههایی که به همگامسازی tokenها یا تحلیل prototype نیاز ندارند
دربارهٔ این اسکیل
Figma Integration
Workflows for translating Figma designs to production motion code.
Design Token Mapping
Figma Variables → Motion Tokens
| Figma Variable | Motion Token | Type |
|---|---|---|
duration/short | motion.duration.short-4 | Duration |
duration/medium | motion.duration.medium-2 | Duration |
easing/standard | motion.easing.standard | Easing |
easing/emphasized | motion.easing.emphasized-decelerate | Easing |
Export Variables to JSON
// Figma plugin code
function exportMotionVariables() {
const collection = figma.variables.getLocalVariableCollections()
.find(c => c.name === 'Motion');
const tokens = {};
collection.variableIds.forEach(id => {
const variable = figma.variables.getVariableById(id);
const value = variable.valuesByMode[Object.keys(variable.valuesByMode)[0]];
tokens[variable.name] = {
$value: value,
$type: getTokenType(variable.resolvedType),
$description: variable.description
};
});
return tokens;
}
Token Sync Script
// sync-figma-tokens.js
const FigmaAPI = require('figma-api');
async function syncMotionTokens(fileKey) {
const api = new FigmaAPI({ personalAccessToken: process.env.FIGMA_TOKEN });
const file = await api.getFile(fileKey);
const styles = file.styles;
// Extract motion-related styles
const motionTokens = Object.entries(styles)
.filter(([_, style]) => style.name.startsWith('motion/'))
.reduce((acc, [key, style]) => {
acc[style.name] = parseMotionStyle(style);
return acc;
}, {});
// Write to tokens file
fs.writeFileSync(
'./tokens/figma-motion.tokens.json',
JSON.stringify(motionTokens, null, 2)
);
}
Prototype Animation Analysis
Reading Prototype Transitions
// Figma plugin - analyze prototype connections
function analyzePrototypeAnimations() {
const page = figma.currentPage;
const flows = page.flowStartingPoints;
const animations = [];
// Walk through prototype connections
page.findAll(node => {
if (node.reactions) {
node.reactions.forEach(reaction => {
if (reaction.action?.type === 'NODE' && reaction.action.transition) {
animations.push({
trigger: reaction.trigger.type,
from: node.name,
to: reaction.action.destinationId,
transition: {
type: reaction.action.transition.type,
duration: reaction.action.transition.duration,
easing: mapFigmaEasing(reaction.action.transition.easing)
}
});
}
});
}
});
return animations;
}
function mapFigmaEasing(figmaEasing) {
const easingMap = {
'EASE_IN': 'ease-in',
'EASE_OUT': 'ease-out',
'EASE_IN_AND_OUT': 'ease-in-out',
'LINEAR': 'linear',
'CUSTOM_BEZIER': figmaEasing.easingFunctionCubicBezier
};
return easingMap[figmaEasing.type] || 'ease';
}
Generate Motion Spec
// Convert Figma prototype to motion spec
function generateMotionSpec(prototypeData) {
return {
$schema: 'motion-spec/1.0',
name: prototypeData.from,
trigger: prototypeData.trigger,
animation: {
type: prototypeData.transition.type,
duration: `${prototypeData.transition.duration}ms`,
easing: prototypeData.transition.easing,
properties: inferAnimatedProperties(prototypeData)
}
};
}
Smart Animate Translation
Figma Smart Animate → CSS
| Figma Property | CSS Property |
|---|---|
| X, Y | transform: translate() |
| Width, Height | width, height (avoid if possible) |
| Rotation | transform: rotate() |
| Opacity | opacity |
| Fill | background-color |
| Stroke | border-color |
| Corner Radius | border-radius |
Code Generation
// Generate CSS from Figma state diff
function generateCSSTransition(fromState, toState, transition) {
const changes = diffStates(fromState, toState);
const properties = changes.map(c => c.cssProperty);
return `
.element {
transition: ${properties.join(', ')} ${transition.duration}ms ${transition.easing};
}
.element.active {
${changes.map(c => `${c.cssProperty}: ${c.toValue};`).join('\n ')}
}
`;
}
// Generate Framer Motion
function generateFramerMotion(fromState, toState, transition) {
const changes = diffStates(fromState, toState);
return `
const variants = {
initial: {
${changes.map(c => `${c.framerProperty}: ${c.fromValue}`).join(',\n ')}
},
animate: {
${changes.map(c => `${c.framerProperty}: ${c.toValue}`).join(',\n ')}
}
};
<motion.div
variants={variants}
initial="initial"
animate="animate"
transition={{
duration: ${transition.duration / 1000},
ease: ${JSON.stringify(transition.easing)}
}}
/>
`;
}
Interactive Component States
Mapping Figma Variants to Code
// Figma component variants
const figmaVariants = {
'State=Default': { scale: 1, opacity: 1 },
'State=Hover': { scale: 1.02, opacity: 1 },
'State=Pressed': { scale: 0.98, opacity: 0.9 },
'State=Disabled': { scale: 1, opacity: 0.5 }
};
// Generated Framer Motion variants
const motionVariants = {
default: { scale: 1, opacity: 1 },
hover: { scale: 1.02, opacity: 1 },
pressed: { scale: 0.98, opacity: 0.9 },
disabled: { scale: 1, opacity: 0.5 }
};
// With transitions
const transitions = {
default: { type: 'spring', damping: 20, stiffness: 300 },
hover: { duration: 0.2 },
pressed: { duration: 0.1 },
disabled: { duration: 0.3 }
};
Interaction Triggers
// Map Figma triggers to React events
const triggerMap = {
'ON_CLICK': 'onClick',
'ON_HOVER': 'onHoverStart',
'MOUSE_ENTER': 'onMouseEnter',
'MOUSE_LEAVE': 'onMouseLeave',
'ON_PRESS': 'onTapStart',
'ON_DRAG': 'onDrag'
};
Plugin Workflow
Design Handoff Plugin
// Figma plugin UI
figma.showUI(__html__, { width: 400, height: 500 });
figma.ui.onmessage = async (msg) => {
if (msg.type === 'export-motion') {
const selection = figma.currentPage.selection;
const motionData = selection.map(node => ({
name: node.name,
states: extractStates(node),
transitions: extractTransitions(node),
tokens: extractUsedTokens(node)
}));
figma.ui.postMessage({
type: 'motion-data',
data: motionData
});
}
};
Lottie Export
// Export Figma animation to Lottie
// Requires bodymovin-like plugin or LottieFiles integration
async function exportToLottie(frameId) {
// Use Figma's exportAsync for frames
const frame = figma.getNodeById(frameId);
// For complex animations, use LottieFiles plugin
// or manually construct Lottie JSON from Figma data
const lottieData = {
v: '5.7.4',
fr: 60,
ip: 0,
op: 60,
w: frame.width,
h: frame.height,
layers: convertFigmaLayersToLottie(frame.children)
};
return lottieData;
}
Best Practices
Design System Setup
- Create Motion Variable Collection in Figma
- Define duration variables: short, medium, long
- Define easing presets: standard, emphasized, accelerate
- Document in component library
Handoff Checklist
- ☐ All transitions use motion variables
- ☐ States are clearly named (Default, Hover, Active, etc.)
- ☐ Prototype connections documented
- ☐ Reduced motion alternatives specified
- ☐ Motion spec exported
Naming Conventions
motion/duration/short-1
motion/duration/short-2
motion/duration/medium-1
motion/easing/standard
motion/easing/emphasized-decelerate
motion/spring/fast
motion/spring/default
Related Skills
- Dependencies: easing, duration, motion-specs
- See also: code-generation, storyboarding, tools
- Integrations: Figma, FigJam, Figma Tokens