SVG图形

SVG(Scalable Vector Graphics)是一种基于XML的矢量图形格式,可以无损缩放且支持交互和动画。本章将介绍SVG的基本概念和使用方法。

SVG概述

什么是SVG

const svgOverview = {
    definition: 'SVG是一种用于描述二维矢量图形的XML标记语言',
    
    features: [
        '矢量图形,无损缩放',
        '基于XML,可读性好',
        '支持CSS样式',
        '支持JavaScript交互',
        '支持动画',
        '文件体积小'
    ],
    
    comparison: {
        SVG: {
            type: '矢量',
            scaling: '无损',
            editing: '可编程修改',
            use: '图标、图表、简单图形'
        },
        Canvas: {
            type: '位图',
            scaling: '有损',
            editing: '需要重绘',
            use: '游戏、图像处理、复杂动画'
        }
    }
};

SVG嵌入方式

<img src="image.svg" alt="SVG图像">

<object type="image/svg+xml" data="image.svg"></object>

<embed type="image/svg+xml" src="image.svg">

<div style="background-image: url('image.svg')"></div>

<svg width="200" height="200">
    <circle cx="100" cy="100" r="50" fill="red"/>
</svg>

基本形状

矩形

<svg width="300" height="200">
    <rect x="10" y="10" width="100" height="80" fill="blue"/>
    
    <rect x="130" y="10" width="100" height="80" 
          fill="green" stroke="black" stroke-width="3"/>
    
    <rect x="10" y="110" width="100" height="80" rx="10" ry="10" fill="purple"/>
    
    <rect x="130" y="110" width="100" height="80" rx="40" fill="orange"/>
</svg>

圆形和椭圆

<svg width="300" height="200">
    <circle cx="80" cy="100" r="60" fill="red"/>
    
    <circle cx="220" cy="100" r="60" fill="blue" 
            stroke="black" stroke-width="5"/>
    
    <ellipse cx="80" cy="100" rx="70" ry="40" fill="green"/>
    
    <ellipse cx="220" cy="100" rx="40" ry="70" fill="purple"/>
</svg>

线段和折线

<svg width="300" height="200">
    <line x1="10" y1="10" x2="290" y2="190" 
          stroke="black" stroke-width="2"/>
    
    <polyline points="10,190 50,150 90,170 130,130 170,150 210,110 250,130 290,90"
              fill="none" stroke="blue" stroke-width="2"/>
    
    <polygon points="150,10 280,90 230,190 70,190 20,90"
             fill="lime" stroke="green" stroke-width="2"/>
</svg>

路径

<svg width="400" height="300">
    <path d="M 10 10 L 100 10 L 100 100 Z" fill="red"/>
    
    <path d="M 150 10 L 250 10 L 200 100 Z" fill="blue"/>
    
    <path d="M 10 150 Q 100 50 200 150" fill="none" stroke="green" stroke-width="2"/>
    
    <path d="M 220 150 C 220 50 350 50 350 150" fill="none" stroke="purple" stroke-width="2"/>
    
    <path d="M 10 250 A 50 50 0 1 1 100 250" fill="none" stroke="orange" stroke-width="2"/>
</svg>
const pathCommands = {
    M: { name: 'moveto', description: '移动到指定点' },
    L: { name: 'lineto', description: '画线到指定点' },
    H: { name: 'horizontal lineto', description: '水平线' },
    V: { name: 'vertical lineto', description: '垂直线' },
    C: { name: 'curveto', description: '三次贝塞尔曲线' },
    S: { name: 'smooth curveto', description: '平滑三次贝塞尔曲线' },
    Q: { name: 'quadratic Bézier curve', description: '二次贝塞尔曲线' },
    T: { name: 'smooth quadratic Bézier curveto', description: '平滑二次贝塞尔曲线' },
    A: { name: 'elliptical Arc', description: '椭圆弧' },
    Z: { name: 'closepath', description: '闭合路径' }
};

样式和属性

填充和描边

<svg width="400" height="300">
    <rect x="10" y="10" width="100" height="80" 
          fill="red" stroke="black" stroke-width="3"/>
    
    <rect x="130" y="10" width="100" height="80" 
          fill="rgb(0, 128, 255)" stroke="#333" stroke-width="2"/>
    
    <rect x="250" y="10" width="100" height="80" 
          fill="rgba(255, 0, 0, 0.5)" stroke="hsl(120, 100%, 50%)" stroke-width="4"/>
    
    <defs>
        <linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
            <stop offset="0%" style="stop-color:red"/>
            <stop offset="100%" style="stop-color:blue"/>
        </linearGradient>
    </defs>
    <rect x="10" y="110" width="100" height="80" fill="url(#grad1)"/>
    
    <defs>
        <radialGradient id="grad2" cx="50%" cy="50%" r="50%">
            <stop offset="0%" style="stop-color:yellow"/>
            <stop offset="100%" style="stop-color:red"/>
        </radialGradient>
    </defs>
    <rect x="130" y="110" width="100" height="80" fill="url(#grad2)"/>
    
    <rect x="250" y="110" width="100" height="80" 
          fill="none" stroke="black" stroke-width="10"
          stroke-dasharray="10,5"/>
    
    <rect x="10" y="210" width="100" height="80" 
          fill="purple" stroke="black" stroke-width="5"
          stroke-linecap="round" stroke-linejoin="round"/>
    
    <rect x="130" y="210" width="100" height="80" 
          fill="purple" opacity="0.5"/>
