mcpField
F
figma-design

figma-design

github.com/manutej设计与 UI62542 次安装40 周前更新
代码仓库

关于 figma-design

figma-design 是面向 Figma 设计和插件开发的技能指南,依据 Figma Plugin API 文档介绍设计工作流。内容涉及组件系统、自动布局、原型、数据管理和资源导出。

主要功能

  1. 01Figma 插件开发
  2. 02组件与设计系统管理
  3. 03自动布局和约束
  4. 04交互原型配置
  5. 05节点搜索与批量操作
  6. 06数据存储和资源导出

适用场景

  1. 01开发 Figma 插件和自动化工具
  2. 02搭建设计系统与可复用组件
  3. 03创建交互原型和流程
  4. 04批量处理节点并导出资源

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/manutej/luxor-claude-marketplace --skill figma-design

安装后在 Claude Code 中输入/figma-design确认已加载。

附带文件EXAMPLES.mdREADME.md

详细介绍

Figma 设计技能

基于 Context7 提供的官方 Figma Plugin API 文档,全面介绍 Figma 设计工作流、插件开发、组件系统、自动布局、原型制作和设计系统管理。

何时使用此技能

在以下场景中使用此技能:

  • Figma 插件开发:构建自定义插件、UI 扩展和自动化工具
  • 设计系统:创建和管理变量、样式、组件和库
  • 组件架构:构建可复用组件、变体和实例
  • 自动布局:通过约束和间距实现响应式画框
  • 原型制作:通过反应和流程创建交互式原型
  • 批量操作:跨多个节点自动执行重复的设计任务
  • 导出与集成:以各种格式(PNG、JPG、SVG)导出资源
  • 数据管理:在节点上存储和检索插件数据或共享数据
  • 协作:管理版本历史记录、评论和团队工作流

核心概念

1. Figma 节点层级

Figma 文档结构是一棵由节点组成的树:

DocumentNode (root)
└── PageNode
    ├── FrameNode
    │   ├── TextNode
    │   ├── RectangleNode
    │   └── ComponentNode
    └── SectionNode
        └── FrameNode

主要节点类型:

  • FRAME:具有自动布局功能的容器
  • COMPONENT:可复用的设计元素(主组件)
  • INSTANCE:组件的副本
  • TEXT:可编辑的文本图层
  • RECTANGLE、ELLIPSE、POLYGON、STAR、LINE:基本形状
  • SECTION:用于组织画框的容器
  • GROUP:不参与布局的容器
2. 组件和实例

组件是可多次实例化的主元素:

// Create a component
const button = figma.createComponent()
button.name = "Primary Button"
button.resize(120, 40)

// Add visual elements
const bg = figma.createRectangle()
bg.resize(120, 40)
bg.cornerRadius = 8
bg.fills = [{ type: 'SOLID', color: { r: 0.2, g: 0.5, b: 1 } }]
button.appendChild(bg)

// Create instance
const buttonInstance = button.createInstance()
buttonInstance.x = 200
buttonInstance.y = 100

组件属性:

  • BOOLEAN:切换可见性或状态
  • TEXT:可自定义的文本内容
  • INSTANCE_SWAP:替换嵌套组件
  • VARIANT:组件的不同变体
3. 自动布局

自动布局可创建响应式画框,使其随内容变化而调整:

核心属性:

  • layoutMode:'HORIZONTAL'、'VERTICAL' 或 'NONE'
  • primaryAxisSizingMode:'FIXED' 或 'AUTO'
  • counterAxisSizingMode:'FIXED' 或 'AUTO'
  • paddingLeft、paddingRight、paddingTop、paddingBottom
  • itemSpacing:子元素之间的间距
  • primaryAxisAlignItems:主轴上的对齐方式
  • counterAxisAlignItems:交叉轴上的对齐方式

子元素约束:

  • minWidth、maxWidth:宽度边界
  • minHeight、maxHeight:高度边界
  • layoutAlign:'MIN'、'CENTER'、'MAX'、'STRETCH'
  • layoutGrow:0(固定)或 1(填充容器)
// Create auto-layout frame
const frame = figma.createFrame()
frame.layoutMode = 'VERTICAL'
frame.primaryAxisSizingMode = 'AUTO'
frame.counterAxisSizingMode = 'FIXED'
frame.resize(300, 0) // Width fixed, height auto
frame.itemSpacing = 16
frame.paddingLeft = 24
frame.paddingRight = 24
frame.paddingTop = 24
frame.paddingBottom = 24

