整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

SVG在Web攻击中的应用

SVG在Web攻击中的应用

x00 前言

在过去几周中,FortiGuard Labs一直在研究带有SVG(Scalable Vector Graphics)图像的Web应用。根据研究结果,我们找到了Web应用中的一些常见问题。在本文中,我们简要介绍了SVG的特点以及针对SVG图像的常见攻击面。

根据之前的研究结果,我们梳理了一些常见的SVG攻击方式,如下所示:

  • 跨站脚本(Cross-Site Scripting)
  • HTML注入
  • XML实体:“Billion Laughs”攻击(针对XML文档解析器的一种DoS攻击)
  • DoS(拒绝服务):新型SVG “Billion Laughs”攻击。

0x01 SVG简介

SVG的全称为 Scalable Vector Graphics(可缩放矢量图),是一种基于XML的二维矢量图格式,支持交互性及动画展示。SVG图像及具体行为由XML文本文件定义,可以通过任何文本编辑器以及绘图软件来创建并编辑。目前所有主流web浏览器都支持渲染SVG图像。

来观察一个示例,更好理解SVG图像。如下图所示,我们编写了一些代码来渲染SVG图像:

图1. simple.svg代码片段

将该图像保存为simple.svg,然后直接打开,或者将其包含在img/image/object/embed HTML标签中,如下图所示:

图2. 通过代码渲染图像

图1代码渲染生成的图像如图2所示,这是rect元素,浏览器会在x, y (100, 100)(即宽度和高度)位置渲染一个红色矩形。

0x02 使用SVG的攻击场景

虽然SVG提供了较大的灵活性,可以方便创建更多的动态web内容,但同时也引入了一些安全风险。在下文中,我们将讨论一些常见的攻击向量,我们在互联网上的一些主流站点上都观察到过这些攻击方式。

跨站脚本

我们可以通过脚本方式来访问并修改SVG文档的任何内容,这与HTML操作方式类似。默认的脚本语言为ECMAScript(与JavaScript密切相关),每个SVG元素及属性都对应已定义的DOM(Document Object Model,文档对象模型)对象。相关脚本被封装在<script>元素中。

这意味着如果web服务器允许用户上传任意SVG图像,就存在XSS(跨站脚本)安全风险。如下所示,我们将脚本存放在图像中:

图3. xss.svg代码片段

将该图像保存为xss.svg,然后直接打开,如下图所示:

图4. 直接访问该文件触发XSS

如果将该文件链接到某个HTML页面,访问该页面也可以触发,如下图所示:

图5. 通过链接文件触发XSS

JavaScript代码会在浏览器上下文中执行,这意味着攻击者可以使用该文件执行恶意行为,比如窃取用户隐私信息等。

HTML注入

在某些情况下,XSS payload会被服务端过滤,然而我们依然能够通过SVG图像的特定功能来注入HTML代码。如前文所述,SVG是基于XML的一种矢量图,因此我们无法简单将HTML内容放入其中,不然会破坏XML的语法。

为了避免这种情况,SVG提供了一个foreignObject元素,可以用来包含来自其他XML命名空间的元素。在浏览器上下文中,这部分数据很可能采用(X)HTML形式。

来看一下html.svg图像:

图6. html.svg代码片段

当我们在foreignObject内添加一个body标签以及XHTML命名空间时,可以使用xmlns属性来声明命名空间。采用这种方式,浏览器会将body标签及其所有子标签解析为属于XHTML的元素。因此,我们可以将来自SVG的任意XHTML代码渲染到页面中:

图7. HTML注入漏洞

这种方式可以运行任意HTML代码,意味着我们可以简单从SVG图像中发起类似钓鱼、绕过同源策略、CSRF之类的攻击。

XML实体:Billion Laughs Attack

由于SVG是基于XML的矢量图,因此可以支持Entity(实体)功能。Entity可以用来定义特殊字符的快捷方式,也可以声明成内部或外部实体。

我们可以通过如下方式声明内部Entity:

<!ENTITY entity-name "entity-value">

通过如下方式声明外部Entity:

<!ENTITY entity-name SYSTEM "URI/URL">

如果解析文件的XML解析器存在脆弱性,那么我们就可以滥用外部Entity功能来泄露内部数据。由于现在大家主要使用的都是现代浏览器,因此我们假设可用的解析器都经过fuzzer的严格测试,因此没那么容易被攻击。在这个前提下,这里我们主要讨论如何滥用内部Entity。

entity.svg的内部实现如下所示:

图8. entity.svg代码片段

如上图所示,我们在第2行定义lab这个Entity,然后在SVG元素中调用该实体。结果如图9所示:

图9. lab实体被加载到页面

一切非常顺利,来尝试另一个例子:entity_2.svg,如下图所示:

图10. entity_2.svg代码片段

结果如下:

图11. lab2实体被加载到页面

如上图所示,这里的文本内容被重复渲染,这表明我们可以使用Entity标签发起“ Billion Laughs ”攻击。

“ Billion Laughs ”攻击是一种DoS(拒绝服务)攻击,目标是XML文档解析器。这种攻击也被称之为XML炸弹或者指数实体攻击。

