paint-brush
Как да импортирате икони от Figma по страхотен начинот@smileek
Нова история

Как да импортирате икони от Figma по страхотен начин

от Andrei Sieedugin8m2025/03/04
Read on Terminal Reader

Твърде дълго; Чета

Това ръководство показва как лесно да импортирате икони от Figma в React/Vue проект и да създадете компонент, който позволява използването им в различни размери и цветове.
featured image - Как да импортирате икони от Figma по страхотен начин
Andrei Sieedugin HackerNoon profile picture

Да кажем, че имате набор от икони във Figma и проект в React/Vue/каквото и да е друго. Не е изненада, ще трябва да използвате тези икони. Как можем да ги импортираме?


Ако сте достатъчно упорит, можете да ги изтеглите един по един и да ги добавите като активи. Но това е тъжен начин. Не ми харесва.


Нека спрем да сме тъжни и вместо това да бъдем страхотни. Ето какво ще ни трябва:

  • Хубав компонент, който позволява използването на тези икони в различни размери и цветове.
  • Автоматично довършване по имена на икони.
  • Проста актуализация при добавяне на нова икона.


*без повече шум*

Започнете тук

Ще ни трябва Figma токен. Не мисля, че е необходимо да обяснявам как се прави това, така че вместо това има раздел за помощ: https://help.figma.com/hc/en-us/articles/8085703771159-Manage-personal-access-tokens .


Настройките по подразбиране са достатъчно добри: имаме нужда от достъп само за четене до съдържанието на файла.



Дайте ми вашата лична карта

Ще използвам иконите за материален дизайн (Общност) като пример.


Ще ни трябва файлов ключ и идентификатор на главния възел:


Сега, когато имаме достатъчно предпоставки, нека започнем да кодираме. Харесвам моите скриптове да са отделени от основния код на проекта, така че ще създам папка scripts в корена и fetch-icons.ts в нея. Няма нужда да го правите на TypeScript, но това прави тази статия доста изискана, така че ще се придържам към нея.


Това обаче ще изисква още няколко зависимости:

 yarn add -D tsx dotenv axios @figma/rest-api-spec


dotenv е тук, за да използва променливите .env , tsx — за стартиране на TS скрипта (фантазия, помните ли?), @figma/rest-api-spec е за безопасност на типа, а axios е просто защото съм свикнал с него. Единственият задължителен е dotenv (освен ако не сте съгласни с добавянето на API ключа към кода) и ще добавя „чистата“ версия в края. Засега ще имаме тази настройка и ето с какво започваме:

 import * as dotenv from "dotenv"; dotenv.config(); const PERSONAL_ACCESS_TOKEN = String( process.env.VITE_FIGMA_PERSONAL_ACCESS_TOKEN ); // I've added my Figma token to the .env file, and so should you const FIGMA_API_URL = "https://api.figma.com/v1"; const FILE_KEY = "v50KJO82W9bBJUppE8intT"; // this one is from the URL const NODE_ID = "2402-2207"; // node-id query param, also from the URL


Донеси!

Първо ще получим метаданните за основния кадър:

 import { GetFileNodesResponse, GetImagesResponse, HasChildrenTrait, } from "@figma/rest-api-spec"; // ... const fetchFrameData = async ( nodeId: string ): Promise<HasChildrenTrait> => { const { data } = await axios.get<GetFileNodesResponse>( `${FIGMA_API_URL}/files/${FILE_KEY}/nodes?ids=${nodeId}`, getConfig() ); return data.nodes[nodeId].document as HasChildrenTrait; };