// Add children with constraints
const child = figma.createRectangle()
child.resize(252, 100)
child.layoutAlign = 'STRETCH' // Fill width
child.minHeight = 100
child.maxHeight = 200
frame.appendChild(child)
4. 约束

约束用于控制父节点变化时节点的调整方式:

interface Constraints {
  horizontal: 'MIN' | 'CENTER' | 'MAX' | 'STRETCH' | 'SCALE'
  vertical: 'MIN' | 'CENTER' | 'MAX' | 'STRETCH' | 'SCALE'
}

node.constraints = {
  horizontal: 'MIN',    // Pin to left
  vertical: 'MAX'       // Pin to bottom
}

约束类型:

  • MIN:固定到顶部/左侧边缘
  • CENTER:在父节点中居中
  • MAX:固定到底部/右侧边缘
  • STRETCH:随父节点缩放(两侧边缘)
  • SCALE:保持位置和尺寸的比例
5. 变量和设计令牌

变量可构建动态设计系统(仅限 Figma Design):

// Create variable collection
const collection = figma.variables.createVariableCollection('Design Tokens')

// Create color variable
const primaryColor = figma.variables.createVariable(
  'color/primary',
  collection,
  'COLOR'
)

// Set value for default mode
const defaultMode = collection.modes[0]
primaryColor.setValueForMode(defaultMode.modeId, {
  r: 0.2, g: 0.5, b: 1, a: 1
})

// Add dark mode
const darkMode = collection.addMode('Dark')
primaryColor.setValueForMode(darkMode, {
  r: 0.4, g: 0.7, b: 1, a: 1
})

// Create variable alias (reference)
const accentColor = figma.variables.createVariable(
  'color/accent',
  collection,
  'COLOR'
)
const alias = figma.variables.createVariableAlias(primaryColor)
accentColor.setValueForMode(defaultMode.modeId, alias)

// Bind variable to node
const rect = figma.createRectangle()
const fill = { type: 'SOLID', color: { r: 0, g: 0, b: 0 } } as SolidPaint
const boundFill = figma.variables.setBoundVariableForPaint(
  fill,
  'color',
  primaryColor
)
rect.fills = [boundFill]

变量类型:

  • COLOR:RGB/RGBA 值
  • FLOAT:数值(间距、尺寸)
  • BOOLEAN:真/假标记
  • STRING:文本值
6. 样式

样式用于定义可复用的视觉属性:

// Paint style (fills/strokes)
const paintStyle = figma.createPaintStyle()
paintStyle.name = 'Brand/Primary'
paintStyle.paints = [{
  type: 'SOLID',
  color: { r: 0.2, g: 0.5, b: 1 }
}]

// Text style
const textStyle = figma.createTextStyle()
textStyle.name = 'Heading/H1'
textStyle.fontSize = 32
textStyle.fontName = { family: 'Inter', style: 'Bold' }
textStyle.lineHeight = { value: 120, unit: 'PERCENT' }
textStyle.letterSpacing = { value: -0.5, unit: 'PIXELS' }

// Effect style (shadows, blurs)
const effectStyle = figma.createEffectStyle()
effectStyle.name = 'Shadow/Card'
effectStyle.effects = [{
  type: 'DROP_SHADOW',
  color: { r: 0, g: 0, b: 0, a: 0.15 },
  offset: { x: 0, y: 4 },
  radius: 8,
  visible: true,
  blendMode: 'NORMAL'
}]

// Apply styles
rect.fillStyleId = paintStyle.id
text.textStyleId = textStyle.id
frame.effectStyleId = effectStyle.id

原型制作

1. 反应和交互

反应用于定义原型中的交互行为:

// Set reactions on a node
await node.setReactionsAsync([
  {
    action: {
      type: 'NODE',
      destinationId: 'nodeId', // Target frame
      navigation: 'NAVIGATE',
      transition: {
        type: 'SMART_ANIMATE',
        easing: { type: 'EASE_IN_AND_OUT' },
        duration: 0.3
      },
      preserveScrollPosition: false
    },
    trigger: {
      type: 'ON_CLICK'
    }
  }
])