图12. billion_laughs.svg代码片段

我们的浏览器在解析这个 billion_laughs.svg数据时,只花了4~5秒就能正常响应。这是因为大多数现代浏览器已经能够能应付这种攻击,可以在渲染过程中解决该问题,因此不会造成安全风险。

拒绝服务:新型SVG “Billion Laughs”攻击

在上一节中,我们发现“ Billion Laughs ”攻击可以延缓浏览器的处理速度,浏览器需要4~5秒才能应付该攻击。不幸的是,攻击者还可以通过SVG图像,发起另一种“ Billion Laughs ”攻击,绕过这些防御措施。

这一次我们使用xlink:href来代替XML Entity。来看一下 xlink_laughs.svg所使用的payload:

图13. xlink_laughs.svg代码片段

xlink:href属性以IRI(国际资源标识)方式定义了对某个资源的引用,该链接的具体含义需根据使用该链接的每个元素的上下文来决定。

<use>元素从SVG文档中获取节点,然后将其复制到其他位置。

我们现在a0中定义circle元素,然后在a1、a2、a3……中通过xlink:href属性调用<use>元素,通过这种方式反复克隆circle。结果如下图所示:

图14. 在解析恶意SVG时,通过xlink:href发起“ Billion Laugh”攻击

需要注意的是,在最坏的情况下,大多数现代浏览器在尝试解析网站上的这张SVG图像时可能会发生崩溃,或者至少会出现无响应情况。

有趣的是,我们在测试某些开源SVG/XML过滤器时,发现这些过滤器并不能正确捕捉到图13所示的SVG图像。因此,这种错误格式的SVG图像也可能造成DoS效果。

0x03 总结

SVG图像更像HTML,而不单单是一张简单的图像。因此,我们建议web开发者尽可能不要以对象或者iframe形式加载任何SVG。Web管理员同样应当限制可以上传到站点的文件类型。

此外,任何不可信的SVG图像在被上传到服务端前都必须经过过滤处理,可以采取如下操作:

  • 限制危险标签,比如script、foreignObject等。
  • 限制通过SVG图像的外部链接加载资源。
  • 限制SVG图像内的扩展逻辑。

我们使用一些浏览器来直接打开这些恶意SVG文件,对比结果如下图所示:

大家可以访问我们的Github仓库下载本文使用的SVG样本。

0x04 参考资料

[1] W3C, “Scalable Vector Graphics” https://www.w3.org/TR/SVG2/ (02 September, 2019)
[2] OWASP, “The Image that called me” https://www.owasp.org/images/0/03/Mario_Heiderich_OWASP_Sweden_The_image_that_called_me.pdf (02 September, 2019)
[3] Blackhat, “Exploiting Browsers without Image Parsing Bugs” https://www.blackhat.com/docs/us-14/materials/us-14-DeGraaf-SVG-Exploiting-Browsers-Without-Image-Parsing-Bugs.pdf (02 September, 2019)

原文链接:https://www.anquanke.com/post/id/190651

我们阅读这篇文章之前,我们需要思考下,我们为什要去了解SVG,阅读了这篇文章是否可以给我们带来帮助。

1. 如果你想要一张 css或者JavaScript可以控制的图片,那么你可以考虑SVG。

2. 如果你期望图片的质量不会因为放大或缩小而降低,那么你可以考虑SVG。

3. 如果你期望网页对残障人士和视力受损的用户有更好的体验,那么你可以考虑SVG。

4. 如果你期望图片在高清设备还是低分辨率设备上,都能保持清晰和细腻的图像质量,那么你可以考虑SVG。

1. 矢量图

我们在学习SVG之前,需要先了解一下位图和矢量图。

简单来说:

  • 位图:由像素点组成的图像,放大图像会失真,canvas就是位图效果。
  • 矢量图:由数学公式(通常是XML)来描述图像,放大图像不会失真。

详细来说:

  • 位图:也称为像素图,是由一个个像素点组成的图像。每个像素点都有特定的颜色和位置,这些信息被编码并存储在文件中。位图的显示效果取决于像素的数量和密度,即分辨率。一般来说,位图主要用于表示细节丰富,颜色和亮度变化多的图像,如照片,自然风景等。
  • 矢量图:使用数学公式(通常是XML)来描述图像,而不是像位图那样使用像素矩阵。这些数据公式决定了图像的形状,线条和填充样式。由于矢量图的每个元素都是由数学公式定义的,因此它们可以在不同尺寸和分辨率下保持清晰度。这种类型的图像在缩放,旋转或以其他方式变换时不会失去细节或者质量。

2. SVG

SVG究竟什么?

是Scalable Vector Graphics的缩写,意思是可缩放矢量图形。

这是一种基于XML的二维矢量图形标准,由W3C开发的。

对于初学者来说,可以将SVG理解为一套新的HTML标签。

所以我们可以使用css和JavaScript来对标签进行操作。

很多小伙伴可能已经忘记了XML,我们回顾一下:
XML(可扩展标记语言)是一种用于描述数据的标记语言,它使用一系列简单的标记来描述数据,这些标记可以用来表示不同类型的数据元素,如标题,作者,价格等。