</svg>

CSS样式

<style>
    .my-rect {
        fill: blue;
        stroke: black;
        stroke-width: 2;
        transition: fill 0.3s ease;
    }
    
    .my-rect:hover {
        fill: red;
    }
    
    .my-circle {
        fill: green;
        filter: drop-shadow(3px 3px 2px rgba(0,0,0,0.3));
    }
</style>

<svg width="300" height="200">
    <rect class="my-rect" x="10" y="10" width="100" height="80"/>
    <circle class="my-circle" cx="200" cy="100" r="50"/>
</svg>

文本

<svg width="400" height="300">
    <text x="50" y="50" font-family="Arial" font-size="24" fill="black">
        东巴文 SVG教程
    </text>
    
    <text x="50" y="100" font-family="Arial" font-size="20" fill="blue"
          text-anchor="middle">
        居中文本
    </text>
    
    <text x="50" y="150" font-family="Arial" font-size="16" fill="green"
          font-weight="bold" font-style="italic">
        粗斜体文本
    </text>
    
    <text x="50" y="200">
        <tspan fill="red">红色</tspan>
        <tspan fill="blue">蓝色</tspan>
        <tspan fill="green">绿色</tspan>
    </text>
    
    <defs>
        <path id="textPath" d="M 50 250 Q 200 200 350 250"/>
    </defs>
    <text font-size="16">
        <textPath href="#textPath">
            沿路径排列的文本内容
        </textPath>
    </text>
</svg>

变换

<svg width="400" height="300">
    <rect x="10" y="10" width="50" height="50" fill="red"/>
    
    <rect x="10" y="10" width="50" height="50" fill="blue"
          transform="translate(100, 0)"/>
    
    <rect x="10" y="10" width="50" height="50" fill="green"
          transform="rotate(45, 35, 35) translate(200, 0)"/>
    
    <rect x="10" y="10" width="50" height="50" fill="purple"
          transform="scale(1.5) translate(200, 100)"/>
    
    <rect x="10" y="10" width="50" height="50" fill="orange"
          transform="skewX(20) translate(0, 150)"/>
    
    <rect x="10" y="10" width="50" height="50" fill="cyan"
          transform="matrix(1, 0.5, 0.5, 1, 200, 150)"/>
</svg>

动画

SMIL动画

<svg width="400" height="300">
    <circle cx="50" cy="150" r="30" fill="red">
        <animate attributeName="cx" from="50" to="350" 
                 dur="2s" repeatCount="indefinite"/>
    </circle>
    
    <circle cx="200" cy="50" r="20" fill="blue">
        <animate attributeName="cy" from="50" to="250" 
                 dur="2s" repeatCount="indefinite"/>
        <animate attributeName="r" values="20;40;20" 
                 dur="2s" repeatCount="indefinite"/>
    </circle>
    
    <rect x="100" y="100" width="50" height="50" fill="green">
        <animateTransform attributeName="transform" type="rotate"
                          from="0 125 125" to="360 125 125"
                          dur="3s" repeatCount="indefinite"/>
    </rect>
    
    <path d="M 10 250 L 100 250" stroke="purple" stroke-width="3">
        <animate attributeName="d" 
                 values="M 10 250 L 100 250;M 10 250 L 200 250;M 10 250 L 100 250"
                 dur="2s" repeatCount="indefinite"/>
    </path>
</svg>

CSS动画

<style>
    @keyframes rotate {
        from { transform: rotate(0deg); }
        to { transform: rotate(360deg); }
    }
    
    @keyframes pulse {
        0%, 100% { transform: scale(1); }
        50% { transform: scale(1.2); }
    }
    
    .rotating {
        animation: rotate 3s linear infinite;
        transform-origin: center;
    }
    
    .pulsing {
        animation: pulse 1s ease-in-out infinite;
    }
</style>

<svg width="400" height="300">
    <rect class="rotating" x="75" y="75" width="50" height="50" fill="blue"/>
    <circle class="pulsing" cx="250" cy="100" r="30" fill="red"/>
</svg>

JavaScript动画