触发类型:

  • ON_CLICK:点击/轻触
  • ON_HOVER:鼠标悬停
  • ON_PRESS:触摸按下
  • ON_DRAG:拖动交互
  • MOUSE_ENTER、MOUSE_LEAVE、MOUSE_UP、MOUSE_DOWN
  • AFTER_TIMEOUT:延迟触发

导航类型:

  • NAVIGATE:跳转到目标
  • SWAP:替换叠加层
  • OVERLAY:以叠加层形式打开
  • SCROLL_TO:滚动到指定位置
  • CHANGE_TO:切换到指定状态
2. 叠加层配置

控制画框以叠加层形式显示的方式:

frame.overlayPositionType // 'CENTER' | 'TOP_LEFT' | 'TOP_CENTER' | etc.
frame.overlayBackground // How overlay obscures background
frame.overlayBackgroundInteraction // Click-through behavior
3. 滚动画框

配置画框的滚动行为:

frame.overflowDirection = 'VERTICAL_SCROLLING' // or 'HORIZONTAL_SCROLLING'
frame.numberOfFixedChildren = 1 // First N children stay fixed during scroll

插件开发

1. 插件结构

基本插件文件:

my-plugin/
├── manifest.json       # Plugin configuration
├── code.ts            # Main plugin logic
└── ui.html            # Optional UI

manifest.json:

{
  "name": "My Plugin",
  "id": "unique-plugin-id",
  "api": "1.0.0",
  "main": "code.js",
  "ui": "ui.html",
  "editorType": ["figma", "figjam"],
  "documentAccess": "dynamic-page",
  "networkAccess": {
    "allowedDomains": ["api.example.com"]
  }
}
2. 插件生命周期
// Initialize plugin
async function init() {
  // Show UI (optional)
  figma.showUI(__html__, {
    width: 400,
    height: 500,
    title: "My Plugin",
    themeColors: true
  })

  // Load saved preferences
  const prefs = await figma.clientStorage.getAsync('preferences')

  // Send initial data to UI
  figma.ui.postMessage({
    type: 'init',
    data: prefs
  })
}

// Handle UI messages
figma.ui.onmessage = async (msg) => {
  if (msg.type === 'create-shapes') {
    await createShapes(msg.count, msg.color)
  }

  if (msg.type === 'export') {
    await exportSelection()
  }
}

// Clean up on close
figma.on('close', () => {
  console.log('Plugin closing...')
})

// Start plugin
init()
3. UI 通信

从插件向 UI 发送消息:

figma.ui.postMessage({
  type: 'selection-changed',
  count: figma.currentPage.selection.length,
  nodes: figma.currentPage.selection.map(n => ({
    id: n.id,
    name: n.name,
    type: n.type
  }))
})

在 UI(ui.html)中接收消息:

<script>
  window.onmessage = (event) => {
    const msg = event.data.pluginMessage

    if (msg.type === 'selection-changed') {
      document.getElementById('count').textContent = msg.count
    }
  }

  // Send message to plugin
  function createShapes() {
    parent.postMessage({
      pluginMessage: {
        type: 'create-shapes',
        count: 5,
        color: { r: 1, g: 0, b: 0 }
      }
    }, '*')
  }
</script>
4. 事件监听器

监控文档和选区的更改:

// Selection changes
figma.on('selectionchange', () => {
  const selection = figma.currentPage.selection
  console.log(`Selected ${selection.length} nodes`)
})

// Document changes
figma.on('documentchange', (event) => {
  for (const change of event.documentChanges) {
    if (change.type === 'CREATE') {
      console.log(`Created: ${change.id}`)
    }

    if (change.type === 'DELETE') {
      console.log(`Deleted: ${change.id}`)
    }

    if (change.type === 'PROPERTY_CHANGE') {
      console.log(`Changed properties: ${change.properties.join(', ')}`)
    }
  }
})

// Page changes
figma.on('currentpagechange', () => {
  console.log(`Now on page: ${figma.currentPage.name}`)
})
5. 数据存储

插件数据(仅供你的插件使用):

// Store data on node
node.setPluginData('status', 'approved')
node.setPluginData('metadata', JSON.stringify({
  author: 'John',
  tags: ['important']
}))

// Retrieve data
const status = node.getPluginData('status')
const metadata = JSON.parse(node.getPluginData('metadata') || '{}')

// List all keys
const keys = node.getPluginDataKeys()

共享插件数据(可通过命名空间访问):