3. 优势

SVG的优势:

  1. 可扩展性和响应能力:SVG是使用形状,数字和坐标在浏览器中渲染图形,这使得它具有分辨率无关性和无限可伸缩性。不管我们画了什么图形,不论用户将页面放的多大,图形都不会失真,只是比例变化了而已。
  2. 可编程性和交互性:SVG是完全可编辑和脚本可编写的,我们可以通过css和JavaScript将各种动画和交互添加到绘图中。
  3. 无障碍:SVG文件是基于文本的,可以进行搜索和索引,这使得他们可以通过屏幕阅读器,搜索引擎和其他设备阅读。
  4. 性能:与GIF,JPG和PNG相比,SVG通常是较小的文件。

4. 劣势

SVG的劣势:

设计复杂性:SVG需要具备一定的设计和制作技巧,与位图相比,SVG的设计难度较大,需要更高的技术水平。

浏览器兼容性:在一些旧版浏览器中,可能存在对SVG的兼容性问题。

渲染速度:SVG复杂度过高会降低页面渲染速度。

5. 使用方式

对于前端开发的我们,要如何去使用SVG呢?

SVG归根结底来说和JPG,PNG一样,也是一种图像格式,所以我们可以在HTML中,将SVG的路径设置为<img>的src属性。

我们也可以将SVG的代码放在HTML中,我们完全可以把SVG的代码看做成我们的HTML标签。

例如:

<svg width="500" height="500">
    <circle cx="100" cy="100" r="50" fill="transparent" stroke="#000"></circle>
</svg>

我们也可以通过css的background-image或者伪元素,将SVG图片作为背景图像。

6. 使用场景

  1. 图标和徽标:由于SVG可以无损缩放,所以非常适合用来做网站的图标和徽标,无论是在大屏幕还是小屏幕上,SVG都能保证清晰度。
  2. 插画:任何钢笔或铅笔制作的传统绘图,都可以完美转化为SVG格式。
  3. 动态图形:SVG的脚本特性使它可以用来创建动态的,交互式图形。
  4. 之前我做过空调的SVG图片,然后由JavaScript控制空调上的开关,温度,模式。
  5. 地图和图表:SVG可以绘制地图和图表,然后和JavaScript进行数据交互。
  6. 游戏:虽然SVG不是专门用来做游戏的,但是一些游戏开发者会用SVG来制作游戏中的图形。

总的来说,任何需要保持清晰度,动态交互和无损缩放的图形场景,都可以考虑使用SVG。

7. Canvas与SVG

Canvas和SVG都是用于在网页上绘制图形的工具,但它们在许多方面都存在显著的差异。以下是对Canvas和SVG的对比:

  1. 基本概念
  2. Canvas:是HTML5提供的 <canvas>标签,使用JavaScript在网页上绘制图形。它主要通过像素进行渲染,不支持事件处理器。
  3. SVG:全称为可伸缩矢量图形(Scalable Vector Graphics),是基于XML的二维图形语言。它由许多节点构成,每个被绘制的图形都被视为一个对象,如果SVG对象的属性发生变化,浏览器能够自动重现图形。
  4. 特点
  5. Canvas: 依赖分辨率。 不支持事件处理器。 弱的文本渲染能力。 能够以 .png 或 .jpg 格式保存结果图像。 最适合图像密集型的游戏,其中的许多对象会被频繁重绘。
  6. SVG: 基于矢量,能够很好地处理图形大小的改变。 不支持事件处理器。 弱的文本渲染能力。 能够以 .png 或 .jpg 格式保存结果图像。
  7. 性能
  8. Canvas的历史没有SVG悠久,它是H5新增的标签,而SVG已经存在十几年。
  9. SVG图形节点足够多时,渲染会比较慢,而Canvas性能好一些。
  10. 其他差异
  11. Canvas适合带有大型渲染区域的应用程序(如谷歌地图)。
  12. SVG的复杂度高可能会减慢渲染速度(任何过度使用DOM的应用都不快)。

总结:Canvas和SVG各有其优势和适用场景。Canvas更适合图像密集型的游戏和需要高效渲染的应用,而SVG更适合处理矢量图形和大型渲染区域。

8. 实战一

说了这么多,我们来开始实战,我们要在HTML中,画出一个SVG图像。

<html>
<body>
<h1>Feng SVG</h1>

<svg version="1.1"  baseProfile="full"  width="300" height="200"  xmlns="http://www.w3.org/2000/svg">
  <rect width="100%" height="100%" stroke="red" stroke-width="4" fill="yellow" />
  <circle cx="150" cy="100" r="80" fill="green" />
  <text x="150" y="115" font-size="16" text-anchor="middle" fill="white">RUNOOB SVG TEST</text>
</svg> 

</body>
</html>

运行后:

