{T}

JavaScript 交互

SVG 元素是 DOM 的一部分,可以通过 JavaScript 进行完全的操作和交互。

获取和操作 SVG 元素

javascript
// 获取 SVG 元素
const svg = document.querySelector("svg")
const circle = document.getElementById("myCircle")
const rect = document.querySelector("rect")

// 修改属性
circle.setAttribute("r", "60")
circle.setAttribute("fill", "blue")
rect.style.fill = "red" // 使用 style 属性

// 获取属性
const radius = circle.getAttribute("r")
const fillColor = circle.getAttribute("fill")

动态创建 SVG 元素

javascript
// 创建 SVG 元素
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
svg.setAttribute("width", "400")
svg.setAttribute("height", "300")

// 创建圆形
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle")
circle.setAttribute("cx", "200")
circle.setAttribute("cy", "150")
circle.setAttribute("r", "50")
circle.setAttribute("fill", "blue")

// 添加到 SVG
svg.appendChild(circle)
document.body.appendChild(svg)

使用 JavaScript 操作 SVG 的完整示例

html
<svg id="interactiveSvg" width="400" height="300" xmlns="http://www.w3.org/2000/svg">
  <circle id="myCircle" cx="200" cy="150" r="50" fill="blue" />
  <rect id="myRect" x="100" y="100" width="100" height="80" fill="red" />
</svg>

<button onclick="changeColor()">改变颜色</button>
<button onclick="animateCircle()">动画圆形</button>
<button onclick="addShape()">添加形状</button>

<script>
  const circle = document.getElementById("myCircle")
  const rect = document.getElementById("myRect")

  function changeColor() {
    const colors = ["red", "blue", "green", "yellow", "purple"]
    const randomColor = colors[Math.floor(Math.random() * colors.length)]
    circle.setAttribute("fill", randomColor)
  }

  function animateCircle() {
    let radius = 50
    const interval = setInterval(() => {
      radius += 5
      circle.setAttribute("r", radius)
      if (radius > 100) {
        clearInterval(interval)
        circle.setAttribute("r", "50")
      }
    }, 50)
  }

  function addShape() {
    const svg = document.getElementById("interactiveSvg")
    const newCircle = document.createElementNS(
      "http://www.w3.org/2000/svg",
      "circle"
    )
    newCircle.setAttribute("cx", Math.random() * 400)
    newCircle.setAttribute("cy", Math.random() * 300)
    newCircle.setAttribute("r", "20")
    newCircle.setAttribute("fill", "orange")
    svg.appendChild(newCircle)
  }
</script>

事件处理

SVG 元素支持标准的 DOM 事件,可以轻松实现交互功能。

鼠标事件

html
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
  <circle
    id="clickableCircle"
    cx="200"
    cy="150"
    r="50"
    fill="blue"
    style="cursor: pointer;" />
</svg>

<script>
  const circle = document.getElementById("clickableCircle")

  // 点击事件
  circle.addEventListener("click", (e) => {
    const colors = ["red", "blue", "green", "yellow", "purple"]
    const randomColor = colors[Math.floor(Math.random() * colors.length)]
    circle.setAttribute("fill", randomColor)
  })

  // 鼠标悬停
  circle.addEventListener("mouseenter", () => {
    circle.setAttribute("r", "60")
  })

  circle.addEventListener("mouseleave", () => {
    circle.setAttribute("r", "50")
  })

  // 鼠标移动
  circle.addEventListener("mousemove", (e) => {
    const rect = circle.getBoundingClientRect()
    const x = e.clientX - rect.left
    const y = e.clientY - rect.top
    console.log(`鼠标位置: (${x}, ${y})`)
  })
</script>

触摸事件(移动端)

html
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
  <circle id="touchableCircle" cx="200" cy="150" r="50" fill="green" />
</svg>