// Store shared data
node.setSharedPluginData('com.example.plugin', 'version', '2.0')

// Retrieve shared data
const version = node.getSharedPluginData('com.example.plugin', 'version')

// List shared keys
const keys = node.getSharedPluginDataKeys('com.example.plugin')

客户端存储(持久化设置):

// Save preferences
await figma.clientStorage.setAsync('preferences', {
  theme: 'dark',
  lastUsed: new Date().toISOString()
})

// Load preferences
const prefs = await figma.clientStorage.getAsync('preferences')

// Delete data
await figma.clientStorage.deleteAsync('preferences')

// List all keys
const keys = await figma.clientStorage.keysAsync()

查找节点

1. 使用 findAllWithCriteria 优化搜索

大型文档中速度最快的方法(快数百倍):

// Enable performance optimization
figma.skipInvisibleInstanceChildren = true

// Find by type
const textNodes = figma.currentPage.findAllWithCriteria({
  types: ['TEXT']
})

// Find multiple types
const shapes = figma.currentPage.findAllWithCriteria({
  types: ['RECTANGLE', 'ELLIPSE', 'POLYGON']
})

// Find nodes with plugin data
const nodesWithStatus = figma.currentPage.findAllWithCriteria({
  pluginData: {
    keys: ['status']
  }
})

// Find nodes with shared plugin data
const nodesWithSharedData = figma.currentPage.findAllWithCriteria({
  sharedPluginData: {
    namespace: 'com.example.plugin',
    keys: ['version']
  }
})

// Combine criteria
const textWithData = figma.currentPage.findAllWithCriteria({
  types: ['TEXT'],
  pluginData: {} // Any plugin data
})
2. 传统搜索方法
// Find all matching nodes
const frames = figma.currentPage.findAll(node => node.type === 'FRAME')

// Find first match
const template = figma.currentPage.findOne(node =>
  node.name.startsWith('Template')
)

// Find by ID
const node = await figma.getNodeByIdAsync('123:456')

// Recursive tree traversal
function walkTree(node: BaseNode) {
  console.log(`${node.type}: ${node.name}`)

  if ('children' in node) {
    for (const child of node.children) {
      walkTree(child)
    }
  }
}

walkTree(figma.currentPage)

导出与图像处理

1. 导出节点
// Export as PNG
const pngBytes = await node.exportAsync({
  format: 'PNG',
  constraint: { type: 'SCALE', value: 2 } // 2x resolution
})

// Export as JPG
const jpgBytes = await node.exportAsync({
  format: 'JPG',
  constraint: { type: 'SCALE', value: 1 },
  contentsOnly: false // Include background
})

// Export as SVG
const svgBytes = await node.exportAsync({
  format: 'SVG',
  svgIdAttribute: true,
  svgOutlineText: false,
  svgSimplifyStroke: true
})

// Export with fixed dimensions
const thumbnail = await node.exportAsync({
  format: 'PNG',
  constraint: { type: 'WIDTH', value: 200 }
})

// Export with height constraint
const preview = await node.exportAsync({
  format: 'PNG',
  constraint: { type: 'HEIGHT', value: 400 }
})
2. 加载图像
// Load from URL
const image = await figma.createImageAsync('https://example.com/image.png')
const { width, height } = await image.getSizeAsync()

// Create rectangle with image
const rect = figma.createRectangle()
rect.resize(width, height)
rect.fills = [{
  type: 'IMAGE',
  imageHash: image.hash,
  scaleMode: 'FILL' // or 'FIT', 'CROP', 'TILE'
}]

// Load from bytes
function loadFromBytes(bytes: Uint8Array) {
  const image = figma.createImage(bytes)
  return image
}

// Create from SVG string
const svgString = `
  <svg width="100" height="100">
    <circle cx="50" cy="50" r="40" fill="#3366ff"/>
  </svg>
`
const node = figma.createNodeFromSvg(svgString)

文本处理

1. 文本基础
// Create text (MUST load font first)
const text = figma.createText()
await figma.loadFontAsync(text.fontName) // Load default font

// Set content
text.characters = 'Hello World'

// Change font
await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })
text.fontName = { family: 'Inter', style: 'Bold' }