代码解读:

  1. <svg>和</svg>是表示SVG代码,相当于开始标签和结束标签,这是根元素。
  2. width 和 height 属性可设置此 SVG 的宽度和高度。
  3. version 属性可定义所使用的 SVG 版本。
  4. xmlns 属性可定义 SVG 命名空间。
  5. baseProfile 特性描述了作者认为正确渲染内容所需要的最小的 SVG 语言概述。这个特性不会说明任何处理限制,可以把它看作是元数据。
  6. SVG 的 <rect> 用来创建一个矩形,通过 fill 把背景颜色设为黄色。
  7. SVG 的 <circle> 用来创建一个圆。cx 和 cy 属性定义圆中心的 x 和 y 坐标。如果忽略这两个属性,那么圆点会被设置为 (0, 0),r 属性定义圆的半径。 一个半径 80px 的绿色圆圈 <circle> 绘制在红色矩形的正中央 (向右偏移 150px,向下偏移115px)。
  8. stroke 和 stroke-width 属性控制如何显示形状的轮廓。我们把圆的轮廓设置为 4px 宽,红色边框。
  9. fill 属性设置形状内的颜色。我们把填充颜色设置为红色。

9. 实战二

我们还可以在线设计SVG图片,我们可以直接使用该图片或者拷贝其代码复制到我们的代码中。

SVG在线编辑: c.runoob.com/more/svgedi…

10. SVG的基本形状

10.1 矩形

示例一

正常的矩形

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <rect width="300" height="100" style="fill:rgb(122,122,0);stroke-width:2;stroke:rgb(0,0,0)" />
</svg>

效果:

代码解析:

  • <rect>:这是一个矩形元素,用于在SVG图像中绘制矩形。
  • width="300" 和 height="100":这两个属性定义了矩形的宽度和高度。在这个例子中,矩形的宽度是300像素,高度是100像素。
  • style="fill:rgb(122,122,0);stroke-width:2;stroke:rgb(0,0,0)":这设置了矩形的样式。 fill:rgb(122,122,0):设置填充颜色为RGB值(122,122,0)的颜色,这是一种黄色调的颜色。 stroke-width:2:设置线条宽度为2像素。 stroke:rgb(0,0,0):设置线条颜色为RGB值(0,0,0)的颜色,这是一种黑色。

综上,这段代码会在SVG图像中绘制一个宽度为300像素、高度为100像素、填充颜色为黄色调、线条宽度为2像素、线条颜色为黑色的矩形。

示例二:

填充和边框的透明度

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <rect x="50" y="20" width="150" height="150" style="fill:blue;stroke:red;stroke-width:5;fill-opacity:0.5;stroke-opacity:0.5" />
</svg>

效果:

代码解析:

  • <rect>:这是一个矩形元素,用于在SVG图像中绘制矩形。
  • x="50" 和 y="20":这两个属性定义了矩形左上角的坐标(x, y)。在这个例子中,坐标是(50, 20)。
  • width="150" 和 height="150":这两个属性定义了矩形的宽度和高度。在这个例子中,矩形的宽度是150像素,高度是150像素。
  • style="fill:blue;stroke:red;stroke-width:5;fill-opacity:0.5;stroke-opacity:0.5":这设置了矩形的样式。 fill:blue:设置填充颜色为蓝色。 stroke:red:设置线条颜色为红色。 stroke-width:5:设置线条宽度为5像素。 fill-opacity:0.5:设置填充透明度为0.5,这意味着矩形将是半透明的。 stroke-opacity:0.5:设置线条透明度为0.5,这意味着线条将是半透明的。

综上,这段代码会在一个SVG图像中绘制一个左上角坐标为(50, 20)、宽度为150像素、高度为150像素、填充颜色为蓝色、线条颜色为红色、线条宽度为5像素、填充透明度和线条透明度都为0.5的矩形。

示例三:

整个元素的透明度

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500px" height="500px">
  <rect x="50" y="20" width="150" height="150" style="fill:blue;stroke:pink;stroke-width:5;opacity:0.4" />
</svg>

效果:

代码解析:

  • <rect>:这是一个矩形元素,用于在SVG图像中绘制矩形。
  • x="50" 和 y="20":这两个属性定义了矩形左上角的坐标(x, y)。在这个例子中,坐标是(50, 20)。
  • width="150" 和 height="150":这两个属性定义了矩形的宽度和高度。在这个例子中,矩形的宽度是150像素,高度是150像素。
  • style="fill:blue;stroke:pink;stroke-width:5;opacity:0.4":这设置了矩形的样式。 fill:blue:设置填充颜色为蓝色。 stroke:pink:设置线条颜色为粉红色。 stroke-width:5:设置线条宽度为5像素。 opacity:0.4:设置透明度为0.4,这意味着矩形将是半透明的。

综上,这段代码会在一个500x500像素的SVG图像中绘制一个左上角坐标为(50, 20)、宽度为150像素、高度为150像素、填充颜色为蓝色、线条颜色为粉红色、线条宽度为5像素、透明度为0.4的矩形。

示例四:

圆角矩形

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500px" height="500px">
  <rect x="50" y="20" rx="20" ry="20" width="150" height="160" style="fill:red;stroke:black;stroke-width:1;opacity:0.5" />
</svg>

效果:

代码解析:

  • <rect>:这是一个矩形元素,用于在SVG图像中绘制矩形。
  • x="50" 和 y="20":这两个属性定义了矩形左上角的坐标(x, y)。在这个例子中,坐标是(50, 20)。
  • rx="20" 和 ry="20":这两个属性定义了矩形的圆角半径。在这个例子中,矩形的圆角半径是20像素。
  • width="150" 和 height="160":这两个属性定义了矩形的宽度和高度。在这个例子中,矩形的宽度是150像素,高度是160像素。
  • style="fill:red;stroke:black;stroke-width:1;opacity:0.5":这设置了矩形的样式。 fill:red:设置填充颜色为红色。 stroke:black:设置线条颜色为黑色。 stroke-width:1:设置线条宽度为1像素。 opacity:0.5:设置透明度为0.5,这意味着矩形将是半透明的。

综上,这段代码会在一个500x500像素的SVG图像中绘制一个左上角坐标为(50, 20)、宽度为150像素、高度为160像素、填充颜色为红色、线条颜色为黑色、线条宽度为1像素、透明度为0.5的圆角矩形。

10.2 圆形

示例:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
   <circle cx="100" cy="50" r="40" stroke="red" stroke-width="2" fill="blue" />
</svg> 

效果:

代码解析:

  • <circle>:这是一个圆形元素,用于在SVG图像中绘制圆形。
  • cx="100" 和 cy="50":这两个属性定义了圆心的坐标(x, y)。在这个例子中,圆心的坐标是(100, 50)。
  • r="40":这个属性定义了圆的半径,单位是像素。在这个例子中,圆的半径是40像素。
  • stroke="red":这个属性定义了圆的边框颜色。在这个例子中,圆的边框颜色是红色。
  • stroke-width="2":这个属性定义了圆的边框宽度。在这个例子中,圆的边框宽度是2像素。
  • fill="blue":这个属性定义了圆的填充颜色。在这个例子中,圆的填充颜色是蓝色。

综上,这段代码会在一个SVG图像中绘制一个圆心坐标为(100, 50)、半径为40像素、边框颜色为红色、边框宽度为2像素、填充颜色为蓝色的圆形。

10.3 椭圆

示例:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="500" height="500">
  <ellipse cx="300" cy="80" rx="100" ry="50" style="fill:red;stroke:purple;stroke-width:2" />
</svg>

效果:

代码解析:

  • <ellipse>:这是一个椭圆元素,用于在SVG图像中绘制椭圆。
  • cx="300" 和 cy="80":这两个属性定义了椭圆的中心点坐标(x, y)。在这个例子中,中心点坐标是(300, 80)。
  • rx="100" 和 ry="50":这两个属性定义了椭圆的长轴和短轴半径。在这个例子中,长轴半径是100像素,短轴半径是50像素。
  • style="fill:red;stroke:purple;stroke-width:2":这设置了椭圆的样式。 fill:red:设置填充颜色为红色。 stroke:purple:设置线条颜色为紫色。 stroke-width:2:设置线条宽度为2像素。

综上,这段代码会在一个500x500像素的SVG图像中绘制一个中心点坐标为(300, 80)、长轴半径为100像素、短轴半径为50像素的红色椭圆,线条颜色为紫色,线条宽度为2像素。

10.4 线

示例:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <line x1="0" y1="0" x2="200" y2="200" style="stroke:black;stroke-width:5" />
</svg>

效果:

代码解析:

  • <line>:这是一个线段元素,用于在SVG图像中绘制线段。
  • x1="0" 和 y1="0":这两个属性定义了线段的起始点坐标(x, y)。在这个例子中,起始点坐标是(0, 0)。
  • x2="200" 和 y2="200":这两个属性定义了线段的终点坐标(x, y)。在这个例子中,终点坐标是(200, 200)。
  • style="stroke:black;stroke-width:5":这设置了线段的样式。 stroke:black:设置线条颜色为黑色。 stroke-width:5:设置线条宽度为5像素。

综上,这段代码会在一个SVG图像中绘制一条从(0, 0)到(200, 200)的黑色线段,线条宽度为5像素。

11.5 折线

示例一:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <polyline points="20,20 40,25 60,40 80,10 120,140 200,180" style="fill:none;stroke:black;stroke-width:3" />
</svg>

效果:

代码解析:

  • <polyline>:这是一个多边形线条元素,它使用points属性定义多边形的顶点。
  • points="20,20 40,25 60,40 80,10 120,140 200,180":这定义了多边形的顶点。每个顶点由两个坐标值(x和y)组成,它们之间用空格分隔,并且每个顶点之间用空格分隔。例如,20,20定义了一个顶点在(20,20)的位置。
  • style="fill:none;stroke:black;stroke-width:3":这设置了多边形的样式。 fill:none:设置填充颜色为透明(不填充)。 stroke:black:设置线条颜色为黑色。 stroke-width:3:设置线条宽度为3像素。

综上,这段代码会在一个SVG图像中绘制一个黑色的折线。这个折线有6个点,形状大致为一个不规则的六边折线。注意:因为设置了fill:none,所以该多边形线段不会进行填充,只显示其黑色边框。

