mcpField
F
figma-design

figma-design

github.com/manutejデザイン・UI62542 回インストール39 週間前に更新
リポジトリ

figma-design について

figma-design は、Figma のデザインワークフローとプラグイン開発を扱うスキルガイドです。Figma Plugin API の資料に基づき、コンポーネント、オートレイアウト、プロトタイプ、データ管理、アセット出力を解説します。

主な機能

  1. 01Figma プラグイン開発
  2. 02コンポーネントとデザインシステムの管理
  3. 03オートレイアウトと制約
  4. 04インタラクティブなプロトタイプ設定
  5. 05ノード検索と一括操作
  6. 06データ保存とアセット出力

活用シーン

  1. 01Figma プラグインと自動化ツールの開発
  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 Design スキル

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 プラグインの構築、デザインワークフローの自動化、デザインシステムの管理、拡張性の高いコンポーネントライブラリの作成に活用できます。

コメント