// Styling
text.fontSize = 24
text.lineHeight = { value: 150, unit: 'PERCENT' }
text.letterSpacing = { value: 0, unit: 'PIXELS' }
text.textAlignHorizontal = 'CENTER'
text.textAlignVertical = 'CENTER'
text.textCase = 'UPPER' // or 'LOWER', 'TITLE', 'ORIGINAL'
text.textDecoration = 'UNDERLINE' // or 'STRIKETHROUGH', 'NONE'
2. 富文本格式
// Apply formatting to range
text.setRangeFontSize(0, 5, 32) // First 5 chars = 32px
text.setRangeFills(0, 5, [{
  type: 'SOLID',
  color: { r: 1, g: 0, b: 0 }
}])

// Get font at position
const fontName = text.getRangeFontName(0, 1)
const fontSize = text.getRangeFontSize(0, 1)

调整大小与变换

1. 调整大小的方法
// Resize with constraints applied
node.resize(300, 200)

// Resize without constraints
node.resizeWithoutConstraints(300, 200)

// Scale proportionally
node.rescale(1.5) // 150% scale
2. 变换
// Position
node.x = 100
node.y = 200

// Relative to parent
console.log(node.relativeTransform) // [[1,0,x], [0,1,y]]

// Absolute position on page
console.log(node.absoluteTransform)

// Bounding box
const bounds = node.absoluteBoundingBox
// { x: number, y: number, width: number, height: number }

协作功能

1. 评论

虽然插件无法通过 API 创建评论,但可以:

  • 跳转到含有评论的画框
  • 通过 REST API 读取评论元数据
  • 触发通知
2. 版本历史
// Plugin data can track custom versioning
node.setPluginData('version', '2.1.0')
node.setPluginData('lastModified', new Date().toISOString())
node.setPluginData('modifiedBy', '[email protected]')
3. 团队资源库
// Check if node is from external library
if (node.type === 'INSTANCE') {
  const component = node.mainComponent
  if (component && component.parent?.type === 'PAGE') {
    // Component is from library
  }
}

Context7 最佳实践

1. 性能优化
// ✅ Use findAllWithCriteria for large documents
const nodes = figma.currentPage.findAllWithCriteria({
  types: ['TEXT']
})

// ❌ Avoid findAll for simple type searches
const nodes = figma.currentPage.findAll(n => n.type === 'TEXT')

// ✅ Enable invisible instance optimization
figma.skipInvisibleInstanceChildren = true

// ✅ Batch operations
const nodes = []
for (let i = 0; i < 100; i++) {
  const rect = figma.createRectangle()
  rect.x = i * 120
  nodes.push(rect)
}
figma.currentPage.selection = nodes
figma.viewport.scrollAndZoomIntoView(nodes)

// ❌ Avoid individual viewport updates
for (let i = 0; i < 100; i++) {
  const rect = figma.createRectangle()
  figma.viewport.scrollAndZoomIntoView([rect]) // Slow!
}
2. 字体加载
// ✅ Always load fonts before modifying text
const text = figma.createText()
await figma.loadFontAsync(text.fontName)
text.characters = 'Hello'

// ✅ Load font before changing fontName
await figma.loadFontAsync({ family: 'Roboto', style: 'Bold' })
text.fontName = { family: 'Roboto', style: 'Bold' }

// ❌ This will error
text.characters = 'Hello' // Error: font not loaded
3. 错误处理
try {
  const node = await figma.getNodeByIdAsync(id)
  if (!node) {
    figma.notify('Node not found', { error: true })
    return
  }

  // Process node
} catch (error) {
  figma.notify(`Error: ${error.message}`, { error: true })
  console.error(error)
}
4. 内存管理
// ✅ Clean up large data
figma.on('close', () => {
  // Clear large caches
  cache.clear()
})

// ✅ Use async iteration for large datasets
async function processManyNodes() {
  const nodes = figma.currentPage.findAllWithCriteria({ types: ['TEXT'] })

  for (let i = 0; i < nodes.length; i++) {
    await processNode(nodes[i])

    // Yield to UI every 100 items
    if (i % 100 === 0) {
      await new Promise(resolve => setTimeout(resolve, 0))
    }
  }
}
5. 类型安全
// ✅ Type guards
if (node.type === 'TEXT') {
  // TypeScript knows node is TextNode
  console.log(node.characters)
}

if ('children' in node) {
  // Node has children
  node.children.forEach(child => console.log(child.name))
}

// ✅ Use specific types
function processText(node: TextNode) {
  console.log(node.characters)
}