示例二:

画一个五角星

<svg style="height:300px;width:300px;" xmlns="http://www.w3.org/2000/svg" version="1.1">
    <polyline points="100 10,40 180,190 60,10 60,160 180" style="fill:blue;stroke:blue;stroke-width:1" />
</svg>

效果:

代码解析:

  • <polyline>:这是一个多边形线条元素,它使用points属性定义多边形的顶点。
  • points="100 10,40 180,190 60,10 60,160 180":这定义了多边形的顶点。每个顶点由两个坐标值(x和y)组成,它们之间用空格分隔,并且每个顶点之间用逗号分隔。例如,100 10定义了一个顶点在(100,10)的位置。
  • style="fill:blue;stroke:blue;stroke-width:1":这设置了多边形的样式。 fill:blue:设置填充颜色为蓝色。 stroke:blue:设置线条颜色为蓝色。 stroke-width:1:设置线条宽度为1像素。

综上,这段代码会在一个300x300像素的区域内绘制一个蓝色的多边形。这个多边形有5个顶点,形状大致为一个不规则的五边形。

10.6 多边形

示例一:

<svg  height="210" width="500">
  <polygon points="200,10 250,190 160,210"
  style="fill:red;stroke:purple;stroke-width:1"/>
</svg>

效果:

代码解析:

  • <polygon>:这是一个多边形元素,用于在SVG图像中绘制多边形。
  • points="200,10 250,190 160,210":这个属性定义了多边形的顶点坐标。在这个例子中,多边形有三个顶点:(200,10), (250,190) 和 (160,210)。
  • style="fill:red;stroke:purple;stroke-width:1":这设置了多边形的样式。 fill:red:设置填充颜色为红色。 stroke:purple:设置线条颜色为紫色。 stroke-width:1:设置线条宽度为1像素。

综上,这段代码会在一个500x210像素的SVG图像中绘制一个由三个顶点定义的多边形,填充颜色为红色,线条颜色为紫色,线条宽度为1像素。

示例二:

画一个五角星

<svg style="height:300px;width:300px;" xmlns="http://www.w3.org/2000/svg" version="1.1">
    <polygon points="100 10,40 180,190 60,10 60,160 180" style="fill:none;stroke:black;stroke-width:5"/>
</svg>

效果:

代码解析:

  • <polygon>:这是一个多边形元素,用于在SVG图像中绘制多边形。
  • points="100 10,40 180,190 60,10 60,160 180":这个属性定义了多边形的顶点坐标。在这个例子中,多边形有七个顶点:(100,10), (40,180), (190,60), (10,60), (160,180)。
  • style="fill:none;stroke:black;stroke-width:5":这设置了多边形的样式。 fill:none:设置填充颜色为无色。 stroke:black:设置线条颜色为黑色。 stroke-width:5:设置线条宽度为5像素。

综上,这段代码会在一个300x300像素的SVG图像中绘制一个由七个顶点定义的多边形,没有填充颜色,线条颜色为黑色,线条宽度为5像素。

10.7 路径

路径数据:

  1. M (moveto): 该命令开始一个新的路径,并设置当前点。其后的命令将从这个点开始绘制线条或曲线。
  2. L (lineto): 该命令从当前点绘制一条直线到指定点。
  3. H (horizontal lineto): 该命令从当前点绘制一条水平线到指定x坐标。y坐标保持不变。
  4. V (vertical lineto): 该命令从当前点绘制一条垂直线到指定y坐标。x坐标保持不变。
  5. C (curveto): 该命令绘制一个贝塞尔曲线,需要提供三个点:控制点和结束点。
  6. S (smooth curveto): 该命令绘制一个平滑的贝塞尔曲线,它使用前一个点和当前点作为控制点,并连接到下一个点。
  7. Q (quadratic Bézier curve): 该命令绘制一个二次贝塞尔曲线,需要提供两个点:控制点和结束点。
  8. T (smooth quadratic Bézier curveto): 该命令绘制一个平滑的二次贝塞尔曲线,它使用前一个点和当前点作为控制点,并连接到下一个点。
  9. A (elliptical Arc): 该命令绘制一个椭圆弧,需要提供起始角度、结束角度、半径和旋转角度等参数。
  10. Z (closepath): 该命令关闭路径,将当前点连接到起始点,形成一个封闭图形。

示例:

画一个三角形

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <path d="M150 0 L75 200 L205 270 Z" />
</svg>

效果:

代码解析:

  • <path>:这是一个路径元素,用于绘制线条和曲线。
  • d="M150 0 L75 200 L205 270 Z":这是路径的指令。这些指令定义了图形的形状和位置。 M150 0:移动到(150, 0)点。 L75 200:从当前点绘制一条直线到(75, 200)点。 L205 270:从当前点绘制一条直线到(205, 270)点。 Z:关闭路径,将当前点连接到起始点,形成一个封闭图形。

综上,这段代码将在SVG图像中绘制一个由三个直线段组成的封闭图形,其顶点坐标分别为(150, 0),(75, 200)和(205, 270)。

