figma-capture-extension
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFigma Capture Extension
Figma Capture 扩展
Skill by ara.so — Design Skills collection
This Chrome extension captures any webpage into Figma's clipboard format, adding post-processing to fix fonts (especially CJK), clean up DOM structure, and remove empty elements before pasting into Figma.
来自ara.so的技能——设计技能合集
这款Chrome扩展可将任意网页捕获为Figma的剪贴板格式,在粘贴到Figma之前进行后期处理,修复字体(尤其是CJK字体)、清理DOM结构并移除空元素。
What It Does
功能介绍
- CJK Font Correction: Detects Chinese/Japanese/Korean text and remaps to /
PingFang SCNoto Serif SC - Font Mapping: Remaps unavailable fonts via configurable
font-map.json - Default Font Fallback: Assigns to elements without explicit fonts
Noto Sans SC - DOM Flattening: Removes wrapper elements that don't contribute visually
- Empty Frame Cleanup: Strips zero-size elements and childless containers
- Event Isolation: Prevents toolbar clicks from affecting host page behavior
- CJK字体修正:检测中文/日文/韩文文本,重新映射为/
PingFang SCNoto Serif SC - 字体映射:通过可配置的重新映射不可用字体
font-map.json - 默认字体回退:为未指定字体的元素分配
Noto Sans SC - DOM扁平化:移除对视觉无贡献的包装元素
- 空框架清理:剥离零尺寸元素和无子元素的容器
- 事件隔离:防止工具栏点击影响宿主页面行为
Installation
安装步骤
Step 1: Download Figma's Capture Script
步骤1:下载Figma的Capture脚本
bash
makeThis downloads from Figma's public endpoint. The Makefile does:
capture.jsmakefile
capture.js:
curl -o capture.js https://www.figma.com/community/plugin/1159123024924461424/capture.jsbash
make此命令会从Figma的公开端点下载。Makefile执行的操作如下:
capture.jsmakefile
capture.js:
curl -o capture.js https://www.figma.com/community/plugin/1159123024924461424/capture.jsStep 2: Configure Font Mapping
步骤2:配置字体映射
bash
cp font-map.example.json font-map.jsonExample structure:
font-map.jsonjson
{
"Arial": "Inter",
"Helvetica": "Inter",
"SF Pro Display": "Google Sans Flex",
"Roboto": "Noto Sans SC",
"system-ui": "Inter"
}bash
cp font-map.example.json font-map.jsonfont-map.jsonjson
{
"Arial": "Inter",
"Helvetica": "Inter",
"SF Pro Display": "Google Sans Flex",
"Roboto": "Noto Sans SC",
"system-ui": "Inter"
}Step 3: Load Extension in Chrome
步骤3:在Chrome中加载扩展
- Navigate to
chrome://extensions - Enable Developer mode (top right toggle)
- Click Load unpacked
- Select the directory
figma-capture
- 访问
chrome://extensions - 启用开发者模式(右上角开关)
- 点击加载已解压的扩展程序
- 选择目录
figma-capture
Usage
使用方法
Basic Capture Workflow
基础捕获流程
- Navigate to the webpage you want to capture
- Click the extension icon or press
Alt+Shift+F - Use the toolbar to:
- Capture entire page
- Select specific element by clicking
- Switch to Figma
- Press (Windows) or
Ctrl+V(Mac)Cmd+V
- 导航到需要捕获的网页
- 点击扩展图标 或 按下快捷键
Alt+Shift+F - 使用工具栏:
- 捕获整个页面
- 点击选择特定元素
- 切换到Figma
- 按下(Windows)或
Ctrl+V(Mac)粘贴Cmd+V
Keyboard Shortcut
键盘快捷键
Default:
Alt+Shift+FTo customize, go to
chrome://extensions/shortcuts默认快捷键:
Alt+Shift+F如需自定义,前往
chrome://extensions/shortcutsConfiguration Files
配置文件
manifest.json
manifest.json
Key configuration sections:
json
{
"manifest_version": 3,
"name": "Figma Capture",
"version": "1.0.0",
"permissions": [
"activeTab",
"clipboardWrite"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_start"
}
],
"action": {
"default_icon": "icon.png"
},
"commands": {
"_execute_action": {
"suggested_key": {
"default": "Alt+Shift+F"
}
}
}
}关键配置部分:
json
{
"manifest_version": 3,
"name": "Figma Capture",
"version": "1.0.0",
"permissions": [
"activeTab",
"clipboardWrite"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_start"
}
],
"action": {
"default_icon": "icon.png"
},
"commands": {
"_execute_action": {
"suggested_key": {
"default": "Alt+Shift+F"
}
}
}
}Font Mapping Configuration
字体映射配置
Edit to add custom font substitutions:
font-map.jsonjson
{
"SegoeUI": "Inter",
"San Francisco": "Google Sans Flex",
"PingFang": "PingFang SC",
"Microsoft YaHei": "Noto Sans SC",
"Hiragino Sans": "Noto Sans JP"
}Font mapping logic (applied in order):
- CJK detection → /
PingFang SCNoto Serif SC - Custom mappings
font-map.json - Icon font detection → preserve original
- No font specified →
Noto Sans SC
编辑添加自定义字体替换规则:
font-map.jsonjson
{
"SegoeUI": "Inter",
"San Francisco": "Google Sans Flex",
"PingFang": "PingFang SC",
"Microsoft YaHei": "Noto Sans SC",
"Hiragino Sans": "Noto Sans JP"
}字体映射逻辑(按顺序执行):
- CJK文本检测 → 应用/
PingFang SCNoto Serif SC - 自定义映射规则
font-map.json - 图标字体检测 → 保留原字体
- 未指定字体 → 应用
Noto Sans SC
Architecture
架构设计
Component Flow
组件流程
User clicks extension icon
↓
background.js activates content script
↓
content.js injects capture.js
↓
capture.js serializes DOM → clipboard payload
↓
Clipboard interceptor transforms payload:
- Font correction
- DOM cleanup
- Wrapper flattening
↓
Modified payload written to clipboard
↓
User pastes into Figma用户点击扩展图标
↓
background.js 激活内容脚本
↓
content.js 注入capture.js
↓
capture.js 序列化DOM → 剪贴板负载
↓
剪贴板拦截器转换负载:
- 字体修正
- DOM清理
- 包装元素扁平化
↓
修改后的负载写入剪贴板
↓
用户粘贴到FigmaKey Files
核心文件
- background.js: Service worker, patches for event isolation
attachShadow - content.js: Injected script, intercepts clipboard API
- capture.js: Figma's official serializer (downloaded, not in repo)
- font-map.json: User-configurable font substitutions
- background.js:服务工作线程,修补实现事件隔离
attachShadow - content.js:注入脚本,拦截剪贴板API
- capture.js:Figma官方序列化脚本(需下载,不在仓库中)
- font-map.json:用户可配置的字体替换规则
Code Examples
代码示例
Clipboard Interception Pattern
剪贴板拦截模式
javascript
// Override clipboard write to transform Figma payload
const originalWrite = navigator.clipboard.write;
navigator.clipboard.write = async function(data) {
const items = await Promise.all(data.map(async item => {
if (item.types.includes('text/html')) {
const blob = await item.getType('text/html');
const html = await blob.text();
// Transform Figma's clipboard payload
const transformed = transformFigmaPayload(html);
return new ClipboardItem({
'text/html': new Blob([transformed], { type: 'text/html' })
});
}
return item;
}));
return originalWrite.call(this, items);
};javascript
// Override clipboard write to transform Figma payload
const originalWrite = navigator.clipboard.write;
navigator.clipboard.write = async function(data) {
const items = await Promise.all(data.map(async item => {
if (item.types.includes('text/html')) {
const blob = await item.getType('text/html');
const html = await blob.text();
// Transform Figma's clipboard payload
const transformed = transformFigmaPayload(html);
return new ClipboardItem({
'text/html': new Blob([transformed], { type: 'text/html' })
});
}
return item;
}));
return originalWrite.call(this, items);
};Font Detection and Remapping
字体检测与重映射
javascript
function detectAndFixFont(element, computedStyle) {
const text = element.textContent || '';
const fontFamily = computedStyle.fontFamily;
// CJK detection
const cjkRegex = /[\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/;
if (cjkRegex.test(text)) {
// Check if serif
const isSerif = /serif/i.test(fontFamily);
return isSerif ? 'Noto Serif SC' : 'PingFang SC';
}
// Load font map
const fontMap = loadFontMap(); // from font-map.json
// Check for mapped font
for (const [original, replacement] of Object.entries(fontMap)) {
if (fontFamily.includes(original)) {
return replacement;
}
}
// Icon font detection (preserve)
if (/icon|symbol|awesome|material/i.test(fontFamily)) {
return fontFamily;
}
// Default fallback
return fontFamily || 'Noto Sans SC';
}javascript
function detectAndFixFont(element, computedStyle) {
const text = element.textContent || '';
const fontFamily = computedStyle.fontFamily;
// CJK detection
const cjkRegex = /[\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/;
if (cjkRegex.test(text)) {
// Check if serif
const isSerif = /serif/i.test(fontFamily);
return isSerif ? 'Noto Serif SC' : 'PingFang SC';
}
// Load font map
const fontMap = loadFontMap(); // from font-map.json
// Check for mapped font
for (const [original, replacement] of Object.entries(fontMap)) {
if (fontFamily.includes(original)) {
return replacement;
}
}
// Icon font detection (preserve)
if (/icon|symbol|awesome|material/i.test(fontFamily)) {
return fontFamily;
}
// Default fallback
return fontFamily || 'Noto Sans SC';
}DOM Cleanup: Wrapper Flattening
DOM清理:包装元素扁平化
javascript
function flattenWrappers(node) {
// Skip if not a wrapper candidate
if (node.children.length !== 1) return false;
const parent = node;
const child = node.children[0];
// Check if parent is just a pass-through container
const parentStyle = getComputedStyle(parent);
const childStyle = getComputedStyle(child);
const isPassThrough =
parentStyle.backgroundColor === 'transparent' &&
parentStyle.border === 'none' &&
parent.getBoundingClientRect().width === child.getBoundingClientRect().width &&
parent.getBoundingClientRect().height === child.getBoundingClientRect().height;
if (isPassThrough) {
// Promote child to parent's position
parent.replaceWith(child);
return true;
}
return false;
}javascript
function flattenWrappers(node) {
// Skip if not a wrapper candidate
if (node.children.length !== 1) return false;
const parent = node;
const child = node.children[0];
// Check if parent is just a pass-through container
const parentStyle = getComputedStyle(parent);
const childStyle = getComputedStyle(child);
const isPassThrough =
parentStyle.backgroundColor === 'transparent' &&
parentStyle.border === 'none' &&
parent.getBoundingClientRect().width === child.getBoundingClientRect().width &&
parent.getBoundingClientRect().height === child.getBoundingClientRect().height;
if (isPassThrough) {
// Promote child to parent's position
parent.replaceWith(child);
return true;
}
return false;
}Event Isolation for Toolbar
工具栏事件隔离
javascript
// In background.js - save closed shadow roots
const shadowRoots = new WeakMap();
const originalAttachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function(init) {
const shadowRoot = originalAttachShadow.call(this, init);
if (init.mode === 'closed') {
shadowRoots.set(this, shadowRoot);
}
return shadowRoot;
};
// In content.js - intercept toolbar clicks
window.addEventListener('click', (event) => {
const toolbar = document.querySelector('.figma-capture-toolbar');
if (!toolbar) return;
const toolbarHost = toolbar.host;
const shadowRoot = shadowRoots.get(toolbarHost);
if (shadowRoot) {
event.stopPropagation();
event.preventDefault();
// Re-dispatch inside shadow DOM
const point = shadowRoot.elementFromPoint(event.clientX, event.clientY);
if (point) {
point.dispatchEvent(new MouseEvent('click', event));
}
}
}, true); // Capture phasejavascript
// In background.js - save closed shadow roots
const shadowRoots = new WeakMap();
const originalAttachShadow = Element.prototype.attachShadow;
Element.prototype.attachShadow = function(init) {
const shadowRoot = originalAttachShadow.call(this, init);
if (init.mode === 'closed') {
shadowRoots.set(this, shadowRoot);
}
return shadowRoot;
};
// In content.js - intercept toolbar clicks
window.addEventListener('click', (event) => {
const toolbar = document.querySelector('.figma-capture-toolbar');
if (!toolbar) return;
const toolbarHost = toolbar.host;
const shadowRoot = shadowRoots.get(toolbarHost);
if (shadowRoot) {
event.stopPropagation();
event.preventDefault();
// Re-dispatch inside shadow DOM
const point = shadowRoot.elementFromPoint(event.clientX, event.clientY);
if (point) {
point.dispatchEvent(new MouseEvent('click', event));
}
}
}, true); // Capture phaseCommon Patterns
通用模式
Handling Special Elements
特殊元素处理
javascript
// Skip script/style/svg from processing
const SKIP_TAGS = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'LINK', 'META'];
function shouldProcessElement(element) {
if (SKIP_TAGS.includes(element.tagName)) {
return false;
}
// Skip hidden elements
const style = getComputedStyle(element);
if (style.display === 'none' || style.visibility === 'hidden') {
return false;
}
// Skip zero-size elements
const rect = element.getBoundingClientRect();
if (rect.width === 0 && rect.height === 0) {
return false;
}
return true;
}javascript
// Skip script/style/svg from processing
const SKIP_TAGS = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'LINK', 'META'];
function shouldProcessElement(element) {
if (SKIP_TAGS.includes(element.tagName)) {
return false;
}
// Skip hidden elements
const style = getComputedStyle(element);
if (style.display === 'none' || style.visibility === 'hidden') {
return false;
}
// Skip zero-size elements
const rect = element.getBoundingClientRect();
if (rect.width === 0 && rect.height === 0) {
return false;
}
return true;
}Custom Font Mapping for Specific Websites
特定网站自定义字体映射
javascript
// Create site-specific font mappings
const siteSpecificMaps = {
'github.com': {
'-apple-system': 'Inter',
'BlinkMacSystemFont': 'Inter',
'Segoe UI': 'Inter'
},
'twitter.com': {
'TwitterChirp': 'Google Sans Flex',
'Helvetica Neue': 'Inter'
}
};
function getFontMap() {
const hostname = window.location.hostname;
const baseMap = JSON.parse(localStorage.getItem('font-map.json') || '{}');
const siteMap = siteSpecificMaps[hostname] || {};
return { ...baseMap, ...siteMap };
}javascript
// Create site-specific font mappings
const siteSpecificMaps = {
'github.com': {
'-apple-system': 'Inter',
'BlinkMacSystemFont': 'Inter',
'Segoe UI': 'Inter'
},
'twitter.com': {
'TwitterChirp': 'Google Sans Flex',
'Helvetica Neue': 'Inter'
}
};
function getFontMap() {
const hostname = window.location.hostname;
const baseMap = JSON.parse(localStorage.getItem('font-map.json') || '{}');
const siteMap = siteSpecificMaps[hostname] || {};
return { ...baseMap, ...siteMap };
}Troubleshooting
故障排除
Fonts Not Displaying Correctly in Figma
Figma中字体显示不正确
Problem: Pasted elements show default Times or incorrect fonts
Solution: Check if fonts are installed in Figma:
javascript
// Add debug logging to see what fonts are being applied
console.log('Applied font:', appliedFont);
console.log('Original font:', originalFont);
console.log('Font map:', fontMap);Update to map problematic fonts to fonts you have in Figma:
font-map.jsonjson
{
"ProblematicFont": "Inter",
"AnotherBadFont": "Roboto"
}问题:粘贴的元素显示默认Times字体或错误字体
解决方案:检查字体是否已在Figma中安装:
javascript
// Add debug logging to see what fonts are being applied
console.log('Applied font:', appliedFont);
console.log('Original font:', originalFont);
console.log('Font map:', fontMap);更新,将有问题的字体映射到你在Figma中已有的字体:
font-map.jsonjson
{
"ProblematicFont": "Inter",
"AnotherBadFont": "Roboto"
}Extension Not Activating
扩展无法激活
Problem: Clicking icon does nothing
Solution: Check console for errors:
- Right-click extension icon → Inspect popup
- Go to → Find Figma Capture → Click "background page"
chrome://extensions - Check for errors in console
Verify exists:
capture.jsbash
ls -la capture.js问题:点击图标无反应
解决方案:检查控制台错误:
- 右键点击扩展图标 → 检查弹出窗口
- 前往→ 找到Figma Capture → 点击“背景页”
chrome://extensions - 检查控制台中的错误信息
确认存在:
capture.jsbash
ls -la capture.jsIf missing:
如果缺失:
make
undefinedmake
undefinedClipboard Payload Not Modified
剪贴板负载未被修改
Problem: Fonts/cleanup not being applied
Solution: Verify clipboard interceptor is loaded:
javascript
// In browser console on target page:
console.log(navigator.clipboard.write.toString());
// Should show wrapped function, not native codeReload extension:
chrome://extensions- Click reload icon for Figma Capture
- Refresh target webpage
问题:字体修复/清理未生效
解决方案:验证剪贴板拦截器已加载:
javascript
// In browser console on target page:
console.log(navigator.clipboard.write.toString());
// 应显示包装后的函数,而非原生代码重新加载扩展:
- 打开
chrome://extensions - 点击Figma Capture的重新加载图标
- 刷新目标网页
Empty or Broken Capture
捕获内容为空或损坏
Problem: Pasting creates empty frame or errors
Solution: Some elements may be over-cleaned. Adjust cleanup logic:
javascript
// Disable wrapper flattening temporarily
const ENABLE_FLATTENING = false;
if (ENABLE_FLATTENING) {
flattenWrappers(node);
}Check for CSP violations in console — some sites block extension scripts.
问题:粘贴后生成空框架或出现错误
解决方案:部分元素可能被过度清理,调整清理逻辑:
javascript
// Disable wrapper flattening temporarily
const ENABLE_FLATTENING = false;
if (ENABLE_FLATTENING) {
flattenWrappers(node);
}检查控制台中的CSP违规信息——部分网站会阻止扩展脚本注入。
CJK Text Not Detected
CJK文本未被检测到
Problem: Chinese/Japanese/Korean text uses wrong font
Solution: Verify regex pattern matches your text:
javascript
const text = "你好世界";
const cjkRegex = /[\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/;
console.log(cjkRegex.test(text)); // Should be trueManually set font in :
font-map.jsonjson
{
"SimSun": "Noto Sans SC",
"Microsoft YaHei": "PingFang SC"
}问题:中文/日文/韩文文本使用错误字体
解决方案:验证正则表达式是否匹配你的文本:
javascript
const text = "你好世界";
const cjkRegex = /[\u4E00-\u9FFF\u3040-\u309F\u30A0-\u30FF\uAC00-\uD7AF]/;
console.log(cjkRegex.test(text)); // 应返回true在中手动设置字体:
font-map.jsonjson
{
"SimSun": "Noto Sans SC",
"Microsoft YaHei": "PingFang SC"
}Advanced Configuration
高级配置
Modify Default Fallback Font
修改默认回退字体
Edit :
content.jsjavascript
const DEFAULT_FALLBACK_FONT = 'Inter'; // Change from 'Noto Sans SC'编辑:
content.jsjavascript
const DEFAULT_FALLBACK_FONT = 'Inter'; // 从'Noto Sans SC'修改Add Custom Cleanup Rules
添加自定义清理规则
javascript
function customCleanup(element) {
// Remove all data attributes
for (const attr of element.attributes) {
if (attr.name.startsWith('data-')) {
element.removeAttribute(attr.name);
}
}
// Remove specific classes
const REMOVE_CLASSES = ['ad', 'banner', 'cookie-notice'];
element.classList.remove(...REMOVE_CLASSES);
}javascript
function customCleanup(element) {
// Remove all data attributes
for (const attr of element.attributes) {
if (attr.name.startsWith('data-')) {
element.removeAttribute(attr.name);
}
}
// Remove specific classes
const REMOVE_CLASSES = ['ad', 'banner', 'cookie-notice'];
element.classList.remove(...REMOVE_CLASSES);
}Debug Mode
调试模式
Add to :
content.jsjavascript
const DEBUG = true;
function debug(...args) {
if (DEBUG) {
console.log('[Figma Capture]', ...args);
}
}
debug('Font applied:', fontFamily);
debug('Element cleaned:', element.tagName);在中添加:
content.jsjavascript
const DEBUG = true;
function debug(...args) {
if (DEBUG) {
console.log('[Figma Capture]', ...args);
}
}
debug('Font applied:', fontFamily);
debug('Element cleaned:', element.tagName);Limitations
局限性
- Figma Dependency: Relies on Figma's undocumented clipboard format (may break)
- Font Availability: Target fonts must be installed in Figma
- CSP Restrictions: Some sites block extension script injection
- Dynamic Content: May not capture lazy-loaded or JavaScript-rendered elements
- Updates: Figma may change/remove the download endpoint
capture.js
- Figma依赖:依赖Figma未公开的剪贴板格式(可能随时失效)
- 字体可用性:目标字体必须已在Figma中安装
- CSP限制:部分网站会阻止扩展脚本注入
- 动态内容:可能无法捕获懒加载或JavaScript渲染的元素
- 更新:Figma可能更改/移除下载端点
capture.js
Resources
相关资源
- Figma HTML to Design Plugin: https://www.figma.com/community/plugin/1159123024924461424
- Chrome Extension Manifest V3: https://developer.chrome.com/docs/extensions/mv3/
- Clipboard API: https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API
- Figma HTML转设计插件:https://www.figma.com/community/plugin/1159123024924461424
- Chrome扩展Manifest V3:https://developer.chrome.com/docs/extensions/mv3/
- Clipboard API:https://developer.mozilla.org/en-US/docs/Web/API/Clipboard_API