<script>
  const circle = document.getElementById("touchableCircle")

  circle.addEventListener("touchstart", (e) => {
    e.preventDefault()
    circle.setAttribute("fill", "red")
  })

  circle.addEventListener("touchend", (e) => {
    e.preventDefault()
    circle.setAttribute("fill", "green")
  })
</script>

拖拽功能

html
<svg id="dragSvg" width="400" height="300" xmlns="http://www.w3.org/2000/svg">
  <circle
    id="draggableCircle"
    cx="200"
    cy="150"
    r="50"
    fill="blue"
    style="cursor: move;" />
</svg>

<script>
  const circle = document.getElementById("draggableCircle")
  const svg = document.getElementById("dragSvg")
  let isDragging = false
  let offset = { x: 0, y: 0 }

  circle.addEventListener("mousedown", (e) => {
    isDragging = true
    const rect = svg.getBoundingClientRect()
    const cx = parseFloat(circle.getAttribute("cx"))
    const cy = parseFloat(circle.getAttribute("cy"))
    offset.x = e.clientX - rect.left - cx
    offset.y = e.clientY - rect.top - cy
  })

  svg.addEventListener("mousemove", (e) => {
    if (isDragging) {
      const rect = svg.getBoundingClientRect()
      const x = e.clientX - rect.left - offset.x
      const y = e.clientY - rect.top - offset.y
      circle.setAttribute("cx", x)
      circle.setAttribute("cy", y)
    }
  })

  svg.addEventListener("mouseup", () => {
    isDragging = false
  })

  svg.addEventListener("mouseleave", () => {
    isDragging = false
  })
</script>