11. 设置样式

11.1 属性样式

直接在元素属性上设置样式,比如将矩形填充色改成粉红

<svg width="400" height="400" style="border: 1px solid red;">
  <rect
    x="100"
    y="100"
    width="200"
    height="100"
    fill="pink"
  />
</svg>

11.2 内联样式

把所有样式写在 style 属性里

<svg width="400" height="400" style="border: 1px solid red;">
  <rect
    x="100"
    y="100"
    width="200"
    height="100"
    style="fill: pink;"
  />
</svg>

11.3 内部样式

将样式写在 <style> 标签里

<style>
  .rect {
    fill: pink;
  }
</style>

<svg width="400" height="400" style="border: 1px solid red;">
  <rect
    x="100"
    y="100"
    width="200"
    height="100"
    class="rect"
  />
</svg>

11.4 外部样式

将样式写在 .css 文件里,然后在页面中引入该 CSS 文件。

12. SVG动画

SVG动画可以通过多种方式实现,包括使用SMIL、CSS和JavaScript。

12.1 SMIL

能让SVG不靠JavaScript与CSS就能动起来是因为使用了SMIL(Synchronized Multimedia Integration Language),是W3C的标准之一,旨在以XML格式提供多媒体的交互表现(白话点其实就是动画),是Web上动画的开路先锋,启发了Web animation与CSS animation。SVG与SMIL的开发团队合作,让SVG能利用SMIL达到如下效果:

  1. 动画化元素的数值属性
  2. 动画化元素的transform属性(平移、旋转)
  3. 动画化元素颜色
  4. 轨迹路线移动动画,类似于CSS中的offset-path

光是这些特性就够我们组合出很多种的动画了, 使用方法也不难,只要在SVG元素内置入以下四种元素即可操作动画:

  • <set>
  • <animate>
  • <animateTransform>
  • <animateMotion>

例如:

<circle cx=“56.7573”cy=“92.8179”r=“2”fill=“black”stroke=“black”stroke-width=“1”>
    <set attributeName=“cy”to=“105.7318”begin=“2s”/>
</circle>

代码解析:

这段代码用于描述一个圆形,并在特定时间改变其中心点的y坐标。

  1. <circle cx="56.7573" cy="92.8179" r="2" fill="black" stroke="black" stroke-width="1" />
  2. <circle>: 这是一个SVG元素,用于绘制一个圆形。
  3. cx="56.7573" 和 cy="92.8179": 定义了圆心的x和y坐标。这里,圆心的初始位置是(56.7573, 92.8179)。
  4. r="2": 定义了圆的半径为2单位。
  5. fill="black": 定义了圆的填充颜色为黑色。
  6. stroke="black": 定义了圆的边框颜色为黑色。
  7. stroke-width="1": 定义了圆的边框宽度为1单位。
  8. <set attributeName="cy" to="105.7318" begin="2s"/>
  9. <set>: 这是一个SVG动画元素,用于改变元素的属性。
  10. attributeName="cy": 指定要改变的属性是cy,即中心点的y坐标。
  11. to="105.7318": 指定新的属性值,即圆心的y坐标将变为105.7318。
  12. begin="2s": 指定动画开始的时间,这里表示动画将在2秒后开始。

综上,这段代码绘制了一个半径为2单位、填充和边框颜色均为黑色的圆形,并设置了一个动画,使圆心的y坐标在2秒后从92.8179变为105.7318。

12.2 基于 CSS 的 SVG 动画

通过设置一组 CSS 样式和关键帧,可以实现基于时间或基于事件的 SVG 动画。这种方式实现的 SVG 动画相对简单,具有易于实现、可读性好、易于维护、性能良好等优点。

下面是一个基于 CSS 的 SVG 动画示例,实现了一个圆形的旋转动画:

<svg>
  <circle cx="50" cy="50" r="40" />
</svg>

<style>
  circle {
    fill: red;
    animation: rotate 2s linear infinite;
  }
  @keyframes rotate {
    to {
      transform: rotate(360deg);
    }
  }
</style>

12.3 基于 JavaScript 的 SVG 动画

通过 JavaScript,可以对 SVG 图形进行更加自由和复杂的动画操作。JavaScript 可以对 SVG 元素的各种属性,如位置、大小、颜色、透明度、路径等进行操作,配合定时器和事件监听等方法,实现丰富多彩的 SVG动画。

下面是一个基于 JavaScript 的 SVG 动画示例,实现了一个小球自由落体,碰撞弹跳的效果:

<svg>
  <circle id="ball" cx="50" cy="50" r="20" />
</svg>

<script>
  let ball=document.querySelector("#ball");
  let startPos=50;
  let endPos=200;
  let speed=3; // 设置球下落速度
  let gravity=0.2; // 设置加速度

  function moveBall() {
    let pos=parseFloat(ball.getAttribute("cy"));
    let vel=parseFloat(ball.getAttribute("data-vel")) || 0;

    // 计算球的速度和位置
    vel +=gravity;
    pos +=vel * speed;

    // 碰撞检测
    if (pos + 20 > endPos) {
      pos=endPos - 20;
      vel=-vel * 0.8;
    }

    // 更新球的位置和速度
    ball.setAttribute("cy", pos);
    ball.setAttribute("data-vel", vel);

    // 循环移动球
    if (pos < endPos - 20) {
      window.requestAnimationFrame(moveBall);
    }
  }

  moveBall();
