前端技术探索系列:HTML5 SVG 集成详解 🎨
开篇寄语 👋
前端开发者们,
在前五篇文章中,我们探讨了 HTML5 的多个特性。今天,让我们深入了解 SVG 的魅力,看看如何创建可缩放的矢量图形。
一、SVG 基础入门 ✨
1. SVG 语法基础
html
<!-- 内联 SVG -->
<svg width="200" height="200" viewBox="0 0 200 200">
<!-- 基础图形 -->
<circle cx="100" cy="100" r="50" fill="blue" />
<rect x="50" y="50" width="100" height="100" fill="red" opacity="0.5" />
<path d="M10 10 H 90 V 90 H 10 Z" fill="none" stroke="black" />
</svg>
2. 基础图形绘制
html
<svg width="400" height="400">
<!-- 矩形 -->
<rect
x="10" y="10"
width="100" height="50"
fill="blue"
stroke="black"
stroke-width="2"
rx="10" ry="10"
/>
<!-- 圆形 -->
<circle
cx="200" cy="50"
r="40"
fill="red"
stroke="darkred"
stroke-width="2"
/>
<!-- 椭圆 -->
<ellipse
cx="300" cy="50"
rx="60" ry="30"
fill="green"
/>
<!-- 线条 -->
<line
x1="10" y1="100"
x2="100" y2="150"
stroke="purple"
stroke-width="2"
/>
<!-- 多边形 -->
<polygon
points="200,100 250,150 150,150"
fill="orange"
/>
</svg>
3. 复用与组织
html
<!-- 定义可重用元素 -->
<svg>
<defs>
<!-- 渐变定义 -->
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:rgb(255,255,0);" />
<stop offset="100%" style="stop-color:rgb(255,0,0);" />
</linearGradient>
<!-- 符号定义 -->
<symbol id="star" viewBox="0 0 32 32">
<path d="M16 0 L20 12 L32 12 L22 20 L26 32 L16 24 L6 32 L10 20 L0 12 L12 12 Z" />
</symbol>
</defs>
<!-- 使用定义的元素 -->
<rect width="200" height="100" fill="url(#gradient)" />
<use href="#star" x="50" y="50" width="32" height="32" fill="gold" />
</svg>
二、SVG 动画技术 🎬
1. SMIL 动画
html
<svg width="300" height="300">
<circle cx="150" cy="150" r="50" fill="blue">
<!-- 位置动画 -->
<animateTransform
attributeName="transform"
type="translate"
values="0,0; 50,0; 0,0"
dur="2s"
repeatCount="indefinite"
/>
<!-- 颜色动画 -->
<animate
attributeName="fill"
values="blue;red;blue"
dur="3s"
repeatCount="indefinite"
/>
<!-- 路径动画 -->
<animateMotion
path="M 0 0 H 100 V 100 H 0 Z"
dur="4s"
repeatCount="indefinite"
/>
</circle>
</svg>
2. CSS 动画
html
<style>
.rotating-star {
animation: rotate 3s linear infinite;
transform-origin: center;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.pulsing-circle {
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
</style>
<svg width="200" height="200">
<use href="#star" class="rotating-star" x="50" y="50" width="100" height="100" />
<circle class="pulsing-circle" cx="100" cy="100" r="50" fill="purple" />
</svg>
3. JavaScript 动画
javascript
class SVGAnimator {
constructor(element) {
this.element = element;
this.animations = new Map();
}
// 添加动画
animate(properties, duration, easing = 'linear') {
const startTime = performance.now();
const initialValues = {};
// 获取初始值
for (const prop in properties) {
initialValues[prop] = parseFloat(this.element.getAttribute(prop));
}
const animation = {
startTime,
duration,
initialValues,
targetValues: properties,
easing
};
this.animations.set(animation, true);
this.startAnimation();
}
// 动画循环
startAnimation() {
if (this.animationFrame) return;
const animate = (currentTime) => {
let hasRunning = false;
this.animations.forEach((isRunning, animation) => {
if (!isRunning) return;
const elapsed = currentTime - animation.startTime;
const progress = Math.min(elapsed / animation.duration, 1);
if (progress < 1) {
hasRunning = true;
this.updateProperties(animation, progress);
} else {
this.animations.delete(animation);
}
});
if (hasRunning) {
this.animationFrame = requestAnimationFrame(animate);
} else {
this.animationFrame = null;
}
};
this.animationFrame = requestAnimationFrame(animate);
}
// 更新属性
updateProperties(animation, progress) {
for (const prop in animation.targetValues) {
const initial = animation.initialValues[prop];
const target = animation.targetValues[prop];
const current = initial + (target - initial) * progress;
this.element.setAttribute(prop, current);
}
}
}
三、SVG 交互实现 🖱️
1. 事件处理
javascript
class SVGInteraction {
constructor(svg) {
this.svg = svg;
this.elements = new Map();
this.setupEvents();
}
setupEvents() {
this.svg.addEventListener('click', this.handleClick.bind(this));
this.svg.addEventListener('mousemove', this.handleMouseMove.bind(this));
}
// 获取 SVG 坐标
getSVGPoint(event) {
const point = this.svg.createSVGPoint();
point.x = event.clientX;
point.y = event.clientY;
return point.matrixTransform(this.svg.getScreenCTM().inverse());
}
// 添加可交互元素
addInteractiveElement(element, handlers) {
this.elements.set(element, handlers);
element.addEventListener('mouseenter', () => {
if (handlers.hover) {
handlers.hover(element, true);
}
});
element.addEventListener('mouseleave', () => {
if (handlers.hover) {
handlers.hover(element, false);
}
});
}
handleClick(event) {
const point = this.getSVGPoint(event);
this.elements.forEach((handlers, element) => {
if (this.isPointInElement(point, element) && handlers.click) {
handlers.click(element);
}
});
}
handleMouseMove(event) {
const point = this.getSVGPoint(event);
this.elements.forEach((handlers, element) => {
if (this.isPointInElement(point, element) && handlers.move) {
handlers.move(element, point);
}
});
}
isPointInElement(point, element) {
const bbox = element.getBBox();
return (
point.x >= bbox.x &&
point.x <= bbox.x + bbox.width &&
point.y >= bbox.y &&
point.y <= bbox.y + bbox.height
);
}
}
2. 滤镜效果
html
<svg width="400" height="400">
<defs>
<!-- 高斯模糊 -->
<filter id="blur">
<feGaussianBlur stdDeviation="2" />
</filter>
<!-- 阴影效果 -->
<filter id="shadow">
<feDropShadow dx="2" dy="2" stdDeviation="2" />
</filter>
<!-- 发光效果 -->
<filter id="glow">
<feGaussianBlur stdDeviation="2" result="coloredBlur" />
<feMerge>
<feMergeNode in="coloredBlur" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
</defs>
<!-- 使用滤镜 -->
<circle cx="100" cy="100" r="50" fill="purple" filter="url(#glow)" />
</svg>
四、实践项目:动态 LOGO 生成器 🎯
javascript
class LogoGenerator {
constructor(container) {
this.container = container;
this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.setup();
}
setup() {
this.svg.setAttribute('width', '300');
this.svg.setAttribute('height', '300');
this.container.appendChild(this.svg);
// 定义滤镜和渐变
this.defineFilters();
this.defineGradients();
}
defineFilters() {
const defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
// 添加发光效果
const glowFilter = `
<filter id="logo-glow">
<feGaussianBlur stdDeviation="2" result="blur" />
<feFlood flood-color="rgba(0,0,255,0.3)" result="color" />
<feComposite in="color" in2="blur" operator="in" />
<feMerge>
<feMergeNode />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
`;
defs.innerHTML = glowFilter;
this.svg.appendChild(defs);
}
defineGradients() {
const defs = this.svg.querySelector('defs');
// 添加渐变
const gradient = `
<linearGradient id="logo-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" style="stop-color:#4CAF50" />
<stop offset="100%" style="stop-color:#2196F3" />
</linearGradient>
`;
defs.innerHTML += gradient;
}
// 生成 Logo
generateLogo(text, options = {}) {
const {
fontSize = 48,
fontFamily = 'Arial',
color = 'url(#logo-gradient)',
useGlow = true
} = options;
// 清空现有内容
while (this.svg.lastChild) {
this.svg.removeChild(this.svg.lastChild);
}
// 重新添加 defs
this.defineFilters();
this.defineGradients();
// 创建文本元素
const textElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
textElement.textContent = text;
textElement.setAttribute('x', '150');
textElement.setAttribute('y', '150');
textElement.setAttribute('text-anchor', 'middle');
textElement.setAttribute('dominant-baseline', 'middle');
textElement.setAttribute('font-size', fontSize);
textElement.setAttribute('font-family', fontFamily);
textElement.setAttribute('fill', color);
if (useGlow) {
textElement.setAttribute('filter', 'url(#logo-glow)');
}
// 添加动画
const animation = document.createElementNS('http://www.w3.org/2000/svg', 'animateTransform');
animation.setAttribute('attributeName', 'transform');
animation.setAttribute('type', 'scale');
animation.setAttribute('values', '1;1.1;1');
animation.setAttribute('dur', '2s');
animation.setAttribute('repeatCount', 'indefinite');
textElement.appendChild(animation);
this.svg.appendChild(textElement);
return this;
}
// 导出 SVG
export() {
const serializer = new XMLSerializer();
return serializer.serializeToString(this.svg);
}
// 导出 PNG
async exportPNG() {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = 300;
canvas.height = 300;
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
resolve(canvas.toDataURL('image/png'));
};
image.onerror = reject;
const svgBlob = new Blob([this.export()], { type: 'image/svg+xml' });
image.src = URL.createObjectURL(svgBlob);
});
}
}
// 使用示例
const logoGen = new LogoGenerator(document.getElementById('logo-container'));
logoGen.generateLogo('LOGO', {
fontSize: 64,
useGlow: true
});
性能优化建议 🚀
-
SVG 优化
- 使用
<use>
复用元素 - 压缩 SVG 代码
- 避免不必要的精度
- 使用
-
动画优化
- 使用 CSS 动画代替 SMIL
- 使用
transform
代替位置属性 - 避免频繁的 DOM 操作
-
交互优化
- 使用事件委托
- 优化碰撞检测
- 使用
requestAnimationFrame
浏览器兼容性 🌐
特性 | Chrome | Firefox | Safari | Edge |
---|---|---|---|---|
基础 SVG | ✅ | ✅ | ✅ | ✅ |
SMIL | ✅ | ✅ | ✅ | ✅ |
CSS 动画 | ✅ | ✅ | ✅ | ✅ |
滤镜 | ✅ | ✅ | ✅ | ✅ |
实用工具推荐 🛠️
-
SVG 编辑器
- Inkscape
- Adobe Illustrator
- Figma
-
SVG 优化工具
- SVGO
- SVG OMG
- SVG Optimizer
-
JavaScript 库
- Snap.svg
- SVG.js
- GreenSock
总结 🎯
SVG 为我们提供了强大的矢量图形能力:
- 可缩放性 📏
- 动画效果 🎬
- 交互能力 🖱️
- 滤镜效果 🎨
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