Ver3.0 ๐จ SVG ์คํ๋ผ์ดํธ ๊ธฐ๋ฒ์ผ๋ก ์์ด์ฝ ์์คํ ์๋ฒฝํ๊ฒ ์ต์ ํํ๊ธฐ

๐จ SVG ์คํ๋ผ์ดํธ ๊ธฐ๋ฒ์ผ๋ก ์์ด์ฝ ์์คํ ์๋ฒฝํ๊ฒ ์ต์ ํํ๊ธฐ
์น์ฌ์ดํธ ์ฑ๋ฅ์ 200% ๋์ด์ฌ๋ฆฌ๋ ๋๋ํ ์์ด์ฝ ๊ด๋ฆฌ๋ฒ
๐์๋ ! ์น๋์์ธ ์์ ํ๋ค๊ฐ ์์ด์ฝ ํ์ผ ๋๋ฌธ์ ๋จธ๋ฆฌ ์ํ ๋ ๊ฒฝํ ์์ง? ์์ญ ๊ฐ์ ์์ด์ฝ์ ํ๋ํ๋ ๋ถ๋ฌ์ค๋ฉด์ ๋ก๋ฉ ์๋๊ฐ ๋๋ ค์ง๊ณ , ๊ด๋ฆฌ๋ ๋ณต์กํด์ง๊ณ ... ์ค๋์ ๊ทธ๋ฐ ๊ณ ๋ฏผ์ ํ ๋ฐฉ์ ํด๊ฒฐํด์ค SVG ์คํ๋ผ์ดํธ ๊ธฐ๋ฒ์ ๋ํด ์น๊ตฌ์ฒ๋ผ ํธํ๊ฒ ์ด์ผ๊ธฐํด๋ณผ๊ฒ! ๐
๐ค SVG ์คํ๋ผ์ดํธ๊ฐ ๋ญ๊ธธ๋?
๋จผ์ ๊ธฐ๋ณธ๋ถํฐ ์ง๊ณ ๋์ด๊ฐ์. SVG ์คํ๋ผ์ดํธ๋ ์ฝ๊ฒ ๋งํด์ ์ฌ๋ฌ ๊ฐ์ SVG ์์ด์ฝ์ ํ๋์ ํ์ผ์ ๋ชจ์๋์ ๊ฒ์ด์ผ. ๋ง์น ๊ฒ์ ์บ๋ฆญํฐ ์คํ๋ผ์ดํธ ์ํธ์ฒ๋ผ ๋ง์ด์ง! ๐ฎ
์์ ์๋ PNG๋ JPG ์ด๋ฏธ์ง ์คํ๋ผ์ดํธ๋ฅผ ๋ง์ด ์ผ๋๋ฐ, ์์ฆ์ SVG๊ฐ ๋์ธ์ผ. ์๋๊ณ ? SVG๋ ๋ฒกํฐ ๊ธฐ๋ฐ์ด๋ผ์ ์๋ฌด๋ฆฌ ํ๋ํด๋ ๊นจ์ง์ง ์๊ณ , ํ์ผ ํฌ๊ธฐ๋ ์๊ณ , ์์ ๋ณ๊ฒฝ๋ CSS๋ก ์์ ์์ฌ๋ก ํ ์ ์๊ฑฐ๋ ! โจ
๐ก ์ ๊น๋ง!
SVG(Scalable Vector Graphics)๋ XML ๊ธฐ๋ฐ์ ๋ฒกํฐ ์ด๋ฏธ์ง ํฌ๋งท์ด์ผ. ํฝ์ ์ด ์๋๋ผ ์ํ์ ๊ณต์์ผ๋ก ๊ทธ๋ฆผ์ ๊ทธ๋ฆฌ๊ธฐ ๋๋ฌธ์ ํด์๋์ ์๊ด์์ด ์ ๋ช ํ๊ฒ ๋ณด์ธ๋ค๋ ๊ฒ ์ต๊ณ ์ฅ์ ์ด์ง!
๐ ์ SVG ์คํ๋ผ์ดํธ๋ฅผ ์จ์ผ ํ ๊น?
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก SVG ์คํ๋ผ์ดํธ์ ์ฅ์ ๋ค์ ํํค์ณ๋ณด์! ๋ด๊ฐ ์ค์ ๋ก ํ๋ก์ ํธ์ ์ ์ฉํด๋ณด๋ฉด์ ๋๋ ์ ๋ค์ ์์งํ๊ฒ ๊ณต์ ํ ๊ฒ. ๐ช
โก์ฑ๋ฅ ์ต์ ํ์ ๋ํ์
๊ฐ์ฅ ํฐ ์ฅ์ ์ ์ญ์ HTTP ์์ฒญ ํ์ ๊ฐ์์ผ! ์์ด์ฝ 50๊ฐ๋ฅผ ๊ฐ๋ณ ํ์ผ๋ก ๋ถ๋ฌ์ค๋ฉด 50๋ฒ์ ์์ฒญ์ด ํ์ํ์ง๋ง, ์คํ๋ผ์ดํธ๋ก ๋ฌถ์ผ๋ฉด ๋จ 1๋ฒ์ด๋ฉด ๋์ด๊ฑฐ๋ . ํนํ HTTP/1.1 ํ๊ฒฝ์์๋ ๋์ ์ฐ๊ฒฐ ์ ์ ํ์ด ์์ด์ ์ด ์ฐจ์ด๊ฐ ์์ฒญ๋๊ฒ ์ฒด๊ฐ๋ผ.
์ค์ ๋ก ๋ด๊ฐ ์์ ํ ์ผํ๋ชฐ ์์ธํ์ด์ง์์ ํ ์คํธํด๋ดค๋๋ฐ, ๋ก๋ฉ ์๋๊ฐ ์ฝ 2.3์ด์์ 0.8์ด๋ก ๋จ์ถ๋์ด! ์ฌ์ฉ์ ๊ฒฝํ์ด ํ ๋ฌ๋ผ์ง๋๋ผ๊ณ . ๐ฏ
๐จ์ ์ง๋ณด์๊ฐ ์ ๋ง ํธํด์ ธ
์์ด์ฝ ํ๋ ์์ ํ๋ ค๊ณ ์ฌ๋ฌ ํ์ด์ง๋ฅผ ๋์๋ค๋๋ฉฐ ํ์ผ ๊ฒฝ๋ก ๋ฐ๊พธ๋ ์์ ์ ์ด์ ์๋ ~ ์คํ๋ผ์ดํธ ํ์ผ ํ๋๋ง ๊ด๋ฆฌํ๋ฉด ๋๋๊น ์์ ํจ์จ์ด ์์ฒญ ์ฌ๋ผ๊ฐ. ๋์์ธ ์์คํ ๊ตฌ์ถํ ๋๋ ํจ์ฌ ์ฒด๊ณ์ ์ผ๋ก ๊ด๋ฆฌํ ์ ์์ด!
๐พ์บ์ฑ ํจ์จ ๊ทน๋ํ
ํ๋์ ์คํ๋ผ์ดํธ ํ์ผ๋ง ๋ธ๋ผ์ฐ์ ์บ์์ ์ ์ฅ๋๋ฉด, ์ฌ์ดํธ ์ ์ฒด์์ ์ฌ์ฉํ๋ ๋ชจ๋ ์์ด์ฝ์ด ์บ์์์ ๋ฐ๋ก ๋ถ๋ฌ์์ ธ. ์ฌ๋ฐฉ๋ฌธ ์ฌ์ฉ์ํํ ๋ ๊ฑฐ์ ์ฆ์ ๋ก๋ฉ๋๋ ๋ง๋ฒ ๊ฐ์ ๊ฒฝํ์ ์ ์ฌํ ์ ์์ง! โจ
๐ญCSS๋ก ์์ ์์ฌ ์คํ์ผ๋ง
์ด๊ฒ ์ง์ง ๊ฐ๊ฟ์ธ๋ฐ, SVG๋ CSS๋ก ์์, ํฌ๊ธฐ, ์ ๋๋ฉ์ด์ ๊น์ง ์ ๋ถ ์ ์ดํ ์ ์์ด. ๋คํฌ๋ชจ๋ ๋์๋ ์ฝ๊ณ , ํธ๋ฒ ํจ๊ณผ๋ ๊ฐ๋จํ๊ฒ ์ถ๊ฐ ๊ฐ๋ฅํด. PNG ์ด๋ฏธ์ง์๋ค๋ฉด? ์์๋ณ๋ก ๋ฐ๋ก ํ์ผ ๋ง๋ค์ด์ผ ํ์ ๊ฑฐ์ผ... ๐ฑ
๐ ๏ธ SVG ์คํ๋ผ์ดํธ ๋ง๋๋ ๋ฐฉ๋ฒ
์ด๋ก ์ ์ด์ ์ถฉ๋ถํ๋๊น, ์ค์ ์ผ๋ก ๋ค์ด๊ฐ๋ณผ๊น? SVG ์คํ๋ผ์ดํธ๋ฅผ ๋ง๋๋ ๋ฐฉ๋ฒ์ ํฌ๊ฒ ์ธ ๊ฐ์ง์ผ. ํ๋์ฉ ์์ธํ ์์๋ณด์! ๐ง
์ฒ์์๋ ์ง์ ์์ผ๋ก ๋ง๋ค์ด๋ณด๋ ๊ฒ ์๋ฆฌ๋ฅผ ์ดํดํ๋ ๋ฐ ๋์์ด ๋ผ. ๋ณต์กํด ๋ณด์ด์ง๋ง ๊ตฌ์กฐ๋ ๋จ์ํด!
Step 1: ๊ธฐ๋ณธ ๊ตฌ์กฐ ๋ง๋ค๊ธฐ
SVG ์คํ๋ผ์ดํธ์ ๊ธฐ๋ณธ ๋ผ๋๋ ์ด๋ ๊ฒ ์๊ฒผ์ด:
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<symbol id="icon-home" viewBox="0 0 24 24">
<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>
</symbol>
<symbol id="icon-search" viewBox="0 0 24 24">
<path d="M15.5 14h-.79l-.28-.27A6.471 6.471 0 0 0 16 9.5 6.5 6.5 0 1 0 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/>
</symbol>
<symbol id="icon-user" viewBox="0 0 24 24">
<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
</symbol>
</svg>
๐ ์ฝ๋ ํด๋ถํ๊ธฐ
โข xmlns: SVG ๋ค์์คํ์ด์ค ์ ์ธ (ํ์!)
โข style="display:none;": ์คํ๋ผ์ดํธ ์์ฒด๋ ํ๋ฉด์ ์ ๋ณด์ด๊ฒ
โข <symbol>: ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์์ด์ฝ ์ ์
โข id: ๋์ค์ ์ฐธ์กฐํ ๊ณ ์ ์๋ณ์
โข viewBox: ์์ด์ฝ์ ์ขํ ์์คํ
(์ค์!)
Step 2: ์์ด์ฝ ์ฌ์ฉํ๊ธฐ
์ ์ํ ์์ด์ฝ์ ์ค์ ๋ก ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ด๋ ๊ฒ ๊ฐ๋จํด:
<svg class="icon">
<use href="#icon-home"></use>
</svg>
<svg class="icon">
<use href="#icon-search"></use>
</svg>
CSS๋ก ์คํ์ผ๋ง๋ ์์ ๋กญ๊ฒ!
.icon {
width: 24px;
height: 24px;
fill: currentColor; /* ๋ถ๋ชจ ์์์ color ์์ */
}
.icon:hover {
fill: #3498db;
transform: scale(1.1);
transition: all 0.3s ease;
}
์ค๋ฌด์์๋ ์๋์ผ๋ก ํ๋ํ๋ ๋ง๋ค ์๊ฐ์ด ์์์? ๊ทธ๋์ ์๋ํ ๋๊ตฌ๋ฅผ ์ฐ๋ ๊ฒ ํจ์ฌ ํจ์จ์ ์ด์ผ. ๋ด๊ฐ ์์ฃผ ์ฐ๋ ๋๊ตฌ๋ค์ ์๊ฐํ ๊ฒ!
๐ง svg-sprite (Node.js ํจํค์ง)
๊ฐ์ฅ ๋ง์ด ์ฐ์ด๋ ๋๊ตฌ ์ค ํ๋์ผ. ์ค์น๋ถํฐ ์ฌ์ฉ๊น์ง ๋ณด์ฌ์ค๊ฒ:
npm install svg-sprite --save-dev
์ค์ ํ์ผ (sprite-config.json):
{
"mode": {
"symbol": {
"dest": "dist",
"sprite": "sprite.svg",
"example": true
}
},
"shape": {
"id": {
"generator": "icon-%s"
},
"dimension": {
"maxWidth": 32,
"maxHeight": 32
}
}
}
์ด๋ ๊ฒ ์ค์ ํ๋ฉด icons ํด๋์ ๋ชจ๋ SVG ํ์ผ์ ์๋์ผ๋ก ์คํ๋ผ์ดํธ๋ก ๋ง๋ค์ด์ค! ๐
๐จ IcoMoon (์จ๋ผ์ธ ๋๊ตฌ)
์ฝ๋ฉ ์์ด ์น์์ ๋ฐ๋ก ๋ง๋ค ์ ์๋ ๋๊ตฌ์ผ. ํนํ ๋์์ด๋ ์น๊ตฌ๋คํํ ์ถ์ฒํด!
์ฌ์ฉ๋ฒ:
1. IcoMoon.io ์ ์
2. Import Icons ๋ฒํผ์ผ๋ก SVG ํ์ผ๋ค ์
๋ก๋
3. ํ์ํ ์์ด์ฝ ์ ํ
4. Generate SVG & More ํด๋ฆญ
5. ์คํ๋ผ์ดํธ ํ์ผ ๋ค์ด๋ก๋! ๐
โ๏ธ Webpack + svg-sprite-loader
Webpack ์ฐ๋ ํ๋ก์ ํธ๋ผ๋ฉด ์ด๊ฒ ์ต๊ณ ์ผ. ๋น๋ ํ๋ก์ธ์ค์ ์๋์ผ๋ก ํตํฉ๋ผ์ ๊ฐ๋ฐ ๊ฒฝํ์ด ์ ๋ง ์ข์์ ธ.
npm install svg-sprite-loader --save-dev
webpack.config.js ์ค์ :
module.exports = {
module: {
rules: [
{
test: /\.svg$/,
include: path.resolve(__dirname, 'src/icons'),
use: [
{
loader: 'svg-sprite-loader',
options: {
symbolId: 'icon-[name]'
}
}
]
}
]
}
};
์ด์ JavaScript์์ ์ด๋ ๊ฒ import๋ง ํ๋ฉด ์๋์ผ๋ก ์คํ๋ผ์ดํธ์ ์ถ๊ฐ๋ผ:
import './icons/home.svg';
import './icons/search.svg';
import './icons/user.svg';
๋ ๊ฑฐ์ ํ๋ก์ ํธ๋ ํน์ ์ํฌํ๋ก์ฐ๊ฐ ์๋ค๋ฉด Gulp๋ Grunt๋ฅผ ์ธ ์๋ ์์ด. ๊ฐ๋จํ Gulp ์์ ๋ฅผ ๋ณด์ฌ์ค๊ฒ:
const gulp = require('gulp');
const svgSprite = require('gulp-svg-sprite');
const config = {
mode: {
symbol: {
dest: 'sprite',
sprite: 'icons.svg',
example: true
}
}
};
gulp.task('sprites', function() {
return gulp.src('src/icons/**/*.svg')
.pipe(svgSprite(config))
.pipe(gulp.dest('dist'));
});
gulp.task('watch', function() {
gulp.watch('src/icons/**/*.svg', gulp.series('sprites'));
});
๐ ์ค์ ํ์ฉ ํจํด๊ณผ ๋ฒ ์คํธ ํ๋ํฐ์ค
์ด์ ์ค์ ํ๋ก์ ํธ์์ ์ด๋ป๊ฒ ํ์ฉํ๋์ง ์์๋ณด์! ๋ด๊ฐ ์ฌ๋ฌ ํ๋ก์ ํธ ๊ฒฝํํ๋ฉด์ ์ ๋ฆฌํ ๋ ธํ์ฐ๋ค์ด์ผ. ๐
์์ด์ฝ์ด ๋ง์์ง๋ฉด ์ด๋ฆ ์ง๋ ๊ฒ ์ง์ง ์ค์ํด. ์ผ๊ด๋ ๊ท์น์ด ์์ผ๋ฉด ๋์ค์ ์ฐพ๊ธฐ๋ ํ๋ค๊ณ ๊ด๋ฆฌ๋ ์ด๋ ค์์ ธ. ๋ด๊ฐ ์ฐ๋ ๊ท์น์ ์ด๋:
์ถ์ฒ ๋ค์ด๋ฐ ๊ท์น
โข icon-{category}-{name} ํ์ ์ฌ์ฉ
โข ์์: icon-social-facebook, icon-ui-close, icon-arrow-right
โข ์นดํ
๊ณ ๋ฆฌ๋ณ๋ก ๊ทธ๋ฃนํํ๋ฉด ๋์ค์ ์ฐพ๊ธฐ ์ฌ์!
โข ๋์ฌ๋ ๋งจ ๋ค์: icon-file-download (O) vs icon-download-file (X)
SVG ์คํ๋ผ์ดํธ์ ์ง์ง ํ์๋ CSS๋ก ์์์ ์ ์ดํ ์ ์๋ค๋ ๊ฑฐ์ผ! ๊ทผ๋ฐ ์ด๊ฒ๋ ์ ๋ต์ด ํ์ํด.
๋ฐฉ๋ฒ 1: currentColor ํ์ฉ (๊ฐ์ฅ ์ถ์ฒ! โญ)
SVG ๋ด๋ถ์์ fill="currentColor"๋ฅผ ์ฐ๋ฉด ๋ถ๋ชจ ์์์ color ์์ฑ์ ์์๋ฐ์. ์ด๊ฒ ์ ์ผ ์ ์ฐํด!
<symbol id="icon-heart" viewBox="0 0 24 24">
<path fill="currentColor" d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/>
</symbol>
์ฌ์ฉํ ๋:
<button style="color: #e74c3c;">
<svg class="icon">
<use href="#icon-heart"></use>
</svg>
์ข์์
</button>
๋ฐฉ๋ฒ 2: CSS ๋ณ์๋ก ํ ๋ง ๊ด๋ฆฌ
๋คํฌ๋ชจ๋๋ ์ฌ๋ฌ ํ ๋ง๋ฅผ ์ง์ํ๋ค๋ฉด CSS ๋ณ์๊ฐ ์งฑ์ด์ผ!
:root {
--icon-primary: #3498db;
--icon-secondary: #95a5a6;
--icon-danger: #e74c3c;
}
[data-theme="dark"] {
--icon-primary: #5dade2;
--icon-secondary: #bdc3c7;
--icon-danger: #ec7063;
}
.icon-primary { fill: var(--icon-primary); }
.icon-secondary { fill: var(--icon-secondary); }
.icon-danger { fill: var(--icon-danger); }
๋ชจ๋ฐ์ผ๋ถํฐ ๋ฐ์คํฌํฑ๊น์ง ๋ค์ํ ํ๋ฉด์์ ์์ด์ฝ์ด ์ ์ ํ ํฌ๊ธฐ๋ก ๋ณด์ฌ์ผ ํ์์? ์ด๋ ๊ฒ ํด๋ด:
.icon {
width: 1.5rem; /* ๊ธฐ๋ณธ ํฌ๊ธฐ */
height: 1.5rem;
display: inline-block;
vertical-align: middle;
}
.icon-sm { width: 1rem; height: 1rem; }
.icon-lg { width: 2rem; height: 2rem; }
.icon-xl { width: 3rem; height: 3rem; }
/* ๋ฐ์ํ */
@media (max-width: 768px) {
.icon { width: 1.25rem; height: 1.25rem; }
}
@media (min-width: 1200px) {
.icon { width: 1.75rem; height: 1.75rem; }
}
โ ๏ธ ์ฃผ์์ฌํญ
์คํ๋ผ์ดํธ ํ์ผ์ด ๋๋ฌด ์ปค์ง๋ฉด ์คํ๋ ค ์ญํจ๊ณผ์ผ! ์ผ๋ฐ์ ์ผ๋ก 100KB ์ดํ๋ก ์ ์งํ๋ ๊ฒ ์ข์. ์์ด์ฝ์ด ๋๋ฌด ๋ง๋ค๋ฉด ํ์ด์ง๋ณ๋ก ์คํ๋ผ์ดํธ๋ฅผ ๋๋๋ ๊ฒ๋ ๋ฐฉ๋ฒ์ด์ผ.
์ต์ ํ ์ฒดํฌ๋ฆฌ์คํธ:
โ
SVG ํ์ผ ์ต์ ํ ๋๊ตฌ ์ฌ์ฉ (SVGO ์ถ์ฒ)
โ
๋ถํ์ํ ๋ฉํ๋ฐ์ดํฐ ์ ๊ฑฐ
โ
์์์ ์๋ฆฟ์ ์ค์ด๊ธฐ (2-3์๋ฆฌ๋ฉด ์ถฉ๋ถ)
โ
์ค๋ณต ๊ฒฝ๋ก ์ ๊ฑฐ
โ
Gzip ์์ถ ํ์ฑํ
โ
CDN ํ์ฉ ๊ณ ๋ ค
โ
ํฌ๋ฆฌํฐ์ปฌ ์์ด์ฝ์ ์ธ๋ผ์ธ์ผ๋ก ์ฝ์
SVGO ์ฌ์ฉ ์์:
npm install -g svgo
svgo input.svg -o output.svg
# ๋๋ ํด๋ ์ ์ฒด
svgo -f ./icons -o ./icons-optimized
๐ ๋ค์ํ ๋ก๋ฉ ๋ฐฉ๋ฒ ๋น๊ต
SVG ์คํ๋ผ์ดํธ๋ฅผ ํ์ด์ง์ ๋ก๋ํ๋ ๋ฐฉ๋ฒ๋ ์ฌ๋ฌ ๊ฐ์ง์ผ. ๊ฐ๊ฐ ์ฅ๋จ์ ์ด ์์ผ๋๊น ์ํฉ์ ๋ง๊ฒ ์ ํํด์ผ ํด! ๐
| ๋ฐฉ๋ฒ | ์ฅ์ | ๋จ์ | ์ถ์ฒ ์ํฉ |
|---|---|---|---|
| ์ธ๋ผ์ธ ์ฝ์ | โข ์ถ๊ฐ HTTP ์์ฒญ ์์ โข ์ฆ์ ๋ ๋๋ง โข ์บ์ ๋ถํ์ |
โข HTML ํฌ๊ธฐ ์ฆ๊ฐ โข ํ์ด์ง๋ง๋ค ์ค๋ณต โข ์ ์ง๋ณด์ ์ด๋ ค์ |
ํฌ๋ฆฌํฐ์ปฌ ์์ด์ฝ ์์ |
| ์ธ๋ถ ํ์ผ | โข ์บ์ฑ ๊ฐ๋ฅ โข HTML ๊น๋ โข ์ฌ์ฌ์ฉ ์ฉ์ด |
โข HTTP ์์ฒญ 1ํ โข ์ด๊ธฐ ๋ก๋ฉ ์ฝ๊ฐ ์ง์ฐ |
์ผ๋ฐ์ ์ธ ๊ฒฝ์ฐ (์ถ์ฒ!) |
| Ajax ๋ก๋ | โข ๋์ ๋ก๋ฉ โข ์กฐ๊ฑด๋ถ ๋ก๋ ๊ฐ๋ฅ |
โข JavaScript ํ์ โข ๋ณต์ก๋ ์ฆ๊ฐ |
SPA, ๋์ฉ๋ ์คํ๋ผ์ดํธ |
| Data URI | โข HTTP ์์ฒญ ์์ โข CSS์ ํฌํจ ๊ฐ๋ฅ |
โข Base64 ์ธ์ฝ๋ฉ์ผ๋ก ํฌ๊ธฐ ์ฆ๊ฐ โข ์บ์ฑ ํจ์จ ๋ฎ์ |
๋งค์ฐ ์์ ์์ด์ฝ |
ํ์ด์ง ๋ก๋ ์ฆ์ ๋ณด์ฌ์ผ ํ๋ ์ค์ํ ์์ด์ฝ๋ค์ HTML์ ์ง์ ๋ฃ๋ ๊ฒ ์ข์:
<!DOCTYPE html>
<html>
<head>
<!-- ํค๋ ๋ด์ฉ -->
</head>
<body>
<!-- ํ์ด์ง ์์ ๋ถ๋ถ์ ์คํ๋ผ์ดํธ ์ฝ์
-->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none;">
<symbol id="icon-menu" viewBox="0 0 24 24">
<path d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/>
</symbol>
<!-- ๊ธฐํ ํฌ๋ฆฌํฐ์ปฌ ์์ด์ฝ๋ค -->
</svg>
<!-- ์ค์ ์ปจํ
์ธ -->
<nav>
<button>
<svg class="icon"><use href="#icon-menu"></use></svg>
</button>
</nav>
</body>
</html>
๊ฐ์ฅ ์ผ๋ฐ์ ์ด๊ณ ์ถ์ฒํ๋ ๋ฐฉ๋ฒ์ด์ผ. ์บ์ฑ๋ ๋๊ณ ๊ด๋ฆฌ๋ ํธํด!
<!-- HTML -->
<svg class="icon">
<use href="/assets/sprites.svg#icon-home"></use>
</svg>
๐จ IE ํธํ์ฑ ์ด์
Internet Explorer๋ ์ธ๋ถ SVG ์ฐธ์กฐ๋ฅผ ์ง์ํ์ง ์์. IE ์ง์์ด ํ์ํ๋ค๋ฉด svg4everybody ๊ฐ์ ํด๋ฆฌํ์ ์ฌ์ฉํด์ผ ํด. ํ์ง๋ง 2023๋ ๊ธฐ์ค์ผ๋ก IE๋ ๊ฑฐ์ ์ฌ์ฅ๋์ผ๋ ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๊ฑฑ์ ์ ํด๋ ๋ผ! ๐
SPA(Single Page Application)๋ ์กฐ๊ฑด๋ถ ๋ก๋ฉ์ด ํ์ํ ๋ ์ ์ฉํด:
// Vanilla JavaScript
fetch('/assets/sprites.svg')
.then(response => response.text())
.then(data => {
const div = document.createElement('div');
div.style.display = 'none';
div.innerHTML = data;
document.body.insertBefore(div, document.body.childNodes[0]);
});
// ๋๋ async/await
async function loadSprites() {
const response = await fetch('/assets/sprites.svg');
const svgText = await response.text();
const container = document.createElement('div');
container.style.display = 'none';
container.innerHTML = svgText;
document.body.prepend(container);
}
loadSprites();
React์์๋ ์ด๋ ๊ฒ:
import { useEffect } from 'react';
function App() {
useEffect(() => {
fetch('/sprites.svg')
.then(res => res.text())
.then(svg => {
const div = document.createElement('div');
div.style.display = 'none';
div.innerHTML = svg;
document.body.insertBefore(div, document.body.firstChild);
});
}, []);
return (
<div>
<svg className="icon">
<use href="#icon-home" />
</svg>
</div>
);
}
๐ฏ ์ค์ ํ๋ก์ ํธ ์ ์ฉ ์ฌ๋ก
์ด๋ก ๋ง ์๋ฉด ๋ญํด, ์ค์ ์์ ๋ชป ์ฐ๋ฉด! ๋ด๊ฐ ์ค์ ๋ก ์์ ํ ํ๋ก์ ํธ๋ค์ ์ฌ๋ก๋ฅผ ๊ณต์ ํ ๊ฒ. ๐
๐ฆ ํ๋ก์ ํธ ๋ฐฐ๊ฒฝ
์ผํ๋ชฐ ์์ธํ์ด์ง์ ์ ํ ์ ๋ณด, ๋ฆฌ๋ทฐ, ๋ฐฐ์ก ์ ๋ณด ๋ฑ์ ํ์ํ๋ ์์ด์ฝ์ด ๋ฌด๋ ค 80๊ฐ๋ ์์์ด. ๊ฐ๊ฐ ๊ฐ๋ณ ํ์ผ๋ก ๋ก๋ํ๋๊น ๋ชจ๋ฐ์ผ์์ ๋ก๋ฉ์ด ๋๋ฌด ๋๋ ธ์ง.
๐ง ์ ์ฉ ๋ฐฉ๋ฒ
1. ๋ชจ๋ ์์ด์ฝ์ ์นดํ
๊ณ ๋ฆฌ๋ณ๋ก ๋ถ๋ฅ (UI, ์ ํ, ๋ฐฐ์ก, ์์
๋ฑ)
2. SVGO๋ก ์ต์ ํ ํ svg-sprite๋ก ํตํฉ
3. ํฌ๋ฆฌํฐ์ปฌ ์์ด์ฝ 10๊ฐ๋ ์ธ๋ผ์ธ, ๋๋จธ์ง๋ ์ธ๋ถ ํ์ผ
4. CSS ๋ณ์๋ก ํ
๋ง ์์ ๊ด๋ฆฌ
๐ ๊ฒฐ๊ณผ
โข HTTP ์์ฒญ: 80ํ โ 1ํ (98.75% ๊ฐ์!)
โข ์ด ํ์ผ ํฌ๊ธฐ: 245KB โ 38KB (84.5% ๊ฐ์)
โข ํ์ด์ง ๋ก๋ฉ ์๊ฐ: 2.3์ด โ 0.8์ด
โข Lighthouse ์ฑ๋ฅ ์ ์: 67์ โ 94์ ๐
๐ ํ๋ก์ ํธ ๋ฐฐ๊ฒฝ
๊ด๋ฆฌ์ ๋์๋ณด๋์ ์ฐจํธ, ํต๊ณ, ์ค์ ๋ฑ ๋ค์ํ ๊ธฐ๋ฅ์ด ์์ด์ ์์ด์ฝ ๊ด๋ฆฌ๊ฐ ์ ๋ง ๋ณต์กํ์ด. ๋์์ด๋๊ฐ ์์ด์ฝ ํ๋ ์์ ํ๋ฉด ๊ฐ๋ฐ์๊ฐ ์ฌ๋ฌ ํ์ผ์ ์ฐพ์๋ค๋๋ฉฐ ๊ต์ฒดํด์ผ ํ์ง.
๐ง ์ ์ฉ ๋ฐฉ๋ฒ
1. Figma์์ ์์ด์ฝ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ตฌ์ถ
2. Figma API๋ก ์๋ export ์คํฌ๋ฆฝํธ ์์ฑ
3. GitHub Actions๋ก ์๋ ๋น๋ ํ์ดํ๋ผ์ธ ๊ตฌ์ฑ
4. Storybook์ผ๋ก ์์ด์ฝ ์นดํ๋ก๊ทธ ๋ฌธ์ํ
๐ ๊ฒฐ๊ณผ
โข ์์ด์ฝ ์
๋ฐ์ดํธ ์๊ฐ: 30๋ถ โ 2๋ถ
โข ๋์์ด๋-๊ฐ๋ฐ์ ์ปค๋ฎค๋์ผ์ด์
๋น์ฉ 90% ๊ฐ์
โข ์ผ๊ด๋ ๋์์ธ ์์คํ
ํ๋ฆฝ
โข ์ ๊ท ํ์ ์จ๋ณด๋ฉ ์๊ฐ ๋จ์ถ
ํนํ ๋ ๋ฒ์งธ ์ฌ๋ก์์ ์ด ์๋ํ ์คํฌ๋ฆฝํธ๊ฐ ์ ๋ง ์ ์ฉํ์ด. ์ฝ๋ ๊ณต์ ํ ๊ฒ:
// scripts/build-sprites.js
const fs = require('fs');
const path = require('path');
const SVGSpriter = require('svg-sprite');
const glob = require('glob');
const config = {
mode: {
symbol: {
dest: 'dist',
sprite: 'icons.svg',
example: {
dest: 'demo.html'
}
}
},
shape: {
id: {
generator: 'icon-%s'
},
transform: ['svgo']
},
svg: {
xmlDeclaration: false,
doctypeDeclaration: false
}
};
const spriter = new SVGSpriter(config);
// ๋ชจ๋ SVG ํ์ผ ์ฐพ๊ธฐ
glob('src/icons/**/*.svg', (err, files) => {
if (err) throw err;
files.forEach(file => {
spriter.add(
path.resolve(file),
null,
fs.readFileSync(file, 'utf-8')
);
});
// ์คํ๋ผ์ดํธ ์์ฑ
spriter.compile((error, result) => {
if (error) throw error;
for (const mode in result) {
for (const resource in result[mode]) {
fs.mkdirSync(path.dirname(result[mode][resource].path), { recursive: true });
fs.writeFileSync(result[mode][resource].path, result[mode][resource].contents);
}
}
console.log('โ
์คํ๋ผ์ดํธ ์์ฑ ์๋ฃ!');
});
});
package.json์ ์คํฌ๋ฆฝํธ ์ถ๊ฐ:
{
"scripts": {
"build:icons": "node scripts/build-sprites.js",
"watch:icons": "nodemon --watch src/icons --exec npm run build:icons"
}
}
๐จ ๋์์ธ ์์คํ ๊ณผ์ ํตํฉ
SVG ์คํ๋ผ์ดํธ๋ ๋จ์ํ ์ฑ๋ฅ ์ต์ ํ ๋๊ตฌ๊ฐ ์๋์ผ. ์ ๋๋ก ๋ ๋์์ธ ์์คํ ์ ํต์ฌ ์์๊ฐ ๋ ์ ์์ด! ๐ฏ
์ฒด๊ณ์ ์ธ ์์ด์ฝ ์์คํ ์ ๋ง๋ค๋ ค๋ฉด ์ด๋ฐ ์์๋ค์ด ํ์ํด:
๐๏ธ ์์ด์ฝ ์์คํ ๊ตฌ์ฑ ์์
1. ์ผ๊ด๋ ๊ทธ๋ฆฌ๋ ์์คํ
: 24x24, 32x32 ๋ฑ ์ ํด์ง ํฌ๊ธฐ
2. ์คํธ๋กํฌ ๋๊ป ํต์ผ: ๋ณดํต 1.5px ๋๋ 2px
3. ์คํ์ผ ๊ฐ์ด๋: Outlined, Filled, Rounded ๋ฑ
4. ๋ค์ด๋ฐ ๊ท์น: ์นดํ
๊ณ ๋ฆฌ-์ด๋ฆ-๋ณํ ํ์
5. ์์ ํ๋ ํธ: Primary, Secondary, Accent ๋ฑ
6. ๋ฌธ์ํ: ์ฌ์ฉ๋ฒ๊ณผ ์์ ์ ๊ณต
์ค์ ๋ก ๊ตฌํํ ์์ด์ฝ ์ปดํฌ๋ํธ ์์๋ฅผ ๋ณด์ฌ์ค๊ฒ:
// React Icon Component
import React from 'react';
import PropTypes from 'prop-types';
const Icon = ({
name,
size = 'md',
color = 'currentColor',
className = '',
...props
}) => {
const sizeMap = {
sm: '16',
md: '24',
lg: '32',
xl: '48'
};
const dimension = sizeMap[size] || sizeMap.md;
return (
<svg
className={`icon icon-${size} ${className}`}
width={dimension}
height={dimension}
fill={color}
aria-hidden="true"
{...props}
>
<use href={`#icon-${name}`} />
</svg>
);
};
Icon.propTypes = {
name: PropTypes.string.isRequired,
size: PropTypes.oneOf(['sm', 'md', 'lg', 'xl']),
color: PropTypes.string,
className: PropTypes.string
};
export default Icon;
// ์ฌ์ฉ ์์
<Icon name="home" size="lg" color="#3498db" />
<Icon name="search" />
<Icon name="user" size="sm" className="profile-icon" />
Vue.js ๋ฒ์ ๋ ๋ง๋ค์ด๋ดค์ด:
<!-- Icon.vue -->
<template>
<svg
:class="['icon', `icon-${size}`, className]"
:width="dimension"
:height="dimension"
:fill="color"
aria-hidden="true"
v-bind="$attrs"
>
<use :href="`#icon-${name}`" />
</svg>
</template>
<script>
export default {
name: 'Icon',
props: {
name: {
type: String,
required: true
},
size: {
type: String,
default: 'md',
validator: value => ['sm', 'md', 'lg', 'xl'].includes(value)
},
color: {
type: String,
default: 'currentColor'
},
className: {
type: String,
default: ''
}
},
computed: {
dimension() {
const sizeMap = { sm: '16', md: '24', lg: '32', xl: '48' };
return sizeMap[this.size];
}
}
};
</script>
<!-- ์ฌ์ฉ ์์ -->
<Icon name="home" size="lg" color="#3498db" />
ํ์๋ค์ด ์ฝ๊ฒ ์์ด์ฝ์ ์ฐพ๊ณ ์ฌ์ฉํ ์ ์๋๋ก Storybook์ผ๋ก ์นดํ๋ก๊ทธ๋ฅผ ๋ง๋ค์ด๋ดค์ด:
// Icon.stories.js
import Icon from './Icon';
export default {
title: 'Components/Icon',
component: Icon,
argTypes: {
name: {
control: 'select',
options: ['home', 'search', 'user', 'heart', 'settings']
},
size: {
control: 'select',
options: ['sm', 'md', 'lg', 'xl']
},
color: {
control: 'color'
}
}
};
const Template = (args) => <Icon {...args} />;
export const Default = Template.bind({});
Default.args = {
name: 'home',
size: 'md'
};
export const AllIcons = () => (
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(100px, 1fr))', gap: '20px' }}>
{['home', 'search', 'user', 'heart', 'settings', 'menu', 'close', 'arrow-left', 'arrow-right'].map(icon => (
<div key={icon} style={{ textAlign: 'center' }}>
<Icon name={icon} size="lg" />
<p style={{ fontSize: '12px', marginTop: '8px' }}>{icon}</p>
</div>
))}
</div>
);
export const Sizes = () => (
<div style={{ display: 'flex', alignItems: 'center', gap: '20px' }}>
<Icon name="home" size="sm" />
<Icon name="home" size="md" />
<Icon name="home" size="lg" />
<Icon name="home" size="xl" />
</div>
);
๐ ์์ฃผ ๋ง๋๋ ๋ฌธ์ ์ ํด๊ฒฐ๋ฒ
์ค์ ์์ ๋ถ๋ชํ๋ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์ ๋ฆฌํด๋ดค์ด. ๋ด๊ฐ ์ฝ์งํ๋ฉด์ ๋ฐฐ์ด ๊ฒ๋ค์ด๋๊น ์ฐธ๊ณ ํด! ๐
โ ๋ฌธ์ 1: ์์ด์ฝ์ด ์ ๋ณด์ฌ์!
์์ธ: ์คํ๋ผ์ดํธ ํ์ผ์ด ๋ก๋๋๊ธฐ ์ ์ use ํ๊ทธ๊ฐ ์คํ๋จ
ํด๊ฒฐ: ์คํ๋ผ์ดํธ๋ฅผ HTML ์๋จ์ ์ธ๋ผ์ธ์ผ๋ก ๋ฃ๊ฑฐ๋, DOMContentLoaded ์ด๋ฒคํธ ํ์ ๋ ๋๋ง
// ํด๊ฒฐ ๋ฐฉ๋ฒ 1: ์ธ๋ผ์ธ ์ฝ์
<body>
<!-- ๋งจ ์์ ์คํ๋ผ์ดํธ -->
<svg style="display:none;">...</svg>
<!-- ๋๋จธ์ง ์ปจํ
์ธ -->
</body>
// ํด๊ฒฐ ๋ฐฉ๋ฒ 2: ๋ก๋ ๋๊ธฐ
document.addEventListener('DOMContentLoaded', () => {
// ์์ด์ฝ ๋ ๋๋ง ์ฝ๋
});
โ ๋ฌธ์ 2: CSS๋ก ์์ ๋ณ๊ฒฝ์ด ์ ๋ผ์!
์์ธ: SVG ๋ด๋ถ์ fill ์์ฑ์ด ํ๋์ฝ๋ฉ๋์ด ์์
ํด๊ฒฐ: fill="currentColor"๋ก ๋ณ๊ฒฝํ๊ฑฐ๋ fill ์์ฑ ์ ๊ฑฐ
<!-- โ ๋์ ์ -->
<symbol id="icon-heart">
<path fill="#e74c3c" d="..."/>
</symbol>
<!-- โ
์ข์ ์ -->
<symbol id="icon-heart">
<path fill="currentColor" d="..."/>
</symbol>
<!-- CSS๋ก ์ ์ด -->
.icon-heart {
fill: #e74c3c;
}
.icon-heart:hover {
fill: #c0392b;
}
โ ๋ฌธ์ 3: ์์ด์ฝ ํฌ๊ธฐ๊ฐ ์ด์ํด์!
์์ธ: viewBox ์ค์ ์ด ์๋ชป๋์๊ฑฐ๋ preserveAspectRatio ๋ฌธ์
ํด๊ฒฐ: viewBox๋ฅผ ์ ํํ ์ค์ ํ๊ณ CSS๋ก ํฌ๊ธฐ ์ ์ด
<!-- viewBox ํ์ธ -->
<symbol id="icon-home" viewBox="0 0 24 24">
<!-- 24x24 ๊ทธ๋ฆฌ๋ ๊ธฐ์ค์ผ๋ก ๊ทธ๋ ค์ง ๊ฒฝ๋ก -->
</symbol>
<!-- CSS๋ก ํฌ๊ธฐ ์ ์ด -->
.icon {
width: 24px;
height: 24px;
display: inline-block;
}
<!-- ๋๋ em ๋จ์๋ก -->
.icon {
width: 1.5em;
height: 1.5em;
}
โ ๋ฌธ์ 4: ์คํ๋ผ์ดํธ ํ์ผ์ด ๋๋ฌด ์ปค์!
์์ธ: ์ต์ ํ๋์ง ์์ SVG ํ์ผ๋ค
ํด๊ฒฐ: SVGO๋ก ์ต์ ํํ๊ณ ๋ถํ์ํ ๋ฉํ๋ฐ์ดํฐ ์ ๊ฑฐ
// SVGO ์ค์ (.svgo.config.js)
module.exports = {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false, // viewBox ์ ์ง
cleanupIDs: {
minify: true
}
}
}
},
'removeDimensions', // width/height ์ ๊ฑฐ
'removeStyleElement', // style ํ๊ทธ ์ ๊ฑฐ
'removeScriptElement' // script ํ๊ทธ ์ ๊ฑฐ
]
};
โ ๋ฌธ์ 5: ๋คํฌ๋ชจ๋์์ ์์ด์ฝ์ด ์ ๋ณด์ฌ์!
์์ธ: ๊ณ ์ ๋ ์์ ์ฌ์ฉ
ํด๊ฒฐ: CSS ๋ณ์์ currentColor ํ์ฉ
:root {
--icon-color: #2c3e50;
}
[data-theme="dark"] {
--icon-color: #ecf0f1;
}
.icon {
fill: var(--icon-color);
}
<!-- ๋๋ currentColor -->
.icon {
fill: currentColor;
}
.dark-mode {
color: #ecf0f1;
}
๐ ๊ณ ๊ธ ํ ํฌ๋๊ณผ ํ
๊ธฐ๋ณธ์ ๋ง์คํฐํ๋ค๋ฉด ์ด์ ์ข ๋ ๊ณ ๊ธ ๊ธฐ์ ๋ค์ ๋ฐฐ์๋ณผ ์๊ฐ์ด์ผ! ํ๋ก ๊ฐ๋ฐ์๋ค์ด ์ฐ๋ ๊ฟํ๋ค์ ๊ณต์ ํ ๊ฒ. ๐
SVG๋ CSS ์ ๋๋ฉ์ด์ ๊ณผ ์ฐฐ๋ก๊ถํฉ์ด์ผ! ๊ฐ๋จํ ํธ๋ฒ ํจ๊ณผ๋ถํฐ ๋ณต์กํ ์ ๋๋ฉ์ด์ ๊น์ง ๊ฐ๋ฅํด:
/* ๊ธฐ๋ณธ ํธ๋ฒ ํจ๊ณผ */
.icon {
transition: all 0.3s ease;
}
.icon:hover {
transform: scale(1.1);
fill: #3498db;
}
/* ํ์ ์ ๋๋ฉ์ด์
*/
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.icon-loading {
animation: spin 1s linear infinite;
}
/* ํ์ค ํจ๊ณผ */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.icon-notification {
animation: pulse 2s ease-in-out infinite;
}
/* ๋ฐ์ด์ค ํจ๊ณผ */
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.icon-arrow-down {
animation: bounce 1s ease-in-out infinite;
}
๋ ๋ณต์กํ ์ ๋๋ฉ์ด์ ์ SVG ๋ด๋ถ ์์๋ฅผ ๊ฐ๋ณ์ ์ผ๋ก ์ ์ดํ ์๋ ์์ด:
<symbol id="icon-heart-animated" viewBox="0 0 24 24">
<path class="heart-outline" d="..." />
<path class="heart-fill" d="..." />
</symbol>
<style>
.heart-fill {
fill: #e74c3c;
transform-origin: center;
animation: heartbeat 1.5s ease-in-out infinite;
}
@keyframes heartbeat {
0%, 100% { transform: scale(1); }
10%, 30% { transform: scale(0.9); }
20%, 40% { transform: scale(1.1); }
}
</style>
๋๋ก๋ ์ฌ๋ฌ ์์์ ๊ฐ์ง ์์ด์ฝ์ด ํ์ํ ๋๊ฐ ์์ด. ์ด๋ด ๋๋ CSS ๋ณ์๋ฅผ ํ์ฉํ๋ฉด ๋ผ:
<symbol id="icon-flag" viewBox="0 0 24 24">
<path class="flag-pole" fill="var(--flag-pole-color, #7f8c8d)" d="..."/>
<path class="flag-cloth" fill="var(--flag-cloth-color, #e74c3c)" d="..."/>
</symbol>
<style>
.icon-flag-blue {
--flag-pole-color: #34495e;
--flag-cloth-color: #3498db;
}
.icon-flag-green {
--flag-pole-color: #27ae60;
--flag-cloth-color: #2ecc71;
}
</style>
<!-- ์ฌ์ฉ -->
<svg class="icon icon-flag-blue">
<use href="#icon-flag"></use>
</svg>
ํ๋ฉด ํฌ๊ธฐ์ ๋ฐ๋ผ ์์ด์ฝ ๋ํ ์ผ์ ์กฐ์ ํ๋ ๊ณ ๊ธ ๊ธฐ๋ฒ์ด์ผ:
<symbol id="icon-menu-responsive" viewBox="0 0 24 24">
<!-- ๋ชจ๋ฐ์ผ: ๋จ์ํ ํ๋ฒ๊ฑฐ ๋ฉ๋ด -->
<g class="mobile-only">
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="18" x2="21" y2="18" />
</g>
<!-- ๋ฐ์คํฌํฑ: ๋ํ
์ผํ ๋ฉ๋ด ์์ด์ฝ -->
<g class="desktop-only">
<rect x="2" y="5" width="20" height="2" rx="1" />
<rect x="2" y="11" width="20" height="2" rx="1" />
<rect x="2" y="17" width="20" height="2" rx="1" />
</g>
</symbol>
<style>
.mobile-only { display: block; }
.desktop-only { display: none; }
@media (min-width: 768px) {
.mobile-only { display: none; }
.desktop-only { display: block; }
}
</style>
์์ด์ฝ๋ ์ ๊ทผ์ฑ์ ๊ณ ๋ คํด์ผ ํด! ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์๋ฅผ ์ํ ํ๋ค์ด์ผ:
<!-- ์ฅ์์ฉ ์์ด์ฝ (์๋ฏธ ์์) -->
<svg class="icon" aria-hidden="true" focusable="false">
<use href="#icon-star"></use>
</svg>
<!-- ์๋ฏธ ์๋ ์์ด์ฝ -->
<svg class="icon" role="img" aria-label="๊ฒ์">
<use href="#icon-search"></use>
</svg>
<!-- ๋ฒํผ ์์ ์์ด์ฝ -->
<button aria-label="๋ฉ๋ด ์ด๊ธฐ">
<svg class="icon" aria-hidden="true">
<use href="#icon-menu"></use>
</svg>
</button>
<!-- ๋งํฌ ์์ ์์ด์ฝ -->
<a href="/profile" aria-label="ํ๋กํ ํ์ด์ง๋ก ์ด๋">
<svg class="icon" aria-hidden="true">
<use href="#icon-user"></use>
</svg>
</a>
๋ชจ๋ ์์ด์ฝ์ ํ ๋ฒ์ ๋ก๋ํ ํ์๋ ์์ด. ํ์ํ ๋๋ง ๋ก๋ํ๋ ๋ฐฉ๋ฒ:
// icon-loader.js
class IconLoader {
constructor() {
this.loaded = new Set();
this.loading = new Map();
}
async load(spritePath) {
if (this.loaded.has(spritePath)) {
return;
}
if (this.loading.has(spritePath)) {
return this.loading.get(spritePath);
}
const promise = fetch(spritePath)
.then(res => res.text())
.then(svg => {
const container = document.createElement('div');
container.style.display = 'none';
container.innerHTML = svg;
document.body.insertBefore(container, document.body.firstChild);
this.loaded.add(spritePath);
this.loading.delete(spritePath);
});
this.loading.set(spritePath, promise);
return promise;
}
}
const iconLoader = new IconLoader();
// ์ฌ์ฉ ์์
async function showUserProfile() {
await iconLoader.load('/sprites/user-icons.svg');
// ์ด์ user ๊ด๋ จ ์์ด์ฝ ์ฌ์ฉ ๊ฐ๋ฅ
}
async function showCharts() {
await iconLoader.load('/sprites/chart-icons.svg');
// ์ด์ chart ๊ด๋ จ ์์ด์ฝ ์ฌ์ฉ ๊ฐ๋ฅ
}
๐ผ ์ฌ๋ฅ๋ท์์ SVG ์คํ๋ผ์ดํธ ์ ๋ฌธ๊ฐ ์ฐพ๊ธฐ
์ฌ๊ธฐ๊น์ง ์ฝ์๋ค๋ฉด SVG ์คํ๋ผ์ดํธ์ ๋ํด ๊ฝค ๋ง์ด ์๊ฒ ๋์ ๊ฑฐ์ผ! ํ์ง๋ง ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ค๋ ๋ง๋งํ๋ค๋ฉด? ๐ค
๐ฏ ์ด๋ฐ ๋ถ๋ค๊ป ์ถ์ฒํด์
โข ์น์ฌ์ดํธ ์ฑ๋ฅ ๊ฐ์ ์ด ์๊ธํ ๋ถ
โข ๋์์ธ ์์คํ
์ ๊ตฌ์ถํ๊ณ ์ถ์ ๋ถ
โข ์์ด์ฝ ๊ด๋ฆฌ๊ฐ ๋ณต์กํด์ ๊ณ ๋ฏผ์ธ ๋ถ
โข ์๋ํ ํ์ดํ๋ผ์ธ์ ๊ตฌ์ถํ๊ณ ์ถ์ ๋ถ
์ฌ๋ฅ๋ท์์๋ SVG ์ต์ ํ, ์คํ๋ผ์ดํธ ๊ตฌ์ถ, ๋์์ธ ์์คํ ์ค๊ณ ๋ฑ ๋ค์ํ ์ ๋ฌธ๊ฐ๋ค์ ๋ง๋ ์ ์์ด. ์น๋์์ธ๊ณผ ํ๋ก ํธ์๋ ๊ฐ๋ฐ ๊ฒฝํ์ด ํ๋ถํ ์ ๋ฌธ๊ฐ๋ค์ด ๋น์ ์ ํ๋ก์ ํธ๋ฅผ ๋์์ค ์ค๋น๊ฐ ๋์ด ์์ง! ๐ช
ํนํ ์ด์ปค๋จธ์ค ์์ธํ์ด์ง๋ ๋์๋ณด๋ ๊ฐ์ ๋ณต์กํ ํ๋ก์ ํธ๋ ๊ฒฝํ ์๋ ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ์ผ๋ฉด ์ํ์ฐฉ์ค๋ฅผ ํฌ๊ฒ ์ค์ผ ์ ์์ด. ์ฌ๋ฅ๋ท์์ ํฌํธํด๋ฆฌ์ค๋ฅผ ํ์ธํ๊ณ ๋ฆฌ๋ทฐ๋ฅผ ์ฐธ๊ณ ํด์ ๋ฑ ๋ง๋ ์ ๋ฌธ๊ฐ๋ฅผ ์ฐพ์๋ณด๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ! ๐
๐ ํ์ต ๋ฆฌ์์ค์ ๋๊ตฌ ๋ชจ์
๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ด ๋ฆฌ์์ค๋ค์ ์ถ์ฒํด! ๋ด๊ฐ ์ค์ ๋ก ๋์๋ฐ์๋ ๊ฒ๋ค๋ง ๊ณจ๋ผ๋ดค์ด. ๐
๊ณต์ ๋ฌธ์
โข MDN Web Docs - SVG Tutorial
โข W3C SVG Specification
โข CSS-Tricks - A Complete Guide to SVG Sprites
๋ธ๋ก๊ทธ์ ์ํฐํด
โข Sara Soueidan's Blog (SVG ์ ๋ฌธ๊ฐ)
โข Smashing Magazine - SVG ๊ด๋ จ ๊ธ๋ค
โข CSS-Tricks - SVG ์น์
| ๋๊ตฌ | ์ฉ๋ | ํน์ง |
|---|---|---|
| SVGO | SVG ์ต์ ํ | ํ์ผ ํฌ๊ธฐ ์ต๋ 70% ๊ฐ์ |
| svg-sprite | ์คํ๋ผ์ดํธ ์์ฑ | ๋ค์ํ ๋ชจ๋ ์ง์ |
| IcoMoon | ์จ๋ผ์ธ ์์ฑ๊ธฐ | GUI ๊ธฐ๋ฐ, ์ด๋ณด์ ์นํ์ |
| Figma | ๋์์ธ ๋๊ตฌ | SVG export ์ต์ ํ |
| Inkscape | ๋ฒกํฐ ํธ์ง | ๋ฌด๋ฃ ์คํ์์ค |
| SVGOMG | ์จ๋ผ์ธ ์ต์ ํ | ์ค์๊ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ |
์ฒ์๋ถํฐ ๋ง๋ค๊ธฐ ๋ถ๋ด์ค๋ฝ๋ค๋ฉด ์ด๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ ํ์ฉํด๋ด:
โข Heroicons: Tailwind CSS ํ์ด ๋ง๋ ๊น๋ํ ์์ด์ฝ
โข Feather Icons: ์ฌํํ๊ณ ์ผ๊ด๋ ๋์์ธ
โข Material Icons: ๊ตฌ๊ธ์ Material Design ์์ด์ฝ
โข Font Awesome: ๊ฐ์ฅ ๋ง์ด ์ฐ์ด๋ ์์ด์ฝ ์ธํธ
โข Ionicons: Ionic ํ๋ ์์ํฌ์ ์์ด์ฝ
โข Lucide: Feather Icons์ ์ปค๋ฎค๋ํฐ ํฌํฌ
์ด๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค๋ SVG ์คํ๋ผ์ดํธ๋ก ๋ณํํด์ ์ธ ์ ์์ด. ๋ผ์ด์ ์ค๋ง ํ์ธํ๋ฉด ๋ผ!
๐ฎ ๋ฏธ๋ ์ ๋ง๊ณผ ํธ๋ ๋
SVG ์คํ๋ผ์ดํธ ๊ธฐ์ ์ ๊ณ์ ์งํํ๊ณ ์์ด. ์์ผ๋ก ์ด๋ค ๋ณํ๊ฐ ์์์ง ์ดํด๋ณด์! ๐
HTTP/2๋ถํฐ๋ ๋ฉํฐํ๋ ์ฑ์ด ์ง์๋์ด์ ๋์ ์ฐ๊ฒฐ ์ ํ์ด ์์ด์ก์ด. ๊ทธ๋ผ ์คํ๋ผ์ดํธ๊ฐ ํ์ ์์๊น? ์๋์ผ! ์ฌ์ ํ ์ฅ์ ์ด ๋ง์:
โข ์บ์ฑ ํจ์จ์ฑ์ ์ฌ์ ํ ์ค์
โข ํ์ผ ์๊ฐ ์ ์์๋ก ๊ด๋ฆฌ๊ฐ ํธํจ
โข ์ด๊ธฐ ์ฐ๊ฒฐ ์ค๋ฒํค๋ ๊ฐ์
โข ๋ชจ๋ฐ์ผ ํ๊ฒฝ์์๋ ์ฌ์ ํ ์ ๋ฆฌ
์น ์ปดํฌ๋ํธ ๊ธฐ์ ์ด ๋ฐ์ ํ๋ฉด์ ์์ด์ฝ๋ ์ปดํฌ๋ํธํ๋๋ ์ถ์ธ์ผ:
// ์น ์ปดํฌ๋ํธ๋ก ๋ง๋ ์์ด์ฝ
class IconElement extends HTMLElement {
connectedCallback() {
const name = this.getAttribute('name');
const size = this.getAttribute('size') || 'md';
this.innerHTML = `
<svg class="icon icon-${size}">
<use href="#icon-${name}"></use>
</svg>
`;
}
}
customElements.define('app-icon', IconElement);
// ์ฌ์ฉ
<app-icon name="home" size="lg"></app-icon>
์ต๊ทผ์๋ AI๊ฐ ์์ด์ฝ์ ์๋์ผ๋ก ์์ฑํ๊ฑฐ๋ ์ต์ ํํด์ฃผ๋ ๋๊ตฌ๋ค๋ ๋์ค๊ณ ์์ด. ์์ผ๋ก ๋ ๋ฐ์ ํ ๋ถ์ผ์ผ!
๋คํฌ๋ชจ๋๊ฐ ํ์ค์ด ๋๋ฉด์ ์์ด์ฝ๋ ํ ๋ง์ ๋ง์ถฐ ์๋์ผ๋ก ๋ณํ๋ ์์คํ ์ด ์ค์ํด์ง๊ณ ์์ด:
@media (prefers-color-scheme: dark) {
.icon {
fill: var(--icon-color-dark);
}
}
@media (prefers-color-scheme: light) {
.icon {
fill: var(--icon-color-light);
}
}
โ ์ฒดํฌ๋ฆฌ์คํธ: SVG ์คํ๋ผ์ดํธ ๋์ ์ ํ์ธ์ฌํญ
ํ๋ก์ ํธ์ SVG ์คํ๋ผ์ดํธ๋ฅผ ๋์ ํ๊ธฐ ์ ์ ์ด๊ฒ๋ค์ ์ฒดํฌํด๋ด! ๐
๐ฏ ๊ธฐํ ๋จ๊ณ
โ ์์ด์ฝ ๊ฐ์์ ์ข
๋ฅ ํ์
โ ๋์์ธ ์์คํ
๊ฐ์ด๋ ํ์ธ
โ ๋ค์ด๋ฐ ์ปจ๋ฒค์
์ ์
โ ์์ ํ๋ ํธ ๊ฒฐ์
โ ํฌ๊ธฐ ๊ท๊ฒฉ ์ ์ (sm, md, lg ๋ฑ)
๐ ๏ธ ๊ฐ๋ฐ ๋จ๊ณ
โ ๋น๋ ๋๊ตฌ ์ ํ (Webpack, Gulp ๋ฑ)
โ ์ต์ ํ ๋๊ตฌ ์ค์ (SVGO)
โ ์คํ๋ผ์ดํธ ์์ฑ ์๋ํ
โ ์ปดํฌ๋ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ตฌ์ถ
โ ๋ฌธ์ํ (Storybook ๋ฑ)
๐จ ๋์์ธ ๋จ๊ณ
โ ์ผ๊ด๋ ๊ทธ๋ฆฌ๋ ์์คํ
์ฌ์ฉ
โ ์คํธ๋กํฌ ๋๊ป ํต์ผ
โ viewBox ์ ํํ ์ค์
โ ๋ถํ์ํ ์์ ์ ๊ฑฐ
โ ์์์ currentColor ์ฌ์ฉ
โก ์ฑ๋ฅ ๋จ๊ณ
โ ํ์ผ ํฌ๊ธฐ 100KB ์ดํ ์ ์ง
โ Gzip ์์ถ ํ์ฑํ
โ CDN ์ค์
โ ์บ์ฑ ์ ๋ต ์๋ฆฝ
โ ํฌ๋ฆฌํฐ์ปฌ ์์ด์ฝ ์ธ๋ผ์ธ ์ฒ๋ฆฌ
โฟ ์ ๊ทผ์ฑ ๋จ๊ณ
โ aria-label ์ ์ ํ ์ฌ์ฉ
โ ์ฅ์์ฉ ์์ด์ฝ์ aria-hidden
โ ํค๋ณด๋ ๋ค๋น๊ฒ์ด์
์ง์
โ ์คํฌ๋ฆฐ ๋ฆฌ๋ ํ
์คํธ
โ ์์ ๋๋น ํ์ธ
๐งช ํ ์คํธ ๋จ๊ณ
โ ๋ค์ํ ๋ธ๋ผ์ฐ์ ์์ ํ
์คํธ
โ ๋ชจ๋ฐ์ผ ๊ธฐ๊ธฐ ํ
์คํธ
โ ๋คํฌ๋ชจ๋ ํ
์คํธ
โ ์ฑ๋ฅ ์ธก์ (Lighthouse)
โ ์ ๊ทผ์ฑ ๊ฒ์ฌ (axe, WAVE)
๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ฌ๊ธฐ๊น์ง ์ฝ์๋ค๋ฉด ์ ๋ง ๋๋จํด! ๐ SVG ์คํ๋ผ์ดํธ์ ๋ํด ์ฒ์๋ถํฐ ๋๊น์ง ๋ค๋ค๋ดค์ด. ๊ธฐ๋ณธ ๊ฐ๋ ๋ถํฐ ์ค์ ํ์ฉ, ๊ณ ๊ธ ํ ํฌ๋, ๋ฌธ์ ํด๊ฒฐ๊น์ง ๋ชจ๋ ๊ฑธ ๋ด์ผ๋ ค๊ณ ๋ ธ๋ ฅํ์ด.
ํต์ฌ๋ง ๋ค์ ์ ๋ฆฌํ๋ฉด:
๐ฏ SVG ์คํ๋ผ์ดํธ์ ํต์ฌ ๊ฐ์น
1. ์ฑ๋ฅ ์ต์ ํ: HTTP ์์ฒญ ์ ๋ํญ ๊ฐ์
2. ์ ์ง๋ณด์์ฑ: ์ค์ ์ง์ค์ ๊ด๋ฆฌ๋ก ํจ์จ ์ฆ๊ฐ
3. ์ ์ฐ์ฑ: CSS๋ก ์์ ๋ก์ด ์คํ์ผ๋ง
4. ํ์ฅ์ฑ: ๋์์ธ ์์คํ
์ ํต์ฌ ์์
5. ์ ๊ทผ์ฑ: ๋ชจ๋ ์ฌ์ฉ์๋ฅผ ์ํ ๋ฐฐ๋ ค
์ฒ์์๋ ๋ณต์กํด ๋ณด์ผ ์ ์์ง๋ง, ํ ๋ฒ ์์คํ ์ ๊ตฌ์ถํด๋์ผ๋ฉด ์ ๋ง ํธํด. ํนํ ํ ํ๋ก์ ํธ์์๋ ๊ทธ ์ง๊ฐ๊ฐ ๋๋ฌ๋์ง! ๐
๊ธฐ์ตํด, ์๋ฒฝํ ์์คํ ์ ์ฒ์๋ถํฐ ๋ง๋ค ํ์๋ ์์ด. ์๊ฒ ์์ํด์ ์ ์ง์ ์ผ๋ก ๊ฐ์ ํด๋๊ฐ๋ฉด ๋ผ. ๋ด๊ฐ ์ฒ์ ์์ํ์ ๋๋ ์์ด์ฝ 10๊ฐ๋ก ์์ํ๊ฑฐ๋ ! ๐
์ด์ ๋น์ ์ฐจ๋ก์ผ! ์ค๋ ๋ฐฐ์ด ๋ด์ฉ์ ์ค์ ํ๋ก์ ํธ์ ์ ์ฉํด๋ณด๊ณ , ์ฑ๋ฅ์ด ์ผ๋ง๋ ๊ฐ์ ๋๋์ง ์ง์ ๊ฒฝํํด๋ด. ๋ถ๋ช ๋๋ ๊ฑฐ์ผ! โก
๊ถ๊ธํ ์ ์ด๋ ๋งํ๋ ๋ถ๋ถ์ด ์๋ค๋ฉด ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ์ ์ง๋ฌธํ๊ฑฐ๋, ์์ ๋งํ๋ฏ์ด ์ฌ๋ฅ๋ท์์ ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ. ํผ์ ๊ณ ๋ฏผํ์ง ๋ง๊ณ ํจ๊ป ์ฑ์ฅํ์! ๐ช
์, ์ด์ ์๋ํฐ๋ฅผ ์ด๊ณ ์ฒซ ๋ฒ์งธ SVG ์คํ๋ผ์ดํธ๋ฅผ ๋ง๋ค์ด๋ณผ ์๊ฐ์ด์ผ! ํ์ดํ ! ๐โจ
๐จ Happy Coding & Designing! ๐จ
SVG ์คํ๋ผ์ดํธ๋ก ๋ ๋น ๋ฅด๊ณ ์๋ฆ๋ค์ด ์น์ ๋ง๋ค์ด๋ด์!
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.