HTML5系列(5)-- SVG 集成详解

前端技术探索系列: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>
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
});

性能优化建议 🚀

  1. SVG 优化

    • 使用 <use> 复用元素
    • 压缩 SVG 代码
    • 避免不必要的精度
  2. 动画优化

    • 使用 CSS 动画代替 SMIL
    • 使用 transform 代替位置属性
    • 避免频繁的 DOM 操作
  3. 交互优化

    • 使用事件委托
    • 优化碰撞检测
    • 使用 requestAnimationFrame

浏览器兼容性 🌐

特性 Chrome Firefox Safari Edge
基础 SVG
SMIL
CSS 动画
滤镜

实用工具推荐 🛠️

  1. SVG 编辑器

    • Inkscape
    • Adobe Illustrator
    • Figma
  2. SVG 优化工具

    • SVGO
    • SVG OMG
    • SVG Optimizer
  3. JavaScript 库

    • Snap.svg
    • SVG.js
    • GreenSock

总结 🎯

SVG 为我们提供了强大的矢量图形能力:

  • 可缩放性 📏
  • 动画效果 🎬
  • 交互能力 🖱️
  • 滤镜效果 🎨

如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇

终身学习,共同成长。

咱们下一期见

💻

相关推荐
web150850966411 小时前
【React&前端】大屏适配解决方案&从框架结构到实现(超详细)(附代码)
前端·react.js·前端框架
理想不理想v1 小时前
前端项目性能优化(详细)
前端·性能优化
CodeToGym1 小时前
使用 Vite 和 Redux Toolkit 创建 React 项目
前端·javascript·react.js·redux
Cachel wood2 小时前
Vue.js前端框架教程8:Vue消息提示ElMessage和ElMessageBox
linux·前端·javascript·vue.js·前端框架·ecmascript
桃园码工4 小时前
4_使用 HTML5 Canvas API (3) --[HTML5 API 学习之旅]
前端·html5·canvas
桃园码工4 小时前
9_HTML5 SVG (5) --[HTML5 API 学习之旅]
前端·html5·svg
人才程序员4 小时前
QML z轴(z-order)前后层级
c语言·前端·c++·qt·软件工程·用户界面·界面
m0_548514774 小时前
前端三大主流框架:React、Vue、Angular
前端·vue.js·react.js
m0_748232395 小时前
单页面应用 (SPA):现代 Web 开发的全新视角
前端
孤留光乩5 小时前
从零搭建纯前端飞机大战游戏(附源码)
前端·javascript·游戏·html·css3