</script>

13. API一览表

14. 总结

总的来说,SVG是一种强大的图形描述语言,具有可缩放性、交互性、可访问性、灵活性和跨平台兼容性等特点和优势。它可以用于创建各种复杂的二维矢量图形和富交互的Web应用,为Web设计和开发提供了更多的可能性。

随着Web技术的不断发展,相信SVG的应用范围还将不断扩大。



原文链接:https://juejin.cn/post/7322344486159106100

下这个例子显示了,在html中单击命令按钮设定svg中的矩形的填充颜色,并且调用svg的js函数FunCallByHtmlJs,产生个消息框。

在svg中,单击矩形时,设置html中的text的文本内容,并且调用html的js函数FunCallBySvgJs,产生个消息框。

svg文档以嵌入在html文档中运行。

例子在IE 6.0 + Adobe SVG Viewer 3.03中文版下测试通过。

svg文件的代码:

//文件名:Svg&HtmlInteractive.svg

<svg width="640" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="100%" onload="init(evt)" onclick="Click(evt)">

<script type="text/javascript">

var svgDoc=null ;

var svgRoot=null ;

var parentWnd=null ; //保存html的window对象

//初始化

function init(evt)

{

svgDoc=evt.target.ownerDocument ;

svgRoot=svgDoc.documentElement ; //在html中的第二种交互方式会用到

parentWnd=window.parent ; //ASV 3.0可以这么写。英文6.0版的要换种写法

if(parentWnd.document.title==null || parentWnd.document.title=='')

{

alert("请不要直接在浏览器中打开'svg'文档!");

//下面的代码作用是不提示确认关闭窗口

parentWnd.opener=null ;

parentWnd.open('', '_self') ;

parentWnd.close() ;

}

svgDoc.svgWnd=window ; //这里人为进行设定,以便在html中的第一种交互方式中可以取的到svg的window对象

}

function FunCallByHtmlJs()

{

alert('这个消息框是在html的js中调用svg的js函数产生的。') ;

}

function Click(evt)

{

var id=evt.target.id ;

if(id=='rect') //单击在矩形上,而不是背景上时

{

if(parentWnd)

{

parentWnd.txt.value='在svg中设置html中的text的文本内容' ;

parentWnd.FunCallBySvgJs() ; //调用html中的js函数

}

}

}

</script>

<rect id="background" x="0" y="0" width="100%" height="100%" fill="gray" />

<rect id="rect" x="50" y="50" width="100" height="100" fill="green" />

<text font-family="SimSun" font-size="14" fill="yellow" x="50" y="50" id="text">单击svg的矩形,设置html的text文本内容</text>

</svg>

html文件的代码:

//文件名:Svg&HtmlInteractive.html

<html>

<head>

<title>SVG与html的交互</title>

</head>

<body onload="htmInit()">

<script type=text/javascript>

var svgDoc=null;

var svgRoot=null;

var svgWnd=null; //svg的window对象

function htmInit()

{

txt.value='';

}

function FunCallBySvgJs()

{

alert('这个消息框是在svg的js中调用html的js函数产生的。');

}

function Btn1Clk()

{

//第一种方式

svgDoc=emSvg.getSVGDocument();

if (svgDoc==null) return;

svgRoot=svgDoc.documentElement;

if (svgRoot==null) return;

var rect=svgRoot.getElementById('rect');

if(rect) rect.setAttribute('fill', 'blue');

svgWnd=svgDoc.svgWnd ; //这个window对象是在svg的初始化里面添加进去的

if (svgWnd) svgWnd.FunCallByHtmlJs(); //调用svg里的js函数

}

function Btn2Clk()

{

//第二种方式

svgWnd=emSvg.window;

if(svgWnd==null) return;

svgRoot=svgWnd.svgRoot; //svgRoot在svg的js中是个全局的变量

if(svgRoot==null) return;

var rect=svgRoot.getElementById('rect');

if(rect) rect.setAttribute('fill', 'red');

svgWnd.FunCallByHtmlJs(); //调用svg里的js函数

}

</script>

<input type="button" value="设置svg中矩形的填充颜色为蓝色" onclick="Btn1Clk()" />

<input type="button" value="设置svg中矩形的填充颜色为红色" onclick="Btn2Clk()" />

<input id="txt" type="text" value="" />

<embed id="emSvg" runat="server" src="http://zg672313.blog.163.com/blog/SvgHtmlInteractive.svg" mce_src="http://zg672313.blog.163.com/blog/SvgHtmlInteractive.svg" width="100%" height="95%" wmode="transparent"/>

</body>

</html>

效果图:

另外: 在aspx 页面中,emSvg对象会找不会,应该使用 document.getElementById("emSvg") 来查找 SVG对象