Ще върне информацията за своите деца. Това могат да бъдат изображения (техният тип ще бъде INSTANCE ) или други рамки, които ще анализираме рекурсивно:

 const getImageNodes = ( frameData: HasChildrenTrait ): Record<string, string> => { const nodes: Record<string, string> = {}; for (const image of frameData.children.filter( (node) => node.type === 'INSTANCE' )) { // normalizeName simply converts 'check-box' to 'CheckBox' const name = normalizeName(image.name); const id = image.id; nodes[id] = name; } for (const frame of frameData.children.filter( (node) => node.type === 'FRAME' )) { Object.assign(nodes, getImageNodes(frame)); } return nodes; };


И накрая, когато имаме куп идентификатори на изображения, ще извлечем URL адреси на SVG файлове:

 const fetchImageUrls = async ( nodeIds: string[] ): Promise<GetImagesResponse> => { const { data } = await axios.get<GetImagesResponse>( `${FIGMA_API_URL}/images/${FILE_KEY}?ids=${nodeIds.join(",")}&format=svg`, getConfig() ); return data; };


И ще завършим с масив от такива обекти:

 { name: 'CheckBox', url: 'https://figma-alpha-api.s3.us-west-2.amazonaws.com/images/7dd5bc31-4f26-4701-b155-7bf7dea45824' }


Както вече се досещате, ще ги изтеглим всички, но това не е най-интересното.

Тяхната анатомия

Всеки път, когато извличаме SVG, получаваме нещо подобно:

 <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M17 6H7C3.69 6 1 8.69 [...] 9 17 9Z" fill="black" /> </svg>


Ще премахнем svg таговете, тъй като те са идентични за всяка икона (предполагам, че са с еднакъв размер за по-лесно). Но по-интересното е, че ще заменим всички fill=”black” с контейнер в литерал на шаблон и в резултат на това ще имаме такава функция:

 export const IconToggleOn = (color: string) => `<path d="M17 6H7C3.69 [...] 9 17 9Z" fill="${color}"/>`;


Ето как го получаваме:

 const cleanupSvg = (data: string): string => { const svgTags = /<\/?svg.*?>/g; const colorFills = /fill=["'].+?["']/g; return data .replace(svgTags, "") .replace(colorFills, 'fill="${color}"') .replace(/\n/g, ""); };


Очевидно няма да е необходимо да заменяте цветовете, ако трябва да импортирате някои лога, но създаването на различна логика за тях ще бъде вашата домашна работа.

Нека да започнем

Добре, ето нашия план:

  1. Премахнете получения файл, ако съществува.
  2. Извличане на възли на икона от Figma.
  3. Извличане на URL адреси на икони за тези възли.
  4. Изтеглете SVG.
  5. Създайте нов файл с нашите функции за икони.


Няма да цитирам целия файл — можете да го проверите в GitHub . Нека просто да разгледаме самото изтегляне:

 const FILE_BATCH_SIZE = 10; const fileContent: string[] = []; const getPromise = async (item: IconData) => { try { const { data } = await axios.get<string>( item.url, getConfig("image/svg+xml") // returns necessary options and headers ); return { name: item.name, data }; } catch (err) { console.error(err); } }; for (let i = 0; i < iconsData.length; i += FILE_BATCH_SIZE) { const batch = await Promise.allSettled( iconsData.slice(i, i + FILE_BATCH_SIZE).map(getPromise) ); for (const icon of batch) { if (icon?.status === "fulfilled" && icon.value) { const iconName = `Icon${icon.value.name}`; const svgData = cleanupSvg(icon.value.data); fileContent.push( `export const ${iconName} = (color: string) =>\n \`${svgData}\`;\n` ); } } }


Пакетирането е важно тук, защото в противен случай Figma няма да ви хареса, ако решите да изтеглите няколко хиляди файла паралелно.


Ако трябва да получите икона с името, което получавате от бекенда, можете също да създадете речник:

 const iconArray: string[] = []; // ... for (const icon of batch) { if (icon?.status === "fulfilled" && icon.value) { const iconName = `Icon${icon.value.name}`; const svgData = cleanupSvg(icon.value.data); fileContent.push( `export const ${iconName} = (color: string) =>\n \`${svgData}\`;\n` ); iconArray.push(iconName); // <-- we added this } } // ... when all files are processed fileContent.push( `\r\nexport const iconDictionary: Record<string, (color: string) => string> = {\r\n ${iconArray.join( ",\r\n " )}\r\n};\r\n` );


Този подход е по-лош, тъй като целият набор ще бъде добавен към пакета, дори ако използвате само една икона чрез речника, но понякога трябва да направите нещо неприятно. Освен това всички \r\n и интервали са тук, за да зарадват моя ESLint: може да се наложи да коригирате количеството им.


Тук идва последната стъпка; нека направим нов файл:

 import path from "path"; import { fileURLToPath } from "url"; // ... const PATH_TO_ICONS = "../src/assets/icons"; const currentPath = fileURLToPath(import.meta.url); const currentDirectory = path.dirname(currentPath); const fileName = "index.ts"; const filePath = path.join( path.resolve(currentDirectory, PATH_TO_ICONS), fileName ); // ... fs.writeFileSync(filePath, fileContent.join(""));


Едно малко нещо

Добра идея е да сортирате иконите след извличане на URL адресите, което е толкова просто, колкото това:

 iconsData.sort((a, b) => a.name.localeCompare(b.name));


Сортираният списък ще бъде по-лесен за четене и, което е по-важно, ще имаме четлива разлика, когато получим нови икони. В противен случай редът не е гарантиран и добавянето на една икона може да обърка всичко.

Създаваме отново файла, но редът на иконите остава непокътнат


Сега какво?

Имаме нужда от компонент, който приема низа на иконата, цвета и размера като подпори и рисува нещо сладко:

 import React, { useMemo } from "react"; import { IconSvg } from "../types/IconSvg"; interface IconProps { svg: IconSvg; // type IconSvg = (color: string) => string; color?: string; size?: number | string; } const refine = (x: string | number) => { return typeof x === "number" || !/\D+/.test(x) ? `${x}px` : x; }; const MyIcon: React.FC<IconProps> = ({ svg, color, size, }) => { const iconColor = useMemo(() => color ?? "black", [color]); const refinedSize = useMemo(() => refine(size ?? 24), [size]); const currentIcon = useMemo(() => svg(currentColor), [svg, currentColor]); return ( <svg viewBox="0 0 24 24" // make sure it's the same as in Figma dangerouslySetInnerHTML={{ __html: currentIcon }} style={{ width: refinedSize, minWidth: refinedSize, height: refinedSize, minHeight: refinedSize, }} ></svg> ); }; export default MyIcon;


Вярвам, че е доста лесно, с изключение на прецизиране на размера: когато го внедрих за първи път, Chrome прие числа като ширина/височина CSS свойства, но Firefox не, така че добавих px . Дори и вече да не е калъф, нека да си остане такъв, какъвто трябва да бъде по стандартите.


И ето автодовършването, което обещах:

Това е всичко, хора

Ето как го готвя. Надявам се да ви е полезно.


Недостатъкът на този подход е, че иконите ще бъдат вградени и няма да бъдат кеширани поради това. Въпреки това не мисля, че е голяма работа за няколко байта. Освен това можете да добавите въртене, промяна на цвета при задържане, каквото и да е друго, и ще имате едно малко швейцарско ножче от тези икони.


Освен това ще изисква организиран файл във Figma. Но хей, ако можете да напишете целия този код, можете също така да убедите вашите дизайнери да го подредят.


Можете да намерите пълния код, включително React и Vue компоненти и Node.js версия с по-малко зависимости тук: https://github.com/Smileek/fetch-icons

L O A D I N G
. . . comments & more!

About Author

Andrei Sieedugin HackerNoon profile picture
Andrei Sieedugin@smileek
Senior frontend developer with product management experience

ЗАКАЧВАЙТЕ ЕТИКЕТИ

ТАЗИ СТАТИЯ Е ПРЕДСТАВЕНА В...