交互式 SVG 应用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>交互式 SVG 示例</title>
    <style>
      body {
        font-family: Arial, sans-serif;
        padding: 20px;
        background-color: #f5f5f5;
      }
      .container {
        background: white;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      }
      .controls {
        margin: 20px 0;
      }
      button {
        padding: 10px 20px;
        margin: 5px;
        cursor: pointer;
        background: #4caf50;
        color: white;
        border: none;
        border-radius: 4px;
      }
      button:hover {
        background: #45a049;
      }
      svg {
        border: 1px solid #ddd;
        background: white;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1>交互式 SVG 示例</h1>
      <div class="controls">
        <button onclick="addRandomShape()">添加随机形状</button>
        <button onclick="clearAll()">清空</button>
        <button onclick="animateAll()">动画所有</button>
      </div>
      <svg
        id="interactiveSvg"
        width="600"
        height="400"
        xmlns="http://www.w3.org/2000/svg">
        <defs>
          <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" stop-color="#ff6b6b" />
            <stop offset="100%" stop-color="#4ecdc4" />
          </linearGradient>
        </defs>
        <rect width="600" height="400" fill="url(#grad1)" opacity="0.1" />
      </svg>
    </div>

    <script>
      const svg = document.getElementById("interactiveSvg")
      let shapeCount = 0

      function addRandomShape() {
        const shapes = ["circle", "rect", "ellipse"]
        const shapeType = shapes[Math.floor(Math.random() * shapes.length)]
        const shape = document.createElementNS(
          "http://www.w3.org/2000/svg",
          shapeType
        )

        const colors = ["#ff6b6b", "#4ecdc4", "#ffe66d", "#a8e6cf", "#ff8b94"]
        const color = colors[Math.floor(Math.random() * colors.length)]

        shape.setAttribute("fill", color)
        shape.setAttribute("stroke", "#333")
        shape.setAttribute("stroke-width", "2")
        shape.style.cursor = "pointer"
        shape.setAttribute("class", "interactive-shape")

        if (shapeType === "circle") {
          shape.setAttribute("cx", Math.random() * 500 + 50)
          shape.setAttribute("cy", Math.random() * 300 + 50)
          shape.setAttribute("r", Math.random() * 30 + 20)
        } else if (shapeType === "rect") {
          shape.setAttribute("x", Math.random() * 500)
          shape.setAttribute("y", Math.random() * 300)
          shape.setAttribute("width", Math.random() * 60 + 40)
          shape.setAttribute("height", Math.random() * 60 + 40)
          shape.setAttribute("rx", "5")
        } else if (shapeType === "ellipse") {
          shape.setAttribute("cx", Math.random() * 500 + 50)
          shape.setAttribute("cy", Math.random() * 300 + 50)
          shape.setAttribute("rx", Math.random() * 40 + 30)
          shape.setAttribute("ry", Math.random() * 30 + 20)
        }

        // 添加交互事件
        shape.addEventListener("click", () => {
          const newColor = colors[Math.floor(Math.random() * colors.length)]
          shape.setAttribute("fill", newColor)
        })

        shape.addEventListener("mouseenter", () => {
          shape.setAttribute("opacity", "0.7")
        })

        shape.addEventListener("mouseleave", () => {
          shape.setAttribute("opacity", "1")
        })

        svg.appendChild(shape)
        shapeCount++
      }

      function clearAll() {
        const shapes = svg.querySelectorAll(".interactive-shape")
        shapes.forEach((shape) => shape.remove())
        shapeCount = 0
      }

      function animateAll() {
        const shapes = svg.querySelectorAll(".interactive-shape")
        shapes.forEach((shape, index) => {
          const animate = document.createElementNS(
            "http://www.w3.org/2000/svg",
            "animateTransform"
          )
          animate.setAttribute("attributeName", "transform")
          animate.setAttribute("type", "rotate")
          animate.setAttribute(
            "from",
            `0 ${shape.getAttribute("cx") || 0} ${shape.getAttribute("cy") || 0}`
          )
          animate.setAttribute(
            "to",
            `360 ${shape.getAttribute("cx") || 0} ${shape.getAttribute("cy") || 0}`
          )
          animate.setAttribute("dur", "2s")
          animate.setAttribute("repeatCount", "indefinite")
          shape.appendChild(animate)
        })
      }
    </script>
  </body>
</html>

响应式设计

SVG 天生支持响应式设计,可以通过多种方式实现自适应布局。

viewBox 属性

viewBox 是 SVG 响应式设计的核心,它定义了 SVG 的坐标系统和可见区域。

html
<!-- 使用 viewBox 实现响应式 -->
<svg viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
  <circle cx="200" cy="150" r="50" fill="blue" />
  <rect x="100" y="100" width="200" height="100" fill="red" />
</svg>

<style>
  svg {
    width: 100%;
    height: auto;
    max-width: 800px;
  }
</style>

preserveAspectRatio 属性

控制 SVG 如何适应容器,保持宽高比。

html
<!-- 保持宽高比,居中显示 -->
<svg
  viewBox="0 0 400 300"
  preserveAspectRatio="xMidYMid meet"
  width="100%"
  height="auto">
  <!-- SVG 内容 -->
</svg>

<!-- 填充整个容器,可能裁剪 -->
<svg
  viewBox="0 0 400 300"
  preserveAspectRatio="xMidYMid slice"
  width="100%"
  height="auto">
  <!-- SVG 内容 -->
</svg>

preserveAspectRatio 值说明

说明
xMinYMin meet左上对齐,保持比例
xMidYMid meet居中,保持比例(默认)
xMaxYMax meet右下对齐,保持比例
xMinYMin slice左上对齐,填充容器
xMidYMid slice居中,填充容器
none不保持宽高比,拉伸填充

响应式 SVG 示例

html
<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>响应式 SVG</title>
    <style>
      .responsive-container {
        width: 100%;
        max-width: 800px;
        margin: 0 auto;
      }

      .responsive-svg {
        width: 100%;
        height: auto;
        border: 1px solid #ddd;
      }

      @media (max-width: 768px) {
        .responsive-svg {
          max-height: 300px;
        }
      }
    </style>
  </head>
  <body>
    <div class="responsive-container">
      <h2>响应式 SVG 示例</h2>
      <svg
        class="responsive-svg"
        viewBox="0 0 400 300"
        preserveAspectRatio="xMidYMid meet"
        xmlns="http://www.w3.org/2000/svg">
        <defs>
          <linearGradient id="responsiveGrad" x1="0%" y1="0%" x2="100%" y2="100%">
            <stop offset="0%" stop-color="#ff6b6b" />
            <stop offset="100%" stop-color="#4ecdc4" />
          </linearGradient>
        </defs>

        <rect width="400" height="300" fill="url(#responsiveGrad)" opacity="0.3" />
        <circle cx="200" cy="150" r="80" fill="blue" opacity="0.7" />
        <text x="200" y="160" text-anchor="middle" fill="white" font-size="24">
          响应式 SVG
        </text>
      </svg>
    </div>
  </body>
</html>

性能优化

1. 使用 <use> 元素复用图形

html
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <circle id="reusableCircle" cx="0" cy="0" r="20" fill="blue" />
  </defs>

  <!-- 复用圆形,而不是创建多个 -->
  <use href="#reusableCircle" x="50" y="50" />
  <use href="#reusableCircle" x="150" y="50" />
  <use href="#reusableCircle" x="250" y="50" />
  <use href="#reusableCircle" x="350" y="50" />
</svg>

2. 使用 CSS 动画替代 SMIL 动画

html
<!-- 不推荐:SMIL 动画(部分浏览器已弃用) -->
<circle cx="100" cy="100" r="50" fill="blue">
  <animate attributeName="r" from="50" to="100" dur="2s" repeatCount="indefinite" />
</circle>

<!-- 推荐:CSS 动画 -->
<style>
  .animated-circle {
    animation: pulse 2s infinite;
  }

  @keyframes pulse {
    0%,
    100% {
      r: 50;
    }
    50% {
      r: 100;
    }
  }
</style>

<circle class="animated-circle" cx="100" cy="100" r="50" fill="blue" />

3. 减少 DOM 操作

javascript
// 不好的做法:频繁操作 DOM
for (let i = 0; i < 1000; i++) {
  const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle")
  svg.appendChild(circle)
}

// 好的做法:使用 DocumentFragment
const fragment = document.createDocumentFragment()
for (let i = 0; i < 1000; i++) {
  const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle")
  fragment.appendChild(circle)
}
svg.appendChild(fragment)

4. 优化复杂路径

html
<!-- 简化路径数据 -->
<path d="M10,10 L20,20 L30,10 Z" />

<!-- 使用简化的命令 -->
<path d="M10,10 L20,20 30,10 Z" />

5. 使用 CSS 控制样式

html
<!-- 不推荐:内联样式 -->
<circle cx="100" cy="100" r="50" fill="blue" stroke="red" stroke-width="2" />

<!-- 推荐:使用 CSS -->
<style>
  .my-circle {
    fill: blue;
    stroke: red;
    stroke-width: 2;
  }
</style>
<circle class="my-circle" cx="100" cy="100" r="50" />

最佳实践

1. 命名空间

始终使用正确的命名空间创建 SVG 元素。

javascript
// 正确
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle")

// 错误(会创建 HTML 元素,不是 SVG 元素)
const svg = document.createElement("svg")

2. 可访问性

为 SVG 添加适当的可访问性属性。

html
<svg role="img" aria-label="描述性文本" xmlns="http://www.w3.org/2000/svg">
  <title>图形标题</title>
  <desc>图形的详细描述</desc>
  <!-- SVG 内容 -->
</svg>

3. 语义化结构

使用 <g> 元素组织相关的 SVG 元素。

html
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
  <g id="house" transform="translate(100, 50)">
    <rect x="0" y="0" width="200" height="150" fill="brown" />
    <polygon points="0,0 100,-50 200,0" fill="red" />
  </g>

  <g id="tree" transform="translate(50, 100)">
    <rect x="0" y="0" width="30" height="100" fill="brown" />
    <circle cx="15" cy="0" r="40" fill="green" />
  </g>
</svg>

4. 代码组织

将可复用的定义放在 <defs> 中。

html
<svg width="400" height="300" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <!-- 渐变定义 -->
    <linearGradient id="myGradient">
      <stop offset="0%" stop-color="red" />
      <stop offset="100%" stop-color="blue" />
    </linearGradient>

    <!-- 滤镜定义 -->
    <filter id="myFilter">
      <feGaussianBlur stdDeviation="3" />
    </filter>

    <!-- 可复用图形 -->
    <circle id="reusableCircle" r="20" fill="url(#myGradient)" />
  </defs>

  <!-- 使用定义 -->
  <use href="#reusableCircle" x="100" y="100" />
</svg>

5. 错误处理

javascript
// 检查浏览器支持
if (typeof SVGRect !== "undefined") {
  // SVG 支持
} else {
  // 不支持 SVG,提供降级方案
}

// 检查元素是否存在
const svg = document.querySelector("svg")
if (svg) {
  // 操作 SVG
}

常见问题

1. SVG 不显示

问题:SVG 元素不显示或显示不正确。

解决方案

  • 检查命名空间是否正确
  • 确认 widthheight 属性已设置
  • 检查 viewBox 是否正确
  • 验证路径数据格式
html
<!-- 确保包含命名空间 -->
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300">
  <!-- 内容 -->
</svg>

2. 样式不生效

问题:CSS 样式无法应用到 SVG 元素。

解决方案

  • 使用 fill 而不是 background-color
  • 使用 stroke 而不是 border
  • 某些属性需要使用 setAttribute 而不是 style
css
/* SVG 样式 */
svg circle {
  fill: blue; /* 不是 background-color */
  stroke: red; /* 不是 border */
  stroke-width: 2; /* 不是 border-width */
}

3. 动画不流畅

问题:SVG 动画卡顿或不流畅。

解决方案

  • 使用 CSS 动画替代 SMIL 动画
  • 减少同时运行的动画数量
  • 使用 transform 属性进行动画(硬件加速)
  • 避免动画复杂的路径
css
/* 使用 transform 进行动画(性能更好) */
.animated {
  transform: translate(0, 0);
  transition: transform 0.3s ease;
}

.animated:hover {
  transform: translate(10px, 10px);
}

4. 跨域问题

问题:外部 SVG 文件无法加载或操作。

解决方案

  • 确保服务器设置了正确的 CORS 头
  • 使用同源 SVG 文件
  • 考虑将 SVG 内联到 HTML 中

5. 响应式问题

问题:SVG 在不同屏幕尺寸下显示不正确。

解决方案

  • 使用 viewBox 而不是固定的 widthheight
  • 设置 preserveAspectRatio 属性
  • 使用 CSS 控制 SVG 尺寸
html
<!-- 响应式 SVG -->
<svg
  viewBox="0 0 400 300"
  preserveAspectRatio="xMidYMid meet"
  style="width: 100%; height: auto;">
  <!-- 内容 -->
</svg>

SVG vs Canvas 详细对比

特性SVGCanvas
图形类型矢量图位图
DOM 支持是,每个元素都是 DOM 节点否,只有一个 canvas 元素
事件处理原生支持,每个元素可独立处理需要手动计算坐标
缩放质量无损缩放可能模糊
性能适合少量复杂图形适合大量简单图形
文件大小复杂图形可能较大通常较小
学习曲线中等简单
适用场景图标、地图、可缩放图形、交互式图形游戏、图表、图像处理、动画
可访问性支持,可添加文本描述不支持
样式控制CSS 完全支持需要通过 JavaScript
动画CSS/JS/SMILJavaScript
文本渲染原生支持,可选中需要手动实现

选择建议

  • 使用 SVG 当:

    • 需要可缩放的图形
    • 需要交互性(点击、悬停等)
    • 图形数量较少但复杂
    • 需要可访问性支持
    • 需要 CSS 样式控制
  • 使用 Canvas 当:

    • 需要处理大量图形
    • 需要像素级操作
    • 需要高性能动画
    • 图形不需要交互
    • 需要图像处理功能

实际应用案例

1. 数据可视化

html
<svg id="chart" width="600" height="400" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <linearGradient id="barGradient" x1="0%" y1="0%" x2="0%" y2="100%">
      <stop offset="0%" stop-color="#4ecdc4" />
      <stop offset="100%" stop-color="#44a08d" />
    </linearGradient>
  </defs>

  <!-- 坐标轴 -->
  <line x1="50" y1="350" x2="550" y2="350" stroke="black" stroke-width="2" />
  <line x1="50" y1="50" x2="50" y2="350" stroke="black" stroke-width="2" />

  <!-- 数据柱 -->
  <rect x="100" y="250" width="60" height="100" fill="url(#barGradient)" />
  <rect x="200" y="200" width="60" height="150" fill="url(#barGradient)" />
  <rect x="300" y="150" width="60" height="200" fill="url(#barGradient)" />
  <rect x="400" y="100" width="60" height="250" fill="url(#barGradient)" />
  <rect x="500" y="180" width="60" height="170" fill="url(#barGradient)" />

  <!-- 标签 -->
  <text x="130" y="370" text-anchor="middle" font-size="14">Q1</text>
  <text x="230" y="370" text-anchor="middle" font-size="14">Q2</text>
  <text x="330" y="370" text-anchor="middle" font-size="14">Q3</text>
  <text x="430" y="370" text-anchor="middle" font-size="14">Q4</text>
  <text x="530" y="370" text-anchor="middle" font-size="14">Q5</text>
</svg>

2. 图标系统

html
<svg style="display: none;" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <symbol id="icon-home" viewBox="0 0 24 24">
      <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" fill="currentColor" />
    </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"
        fill="currentColor" />
    </symbol>
  </defs>
</svg>

<!-- 使用图标 -->
<svg class="icon" width="24" height="24">
  <use href="#icon-home" />
</svg>

<style>
  .icon {
    fill: currentColor;
    width: 24px;
    height: 24px;
  }
</style>

3. 加载动画

html
<svg class="spinner" width="50" height="50" xmlns="http://www.w3.org/2000/svg">
  <circle
    cx="25"
    cy="25"
    r="20"
    fill="none"
    stroke="#4ecdc4"
    stroke-width="4"
    stroke-dasharray="31.416"
    stroke-dashoffset="31.416">
    <animate
      attributeName="stroke-dasharray"
      values="0 31.416;15.708 15.708;0 31.416;0 31.416"
      dur="2s"
      repeatCount="indefinite" />
    <animate
      attributeName="stroke-dashoffset"
      values="0;-15.708;-31.416;-31.416"
      dur="2s"
      repeatCount="indefinite" />
  </circle>
</svg>

<style>
  .spinner {
    animation: rotate 2s linear infinite;
  }

  @keyframes rotate {
    from {
      transform: rotate(0deg);
    }
    to {
      transform: rotate(360deg);
    }
  }
</style>

总结

SVG 是一个强大的矢量图形技术,具有以下优势:

  1. 可缩放性:无损缩放,适合各种屏幕尺寸
  2. 交互性:原生支持事件处理,易于实现交互
  3. 可访问性:支持文本描述和语义化标记
  4. 样式控制:完全支持 CSS
  5. DOM 集成:作为 DOM 的一部分,易于操作

关键要点

  • 使用 viewBox 实现响应式设计
  • 使用 <defs><use> 复用图形
  • 优先使用 CSS 动画而非 SMIL
  • 为复杂图形添加适当的可访问性属性
  • 根据场景选择 SVG 或 Canvas

通过合理使用 SVG,可以创建出美观、交互性强、性能良好的图形应用。