Các nhà phát triển thường chèn SVG trực tiếp vào JSX. Điều này thuận tiện khi sử dụng nhưng nó làm tăng kích thước gói JS. Để tối ưu hóa, tôi quyết định tìm một cách khác để sử dụng các biểu tượng SVG mà không làm lộn xộn gói. Chúng ta sẽ nói về các họa tiết SVG, chúng là gì, cách sử dụng chúng và những công cụ nào có sẵn để làm việc với chúng.
Bắt đầu với lý thuyết, chúng ta sẽ viết từng bước một tập lệnh tạo sprite SVG và kết thúc bằng cách thảo luận về các plugin cho vite và webpack .
Hình ảnh sprite là tập hợp các hình ảnh được đặt trong một hình ảnh duy nhất. Ngược lại, SVG Sprite là một tập hợp nội dung SVG, được gói trong <symbol />
, được đặt trong <svg />
.
Ví dụ: chúng ta có biểu tượng bút SVG đơn giản:
Để có được một SVG sprite, chúng ta sẽ thay thế thẻ <svg />
bằng <symbol />
và bọc nó bằng <svg />
ở bên ngoài:
Bây giờ nó là một sprite SVG và chúng ta có một biểu tượng bên trong với id="icon-pen"
.
Được rồi, nhưng chúng ta nên tìm cách đặt biểu tượng này trên trang HTML của mình. Chúng ta sẽ sử dụng thẻ <use />
với thuộc tính href
, chỉ định ID của biểu tượng và nó sẽ sao chép phần tử này bên trong SVG.
Chúng ta hãy xem một ví dụ về cách hoạt động của <use />
:
Trong ví dụ này, có hai vòng tròn. Cái đầu tiên có đường viền màu xanh lam và cái thứ hai là bản sao của cái đầu tiên nhưng có màu đỏ.
Hãy quay lại với sprite SVG của chúng ta. Cùng với việc sử dụng <use />
, chúng ta sẽ nhận được điều này:
Ở đây, chúng ta có một nút có biểu tượng cây bút.
Cho đến nay, chúng ta đã sử dụng một biểu tượng không có mã trong <button />
. Nếu chúng tôi có nhiều hơn một nút trên trang, nó sẽ không ảnh hưởng nhiều lần đến kích thước bố cục HTML của chúng tôi vì tất cả các biểu tượng sẽ đến từ SVG sprite của chúng tôi và sẽ có thể sử dụng lại được.
Hãy di chuyển sprite SVG của chúng ta vào một tệp riêng biệt để chúng ta không phải làm lộn xộn tệp index.html
. Đầu tiên, tạo một tệp sprite.svg
và đặt một sprite SVG vào đó. Bước tiếp theo là cung cấp quyền truy cập vào biểu tượng bằng thuộc tính href
trong <use/>
:
Để tiết kiệm nhiều thời gian sử dụng biểu tượng, hãy thiết lập tự động hóa cho quy trình này. Để dễ dàng truy cập vào các biểu tượng và quản lý chúng theo ý muốn, chúng phải được tách riêng, mỗi biểu tượng nằm trong một tệp riêng.
Đầu tiên chúng ta nên đặt tất cả các icon vào cùng một thư mục, ví dụ:
Bây giờ, hãy viết một tập lệnh lấy các tệp này và kết hợp chúng thành một SVG sprite duy nhất.
Tạo tệp generateSvgSprite.ts
trong thư mục gốc của dự án của bạn.
Cài đặt thư viện toàn cầu :
npm i -D glob
Nhận một loạt đường dẫn đầy đủ cho mỗi biểu tượng bằng cách sử dụng globSync
:
Bây giờ, chúng ta sẽ lặp lại từng đường dẫn tệp và lấy nội dung tệp bằng thư viện tích hợp của Node fs :
Tuyệt vời, chúng ta có mã SVG của từng biểu tượng và bây giờ chúng ta có thể kết hợp chúng, nhưng chúng ta nên thay thế thẻ svg
bên trong mỗi biểu tượng bằng thẻ symbol
và xóa các thuộc tính SVG vô dụng.
Chúng ta nên phân tích mã SVG của mình bằng một số thư viện trình phân tích cú pháp HTML để có được biểu diễn DOM của nó. Tôi sẽ sử dụng node-html-parser :
Chúng tôi đã phân tích mã SVG và thu được phần tử SVG như thể nó là một phần tử HTML thực.
Sử dụng cùng một trình phân tích cú pháp, tạo một phần tử symbol
trống để di chuyển phần tử con của svgElement
sang symbol
:
Sau khi trích xuất các phần tử con từ svgElement
, chúng ta cũng sẽ lấy các thuộc tính id
và viewBox
từ nó. Với id
, hãy đặt tên của tệp biểu tượng.
Bây giờ, chúng ta có một phần tử symbol
có thể được đặt trong một sprite SVG. Vì vậy, chỉ cần xác định biến symbols
trước khi lặp lại các tệp, chuyển đổi symbolElement
thành một chuỗi và đẩy nó thành symbols
:
Bước cuối cùng là tạo sprite SVG. Nó đại diện cho một chuỗi có svg
trong “root” và các ký hiệu là con:
const svgSprite = `<svg>${symbols.join('')}</svg>`;
Và nếu bạn không cân nhắc việc sử dụng các plugin mà tôi sẽ nói bên dưới, bạn cần đặt tệp có sprite đã tạo vào một số thư mục tĩnh. Hầu hết các gói sử dụng thư mục public
:
fs.writeFileSync('public/sprite.svg', svgSprite);
Và đây là nó; tập lệnh đã sẵn sàng để sử dụng:
// generateSvgSprite.ts import { globSync } from 'glob'; import fs from 'fs'; import { HTMLElement, parse } from 'node-html-parser'; import path from 'path'; const svgFiles = globSync('src/icons/*.svg'); const symbols: string[] = []; svgFiles.forEach(file => { const code = fs.readFileSync(file, 'utf-8'); const svgElement = parse(code).querySelector('svg') as HTMLElement; const symbolElement = parse('<symbol/>').querySelector('symbol') as HTMLElement; const fileName = path.basename(file, '.svg'); svgElement.childNodes.forEach(child => symbolElement.appendChild(child)); symbolElement.setAttribute('id', fileName); if (svgElement.attributes.viewBox) { symbolElement.setAttribute('viewBox', svgElement.attributes.viewBox); } symbols.push(symbolElement.toString()); }); const svgSprite = `<svg>${symbols.join('')}</svg>`; fs.writeFileSync('public/sprite.svg', svgSprite);
Bạn có thể đặt tập lệnh này vào thư mục gốc của dự án và chạy nó với tsx :
npx tsx generateSvgSprite.ts
Trên thực tế, tôi đang sử dụng tsx ở đây vì tôi đã từng viết mã bằng TypeScript ở mọi nơi và thư viện này cho phép bạn thực thi các tập lệnh nút được viết bằng TypeScript. Nếu bạn muốn sử dụng JavaScript thuần túy, thì bạn có thể chạy nó bằng:
node generateSvgSprite.js
Vì vậy, hãy tóm tắt những gì kịch bản đang làm:
src/icons
để tìm bất kỳ tệp .svg
nào.
<svg />.
sprite.svg
trong thư mục public
.Hãy đề cập đến một trường hợp thường xuyên và quan trọng: màu sắc! Chúng tôi đã tạo một tập lệnh trong đó biểu tượng chuyển thành hình sprite, nhưng biểu tượng này có thể có các màu khác nhau trong suốt dự án.
Chúng ta nên nhớ rằng không chỉ các phần tử <svg/>
có thể có thuộc tính điền hoặc nét, mà còn có các thuộc path
, circle
, line
và các thuộc tính khác. Có một tính năng CSS rất hữu ích sẽ giúp chúng ta - currentcolor .
Từ khóa này đại diện cho giá trị thuộc tính màu của một phần tử. Ví dụ: nếu chúng ta sử dụng color: red
trên một phần tử có background: currentcolor
thì phần tử này sẽ có nền màu đỏ.
Về cơ bản, chúng ta cần thay đổi mọi giá trị thuộc tính nét hoặc tô thành currentcolor
. Tôi hy vọng bạn không thấy nó được thực hiện thủ công, heh. Và ngay cả việc viết một số mã sẽ thay thế hoặc phân tích chuỗi SVG cũng không hiệu quả lắm so với một công cụ rất hữu ích svgo .
Đây là trình tối ưu hóa SVG không chỉ có thể trợ giúp về màu sắc mà còn loại bỏ thông tin dư thừa khỏi SVG.
Hãy cài đặt svgo :
npm i -D svgo
svgo
có các plugin tích hợp sẵn và một trong số đó là convertColors
, có thuộc tính currentColor: true
. Nếu chúng ta sử dụng đầu ra SVG này, nó sẽ thay thế các màu bằng currentcolor
. Đây là cách sử dụng svgo
cùng với convertColors
:
import { optimize } from 'svgo'; const output = optimize( '<svg viewBox="0 0 24 24"><path fill="#000" d="m15 5 4 4" /></svg>', { plugins: [ { name: 'convertColors', params: { currentColor: true, }, } ], } ) console.log(output);
Và đầu ra sẽ là:
<svg viewBox="0 0 24 24"><path fill="currentColor" d="m15 5 4 4"/></svg>
Hãy thêm svgo
vào tập lệnh kỳ diệu mà chúng ta đã viết ở phần trước:
// generateSvgSprite.ts import { globSync } from 'glob'; import fs from 'fs'; import { HTMLElement, parse } from 'node-html-parser'; import path from 'path'; import { Config as SVGOConfig, optimize } from 'svgo'; // import `optimize` function const svgoConfig: SVGOConfig = { plugins: [ { name: 'convertColors', params: { currentColor: true, }, } ], }; const svgFiles = globSync('src/icons/*.svg'); const symbols: string[] = []; svgFiles.forEach(file => { const code = fs.readFileSync(file, 'utf-8'); const result = optimize(code, svgoConfig).data; // here goes `svgo` magic with optimization const svgElement = parse(result).querySelector('svg') as HTMLElement; const symbolElement = parse('<symbol/>').querySelector('symbol') as HTMLElement; const fileName = path.basename(file, '.svg'); svgElement.childNodes.forEach(child => symbolElement.appendChild(child)); symbolElement.setAttribute('id', fileName); if (svgElement.attributes.viewBox) { symbolElement.setAttribute('viewBox', svgElement.attributes.viewBox); } symbols.push(symbolElement.toString()); }); const svgSprite = `<svg xmlns="http://www.w3.org/2000/svg">${symbols.join('')}</svg>`; fs.writeFileSync('public/sprite.svg', svgSprite);
Và chạy tập lệnh:
npx tsx generateSvgSprite.ts
Kết quả là, sprite SVG sẽ chứa các biểu tượng có currentColor
. Và những biểu tượng này có thể được sử dụng ở mọi nơi trong dự án với bất kỳ màu nào bạn muốn.
Chúng tôi có một tập lệnh và chúng tôi có thể chạy nó bất cứ khi nào chúng tôi muốn, nhưng sẽ hơi bất tiện khi chúng tôi phải sử dụng nó theo cách thủ công. Vì vậy, tôi đề xuất một số plugin có thể xem các tệp .svg
của chúng tôi và tạo các họa tiết SVG khi đang di chuyển:
vite-plugin-svg-spritemap (dành cho người dùng vite )
Đây là plugin của tôi về cơ bản chứa tập lệnh mà chúng tôi vừa tạo trong bài viết này. Plugin này đã bật tính năng thay thế currentColor
theo mặc định, vì vậy bạn có thể thiết lập plugin khá dễ dàng.
// vite.config.ts import svgSpritemap from 'vite-plugin-svg-spritemap'; export default defineConfig({ plugins: [ svgSpritemap({ pattern: 'src/icons/*.svg', filename: 'sprite.svg', }), ], });
svg-spritemap-webpack-plugin (dành cho người dùng webpack )
Tôi đã sử dụng plugin Webpack này cho đến khi chuyển sang Vite. Nhưng plugin này vẫn là giải pháp tốt nếu bạn đang sử dụng Webpack. Bạn nên kích hoạt chuyển đổi màu theo cách thủ công và nó sẽ trông như thế này:
// webpack.config.js const SVGSpritemapPlugin = require('svg-spritemap-webpack-plugin'); module.exports = { plugins: [ new SVGSpritemapPlugin('src/icons/*.svg', { output: { svgo: { plugins: [ { name: 'convertColors', params: { currentColor: true, }, }, ], }, filename: 'sprite.svg', }, }), ], }
Tôi sẽ cung cấp một ví dụ trong React , nhưng bạn có thể triển khai nó ở nơi bạn muốn vì nó chủ yếu là về HTML. Vì vậy, vì chúng ta có sprite.svg
trong thư mục bản dựng của mình, chúng ta có thể truy cập tệp sprite và tạo thành phần Icon
cơ bản:
const Icon: FC<{ name: string }> = ({ name }) => ( <svg> <use href={`/sprite.svg#${name}`} /> </svg> ); const App = () => { return <Icon name="pen" />; };
Vì vậy, tóm tắt mọi thứ, để ngăn chặn nhiều thao tác thủ công với các biểu tượng, chúng tôi:
Hiệu quả trong phát triển không chỉ là tiết kiệm thời gian; đó là việc giải phóng tiềm năng sáng tạo của chúng ta. Tự động hóa các tác vụ quan trọng như quản lý biểu tượng không chỉ là một phím tắt; đó là cánh cổng dẫn đến trải nghiệm viết mã mượt mà hơn, có tác động mạnh mẽ hơn. Và tiết kiệm thời gian cho những công việc thường ngày như vậy, bạn có thể tập trung vào các nhiệm vụ phức tạp hơn và phát triển nhanh hơn với tư cách là nhà phát triển.