// ❌ Avoid any
function processNode(node: any) { // Bad!
  console.log(node.characters) // Might error
}

常见工作流

1. 设计系统设置
async function setupDesignSystem() {
  // Create variable collection
  const tokens = figma.variables.createVariableCollection('Design Tokens')

  // Colors
  const colors = {
    'color/primary': { r: 0.2, g: 0.5, b: 1, a: 1 },
    'color/secondary': { r: 0.5, g: 0.2, b: 0.8, a: 1 },
    'color/success': { r: 0.2, g: 0.8, b: 0.3, a: 1 },
    'color/error': { r: 0.9, g: 0.2, b: 0.2, a: 1 }
  }

  for (const [name, value] of Object.entries(colors)) {
    const variable = figma.variables.createVariable(name, tokens, 'COLOR')
    variable.setValueForMode(tokens.modes[0].modeId, value)
  }

  // Spacing
  const spacing = [4, 8, 12, 16, 24, 32, 48, 64]
  spacing.forEach((value, i) => {
    const variable = figma.variables.createVariable(
      `spacing/${i}`,
      tokens,
      'FLOAT'
    )
    variable.setValueForMode(tokens.modes[0].modeId, value)
  })

  // Create styles
  const primaryStyle = figma.createPaintStyle()
  primaryStyle.name = 'Color/Primary'
  primaryStyle.paints = [{
    type: 'SOLID',
    color: colors['color/primary']
  }]

  figma.notify('Design system created!')
}
2. 组件库
async function createButtonLibrary() {
  // Create component set for variants
  const buttons = figma.createComponentSet()
  buttons.name = "Button"

  // Primary variant
  const primary = figma.createComponent()
  primary.name = "Type=Primary, Size=Medium"
  primary.resize(120, 40)
  buttons.appendChild(primary)

  // Secondary variant
  const secondary = figma.createComponent()
  secondary.name = "Type=Secondary, Size=Medium"
  secondary.resize(120, 40)
  buttons.appendChild(secondary)

  // Large size
  const large = figma.createComponent()
  large.name = "Type=Primary, Size=Large"
  large.resize(160, 48)
  buttons.appendChild(large)

  return buttons
}
3. 批量导出
async function exportAllFrames() {
  const frames = figma.currentPage.findAllWithCriteria({
    types: ['FRAME']
  })

  const exports = []

  for (const frame of frames) {
    // Export 1x and 2x
    const bytes1x = await frame.exportAsync({
      format: 'PNG',
      constraint: { type: 'SCALE', value: 1 }
    })

    const bytes2x = await frame.exportAsync({
      format: 'PNG',
      constraint: { type: 'SCALE', value: 2 }
    })

    exports.push({
      name: frame.name,
      '1x': bytes1x,
      '2x': bytes2x
    })
  }

  // Send to UI for download
  figma.ui.postMessage({
    type: 'exports-ready',
    exports
  })
}
4. 样式同步
async function syncStyles() {
  const paintStyles = await figma.getLocalPaintStylesAsync()
  const textStyles = await figma.getLocalTextStylesAsync()

  // Update all instances
  const nodes = figma.currentPage.findAllWithCriteria({
    types: ['RECTANGLE', 'TEXT']
  })

  for (const node of nodes) {
    if (node.type === 'RECTANGLE') {
      // Apply paint style based on name
      const style = paintStyles.find(s => s.name === 'Brand/Primary')
      if (style) {
        node.fillStyleId = style.id
      }
    }

    if (node.type === 'TEXT') {
      // Apply text style
      const style = textStyles.find(s => s.name === 'Body/Regular')
      if (style) {
        node.textStyleId = style.id
      }
    }
  }

  figma.notify('Styles synced!')
}

插件 UI 模式

1. 适配主题的 UI
<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      margin: 0;
      padding: 16px;
      font-family: 'Inter', sans-serif;
      background: var(--figma-color-bg);
      color: var(--figma-color-text);
    }

    button {
      background: var(--figma-color-bg-brand);
      color: var(--figma-color-text-onbrand);
      border: none;
      padding: 8px 16px;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
    }

    button:hover {
      background: var(--figma-color-bg-brand-hover);
    }

    input {
      background: var(--figma-color-bg);
      color: var(--figma-color-text);
      border: 1px solid var(--figma-color-border);
      padding: 8px;
      border-radius: 4px;
    }
  </style>
