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 为我们提供了强大的矢量图形能力:

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

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

终身学习,共同成长。

咱们下一期见

💻

相关推荐
辻戋9 分钟前
从零实现React Scheduler调度器
前端·react.js·前端框架
徐同保11 分钟前
使用yarn@4.6.0装包,项目是react+vite搭建的,项目无法启动,报错:
前端·react.js·前端框架
Qrun1 小时前
Windows11安装nvm管理node多版本
前端·vscode·react.js·ajax·npm·html5
中国lanwp1 小时前
全局 npm config 与多环境配置
前端·npm·node.js
JELEE.2 小时前
Django登录注册完整代码(图片、邮箱验证、加密)
前端·javascript·后端·python·django·bootstrap·jquery
TeleostNaCl4 小时前
解决 Chrome 无法访问网页但无痕模式下可以访问该网页 的问题
前端·网络·chrome·windows·经验分享
前端大卫6 小时前
为什么 React 中的 key 不能用索引?
前端
你的人类朋友6 小时前
【Node】手动归还主线程控制权:解决 Node.js 阻塞的一个思路
前端·后端·node.js
小李小李不讲道理7 小时前
「Ant Design 组件库探索」五:Tabs组件
前端·react.js·ant design
毕设十刻7 小时前
基于Vue的学分预警系统98k51(程序 + 源码 + 数据库 + 调试部署 + 开发环境配置),配套论文文档字数达万字以上,文末可获取,系统界面展示置于文末
前端·数据库·vue.js