const svg = document.querySelector('svg');
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('cx', 50);
circle.setAttribute('cy', 150);
circle.setAttribute('r', 30);
circle.setAttribute('fill', 'blue');
svg.appendChild(circle);

let x = 50;
let direction = 1;

function animate() {
    x += direction * 2;
    
    if (x > 350 || x < 50) {
        direction *= -1;
    }
    
    circle.setAttribute('cx', x);
    requestAnimationFrame(animate);
}

animate();

function createSVGElement(tag, attributes) {
    const element = document.createElementNS('http://www.w3.org/2000/svg', tag);
    
    for (const [key, value] of Object.entries(attributes)) {
        element.setAttribute(key, value);
    }
    
    return element;
}

const rect = createSVGElement('rect', {
    x: 100,
    y: 100,
    width: 50,
    height: 50,
    fill: 'green'
});
svg.appendChild(rect);

交互

事件处理

<svg width="400" height="300">
    <rect id="myRect" x="50" y="50" width="100" height="80" fill="blue"/>
    
    <circle id="myCircle" cx="250" cy="100" r="40" fill="red"/>
    
    <path id="myPath" d="M 100 200 L 150 150 L 200 200 Z" fill="green"/>
</svg>

<script>
    const rect = document.getElementById('myRect');
    
    rect.addEventListener('click', function() {
        this.setAttribute('fill', this.getAttribute('fill') === 'blue' ? 'red' : 'blue');
    });
    
    rect.addEventListener('mouseenter', function() {
        this.style.cursor = 'pointer';
    });
    
    rect.addEventListener('mouseleave', function() {
        this.style.cursor = 'default';
    });
    
    const circle = document.getElementById('myCircle');
    
    circle.addEventListener('mousedown', function(e) {
        const startX = e.clientX;
        const startY = e.clientY;
        const startCx = parseFloat(this.getAttribute('cx'));
        const startCy = parseFloat(this.getAttribute('cy'));
        
        const moveHandler = (e) => {
            const dx = e.clientX - startX;
            const dy = e.clientY - startY;
            this.setAttribute('cx', startCx + dx);
            this.setAttribute('cy', startCy + dy);
        };
        
        const upHandler = () => {
            document.removeEventListener('mousemove', moveHandler);
            document.removeEventListener('mouseup', upHandler);
        };
        
        document.addEventListener('mousemove', moveHandler);
        document.addEventListener('mouseup', upHandler);
    });
</script>

滤镜和效果

<svg width="400" height="300">
    <defs>
        <filter id="blur">
            <feGaussianBlur in="SourceGraphic" stdDeviation="5"/>
        </filter>
        
        <filter id="shadow">
            <feDropShadow dx="5" dy="5" stdDeviation="3" flood-color="black"/>
        </filter>
        
        <filter id="glow">
            <feGaussianBlur stdDeviation="3" result="coloredBlur"/>
            <feMerge>
                <feMergeNode in="coloredBlur"/>
                <feMergeNode in="SourceGraphic"/>
            </feMerge>
        </filter>
        
        <filter id="grayscale">
            <feColorMatrix type="matrix"
                values="0.33 0.33 0.33 0 0
                        0.33 0.33 0.33 0 0
                        0.33 0.33 0.33 0 0
                        0    0    0    1 0"/>
        </filter>
        
        <clipPath id="clipCircle">
            <circle cx="100" cy="100" r="50"/>
        </clipPath>
        
        <mask id="maskGradient">
            <rect x="0" y="0" width="200" height="200" fill="url(#grad1)"/>
        </mask>
    </defs>
    
    <rect x="10" y="10" width="80" height="80" fill="red" filter="url(#blur)"/>
    
    <rect x="110" y="10" width="80" height="80" fill="blue" filter="url(#shadow)"/>
    
    <rect x="210" y="10" width="80" height="80" fill="green" filter="url(#glow)"/>
    
    <image href="photo.jpg" x="10" y="110" width="100" height="100" filter="url(#grayscale)"/>
    
    <rect x="120" y="110" width="100" height="100" fill="purple" clip-path="url(#clipCircle)"/>
</svg>

东巴文小贴士

🎨 SVG使用建议

  1. 图标系统:使用SVG sprite或symbol创建图标系统
  2. 优化文件:使用SVGO等工具优化SVG文件
  3. 可访问性:添加title和desc元素提高可访问性
  4. 性能:复杂SVG考虑使用CSS will-change优化

📐 坐标系统

SVG坐标系统:

  • 原点在左上角
  • X轴向右为正
  • Y轴向下为正
  • 使用viewBox实现响应式缩放

下一步

下一章将探讨 [Web Audio API](file:///e:/db-w.cn/md_data/javascript/76_Web Audio API.md),学习如何在Web应用中处理音频。