</head>
<body>
  <h2>My Plugin</h2>
  <input type="text" id="input" placeholder="Enter text...">
  <button onclick="handleClick()">Create</button>
</body>
</html>
2. 加载状态
<div id="status">
  <div class="spinner"></div>
  <p>Processing...</p>
</div>

<script>
  function showLoading() {
    document.getElementById('status').style.display = 'block'
  }

  function hideLoading() {
    document.getElementById('status').style.display = 'none'
  }

  async function process() {
    showLoading()
    parent.postMessage({ pluginMessage: { type: 'process' } }, '*')
  }

  window.onmessage = (event) => {
    if (event.data.pluginMessage.type === 'complete') {
      hideLoading()
    }
  }
</script>

FigJam 专属功能

if (figma.editorType === 'figjam') {
  // Create sticky note
  const sticky = figma.createSticky()
  sticky.x = 100
  sticky.y = 100
  await figma.loadFontAsync({ family: "Roboto", style: "Regular" })
  sticky.text.characters = "TODO: Review designs"

  // Create connector
  const connector = figma.createConnector()
  connector.connectorStart = {
    endpointNodeId: sticky.id,
    position: { x: 0, y: 0.5 }
  }

  // Create shape
  const shape = figma.createShape()
  shape.resize(100, 100)
  shape.fills = [{ type: 'SOLID', color: { r: 1, g: 0.8, b: 0 } }]
}

高级技巧

1. 智能组件替换
async function swapComponents(oldComponent: ComponentNode, newComponent: ComponentNode) {
  const instances = figma.currentPage.findAllWithCriteria({
    types: ['INSTANCE']
  })

  let swapCount = 0

  for (const instance of instances) {
    if (instance.mainComponent?.id === oldComponent.id) {
      await instance.swapAsync(newComponent)
      swapCount++
    }
  }

  figma.notify(`Swapped ${swapCount} instances`)
}
2. 自动布局迁移
function convertToAutoLayout(frame: FrameNode) {
  // Store original positions
  const childData = frame.children.map(child => ({
    node: child,
    x: child.x,
    y: child.y
  }))

  // Enable auto layout
  frame.layoutMode = 'VERTICAL'
  frame.primaryAxisSizingMode = 'AUTO'
  frame.counterAxisSizingMode = 'FIXED'
  frame.itemSpacing = 16
  frame.paddingLeft = 24
  frame.paddingRight = 24
  frame.paddingTop = 24
  frame.paddingBottom = 24

  // Configure children
  frame.children.forEach(child => {
    if ('layoutAlign' in child) {
      child.layoutAlign = 'STRETCH'
    }
  })
}
3. 响应式调整大小
function makeResponsive(frame: FrameNode) {
  frame.layoutMode = 'VERTICAL'
  frame.primaryAxisSizingMode = 'AUTO'
  frame.counterAxisSizingMode = 'FIXED'

  // Make children responsive
  frame.children.forEach(child => {
    if ('resize' in child) {
      child.layoutAlign = 'STRETCH'
      child.layoutGrow = 0
      child.minWidth = 200
      child.maxWidth = 800
    }
  })
}

测试与调试

// Console logging
console.log('Selection:', figma.currentPage.selection)
console.error('Error occurred:', error)

// Notifications
figma.notify('Success!', { timeout: 2000 })
figma.notify('Error occurred', { error: true })

// Debugging helpers
function debugNode(node: SceneNode) {
  console.log('Node Debug Info:')
  console.log('  Type:', node.type)
  console.log('  Name:', node.name)
  console.log('  ID:', node.id)
  console.log('  Position:', { x: node.x, y: node.y })

  if ('width' in node) {
    console.log('  Size:', { width: node.width, height: node.height })
  }

  if ('children' in node) {
    console.log('  Children:', node.children.length)
  }
}

资源

总结

此技能涵盖:

  • 组件与实例管理
  • 自动布局与约束
  • 变量与设计令牌
  • 样式(绘制、文本、效果)
  • 原型设计与交互
  • 插件开发模式
  • 节点搜索与操作
  • 导出与图像处理
  • 性能优化
  • Context7 研究中的最佳实践

使用此技能构建可用于生产环境的 Figma 插件、自动化设计工作流、管理设计系统,以及创建可扩展的组件库。

评论

支持 Markdown