SVG与JavaScript实用指南
1. SVG变换与结构元素
1.1 SVG变换
SVG的所有变换都可以用以下几种形式表示:
-
translate(u,v) = matrix(1, 0, 0, 1, u, v)
-
scale(g,h) = matrix(g, 0, 0, h, 0, 0)
-
rotate(q) = matrix(cos(q), sin(q), -sin(q), cos(q), 0, 0 )
-
rotate(q,u,v) = translate(u,v) rotate(q) translate(-u,-v)
-
skewX(q) = matrix(1, 0, tan(q), 1, 0, 0)
-
skewY(q) = matrix(1, tan(q), 0, 1, 0, 0)
SVG变换的原点(对于旋转和缩放操作很重要)默认是SVG自身的原点,位于左上角。但当
transform
属性用于最外层的
<svg>
元素时,它会被解释为CSS变换,此时以元素中心为原点。
1.2 SVG结构元素
SVG定义了一些结构元素,这些元素本身不渲染,仅用于组织文档中的其他元素,如下表所示:
| 元素 | 描述 |
| ---- | ---- |
|
<svg>
| 是一个容器,用于界定SVG文档或文档片段,由
<svg>
元素界定的文档片段可直接包含在HTML5文档中。 |
|
<g>
| 将元素组合成一个复合元素,可作为一个单元进行变换或重用,
<g>
上定义的任何表示属性都会被其子元素继承。 |
|
<defs>
| 是一个容器元素,可用于收集文档内的样式或可重用组件的定义,建议使用,但不是必需的。 |
|
<use>
| 引用另一个元素,并在
<use>
元素的位置渲染该元素的副本,被引用的元素可以是容器元素,此时其整个内容都会被复制。 |
以下是一个示例,展示了如何在SVG文档中一起使用这些元素:
<defs>
<g id="doublecircle">
<circle cx="0" cy="0" r="3" fill="red" />
<circle cx="0" cy="0" r="5" fill="none" stroke="red" />
</g>
</defs>
<use x="10" y="20" href="#doublecircle" />
1.3 SVG坐标、缩放和渲染
默认情况下,SVG图形的原点位于左上角,水平轴从左到右,垂直轴向下。SVG中的长度通常指定为无单位的纯数字,一个单位相当于一个像素。CSS中常见的其他长度单位(如em、ex、px、pt、cm、mm和in)也可用,但很少使用。当前CSS和SVG标准将像素密度固定为96 dpi,从而定义了一个像素的物理长度。
SVG图形原则上是无限大的,
<svg>
标签上设置的
width
和
height
属性并不定义图形的大小,而是定义视口的大小。视口是一个给定大小的区域,SVG图形的部分或全部将在其中显示,本质上是整个SVG的一个“窗口”。
默认情况下,视口将显示其正下方的SVG图像部分,图形的原点位于视口的左上角,视口和图形使用相同的长度单位。要在视口中显示整个SVG图像的特定部分,必须显式设置
<svg>
元素的
viewBox
属性。使用
viewBox
属性,可以指定整个图形的一个矩形区域,然后将其缩放以适应视口。这个映射(从
viewBox
指定的区域到视口的可见区域)的细节由
preserveAspectRatio
属性控制,该属性不仅控制纵横比,还控制所选区域在视口内的对齐方式。
1.4 SVG与CSS
SVG元素可以使用CSS机制和语法进行样式设置,所有表示属性都可以通过样式设置。CSS样式指令可以包含在SVG文档中,使用
<defs>
部分内的
<style>...</style>
标签,示例如下:
<svg>
<defs>
<style>
circle { fill: blue }
.strong { fill: red }
</style>
</defs>
<circle cx="100" cy="100" r="5" />
<rect class="strong" x="200" y="200" width="50" height="20" />
</svg>
也可以链接外部样式表,例如在HTML页面的
<head>...</head>
部分包含如下代码:
<link href="styles.css" rel="stylesheet">
最后,内联样式指令也是合法的,但较少使用,如:
<circle style="fill: green" />
1.5 SVG资源
Mozilla开发网络(MDN)上的教程包含更多细节,推荐作为入门参考:MDN SVG Tutorial。在MDN上,还可以找到元素参考(MDN SVG Element Reference)和属性参考(MDN SVG Attribute Reference)。SVG 2草案标准本身也很易读,是权威的信息来源。
2. JavaScript基础与特性
2.1 JavaScript概述
如果你使用过C、C++、C#和Java系列的语言,那么JavaScript的语法会让你感觉熟悉。如果你之前使用过任何动态类型语言(如Perl、Python或Ruby),那么JavaScript的大部分语义也会很熟悉。但JavaScript也有一些容易让人意外的特性。
2.2 JavaScript特性
2.2.1 宿主语言
JavaScript最初设计为在宿主环境(特别是浏览器)中运行,这意味着操作系统通常提供的许多服务不可用,特别是文件系统和标准输出通道无法访问,但可以访问网络。Node.js项目的目的是提供一个独立的JavaScript运行时环境,使其可以在宿主环境之外(即“在服务器上”)运行。
宿主环境提供了许多服务,其中全局
console
对象特别实用,它让程序员可以访问开发者控制台。不同浏览器打开开发者控制台的方式不同,常见的是右键单击页面并选择“检查”。使用
console.log()
可以将数据结构和其他信息输出到屏幕,
console
对象还提供了其他有用的功能,如格式化输出和简单的性能分析定时器。
在网页浏览器中运行的所有代码都会自动创建一个DOM Window实例,可以通过全局
window
变量访问。
2.2.2 分号插入
JavaScript会尝试插入分号来终止语句,但控制此功能的精确规则很复杂,通常建议不要依赖分号插入,而是在适当的地方显式插入分号。
2.2.3 基本数据类型
-
字符串
:可以用单引号或双引号括起来,两种引号完全等效,且可以相互嵌套,如
"That's good"或'Say "Go"'。单引号和双引号字符串都支持常见的转义序列,没有单独的字符类型,+运算符用于连接字符串。 -
空值
:有两种不同类型表示“空”或“无”:
null和undefined。undefined仅用于表示缺少初始化(如变量声明、不存在的函数参数和对象属性,以及没有显式返回语句的函数的返回值);null通常用于表示合法值的缺失。 -
数字
:JavaScript数字是标准的IEEE 754双精度浮点数,支持特殊值
NaN。
2.2.4 布尔表达式
以下表达式在布尔上下文中求值为
false
(即“假值”):
0
、
""
、
false
、
null
、
undefined
、
NaN
。所有其他值,包括空数组和空对象,都求值为
true
(即“真值”)。布尔运算符会短路,整个布尔表达式的返回值是最后求值的表达式。
2.2.5 类型转换
JavaScript在混合表达式中会进行类型转换,但隐式转换规则复杂、不一致且容易出错。
+
运算符的行为与其他二元运算符不同,最终结果取决于参数的顺序。二元运算符
-
、
*
、
/
和
%
会先将两个参数转换为数字,即使两个参数都是字符串。而
+
运算符如果至少有一个参数是字符串,则会将两个参数都转换为字符串。如果有多个
+
运算符,表达式将从左到右求值,因此参数顺序很重要。当用作一元(前缀)运算符时,
+
和
-
会将后面的字符串转换为数字。示例如下:
"1" - "2" // 求值为 -1 (数字)
1 + "-2" // 求值为 "1-2" (字符串)
1 + +"-2" // 求值为 -1 (数字)
1 + 2 + "3" // 求值为 "33" (字符串)
1 + "2" + 3 // 求值为 "123" (字符串)
1 + 2 + +"3" // 求值为 6 (数字)
1 + 2 + -"3" // 求值为 0 (数字)
在布尔上下文中求值为
true
的表达式在数字上下文中的值为
1
,求值为
false
的表达式在数字上下文中的值为
0
,但
undefined
转换为
NaN
。
2.2.6 相等比较
有两种运算符用于测试相等或不相等:
-
普通比较
:使用
==
和
!=
,会在比较前对参数应用转换规则。
-
严格比较
:使用
===
和
!==
,只有当参数类型相同且值相等时才认为相等。建议只使用严格比较
===
和
!==
。
2.2.7 变量声明和作用域
未使用任何限定符声明的变量会自动成为全局变量。
var
关键字用于将变量的作用域限制在包含它的函数内。需要注意的是,在JavaScript中,相关作用域由包含它的函数定义,而不是包含它的代码块。例如:
function f(x) {
var y = ...;
if( ... ) {
var z = ...;
}
}
在这个例子中,
y
和
z
在整个函数中都可见,变量
z
的声明会被提升到函数顶部,但只有在条件块内才会初始化。
声明和初始化多个变量通常有两种形式:
var a = 1,
b = 2;
// 也可以写在一行:var a = 1, b = 2;
或者
var a = 1;
var b = 2;
在同一个函数(即相同作用域)内多次声明同名变量是合法的,但后续语句中的
var
关键字会被忽略,不会声明新变量,只是为现有变量赋新值。
JavaScript语言的最新版本引入了
let
(用于声明块级变量)和
const
(用于声明常量值)关键字。
2.2.8 函数
定义函数有两种方式:
-
函数声明
:
function f(x) { ... }
- 函数表达式 :
var f = function(x) { ... };
函数表达式不需要赋值给变量,可以用作匿名函数(如定义回调函数)。即使定义为匿名函数,也可以给表达式命名,这个名称仅在函数体内可见,这对于定义递归匿名函数很有用,例如:
promise.then( function f(x) { ...;
f(x); // 递归调用
...; } );
函数是对象,因此可以为函数添加属性:
function f(x) {
f.author = "Joe Doe";
...;
}
所有函数参数都是可选的,未提供的参数值为
undefined
。在函数体内,所有提供的参数也可以在一个类似数组的只读数据结构
arguments
中访问,可以使用
arguments.length
获取提供的参数数量,并通过索引访问每个参数,如
arguments[0]
。
return
关键字用于从函数返回值,没有显式返回语句(或空返回)的函数返回
undefined
。JavaScript支持闭包。
2.2.9
this
变量
this
变量在每个函数体内自动可用。如果函数作为方法调用(即作为某个对象的成员函数),则
this
指向该对象(即该方法调用的接收者);如果函数作为非成员函数调用,则
this
指向全局对象。
this
变量在每个函数作用域中定义,因此嵌套函数定义会重置(或覆盖)
this
变量。这在成员函数内部定义回调和其他嵌套函数时会成为问题,例如:
var obj = {
a: "Hello!",
f: function() {
console.log(this.a); // Hello!
console.log(function() { return this.a; }()); // undefined
var that = this;
console.log(function() { return that.a; }()); } // Hello!
};
在第二个
console.log()
调用中,定义并立即执行了一个匿名函数,由于该匿名函数定义了自己的作用域,
this
被重新赋值,不再指向外部对象,通常的解决方法是将
this
的值保存到另一个变量(通常命名为
that
)中。
JavaScript提供了一些“合成”函数调用的工具,这些工具接受一个额外的参数来填充
this
变量,可查看
call()
、
apply()
和
bind()
的参考文档。
2.2.10 箭头函数
箭头函数(或“胖箭头表示法”)是JavaScript的新特性(2015年发布的ES6引入),用于简化小型匿名函数的定义,其语法要点如下:
- 用括号括起参数,用花括号括起函数体:
(a, b, c) => { statements }
- 如果只有一个参数,括号可选:
a => { statements }
- 如果没有参数,使用空括号:
() => { statements }
- 如果函数体由单个表达式组成,则花括号和
return
语句可选,表达式的值将作为函数的返回值:
(a, b, c) => expression;
箭头函数没有自己的
this
或
arguments
变量,而是从包含它的作用域继承这两个变量的值。因此,在需要
this
的情况下不能使用箭头函数,如果需要
this
,必须使用
function
关键字来定义函数或回调。
2.2.11 集合数据类型
-
数组
:传统上,JavaScript没有真正的集合类型,数组实际上是一个对象,其键限制为整数的字符串表示。数组支持许多实现典型数组操作的方法(如
push()、pop()、splice()、sort()等),数组元素的数量作为成员变量length可用(不是函数)。
删除数组元素(如
delete a[1]
)不会缩小集合的整体长度,而是会创建一个“空洞”,不同的JavaScript工具对空洞的处理不一致。正确的方法是使用
splice()
函数删除数组元素而不留下空洞(
delete
运算符用于删除对象的元素)。
数组的负索引不像在Perl、Python、Ruby等语言中那样从数组末尾开始计数,而是被解释为字符串。为负索引赋值(如
a[-1] = 3;
)会向数组对象添加一个名为
"-1"
的新属性。要从数组末尾访问元素,可以使用
slice()
和
splice()
函数(分别用于读取和写入访问)。
-
字典
:JavaScript也没有合适的字典(或哈希表)类型,而是使用对象来实现。主要问题是对象除了显式分配的键之外,还自动包含许多内置的继承函数和成员,如果不小心使用了已经存在的属性名,可能会导致问题。
JavaScript的最新版本包含了合适的
Map
数据类型,D3也包含了一些集合和容器数据类型,以及一些处理数组的实用函数。
2.2.12 对象
创建对象(实例)的首选方式是通过对象字面量,例如:
var obj = { a: 1,
b: [ 1, 2, 3 ],
c: function(x) { return x*x }
};
注意对象字面量的特定语法(键和值之间用冒号,不同条目之间用逗号),缺少逗号(或用分号代替逗号)可能会导致难以理解的解析错误。
对象的成员可以通过点号表示法和方括号表示法访问,两种形式等效,但点号表示法要求键是有效的JavaScript标识符(由字母数字字符、
_
和
$
组成,且不以数字开头),而方括号表示法允许任何字符串,例如:
obj.a += 2; OR obj["a"] += 2;
obj.f(3); OR obj["f"](3);
JavaScript对象和类型不是“封闭”的,它们的成员和接口可以动态更改,因此可能会找到一个标准类型(如数组)的对象,但它有额外的成员。
JavaScript的对象、类型和继承模型与其他主流编程语言不同,但D3 API有效地封装了大部分底层的生命周期和层次结构管理,使得可以相对安全和容易地忽略这些不熟悉的方面。
2.2.13 数学函数
常见的数学函数可以作为全局
Math
对象的成员访问,例如:
Math.PI;
Math.sin(0.1);
2.2.14 不可用特性
JavaScript或其标准库中没有一些其他编程语言中常见的特性,这导致了大量第三方库和框架的出现。仅使用JavaScript而不使用任何此类库或框架的程序被称为Vanilla JavaScript(或开玩笑地称为使用“VanillaJS框架”)。
JavaScript几乎没有封装和模块化机制,有时会使用立即调用的函数表达式(IIFE)形式的闭包来创建局部作用域。
JavaScript不支持线程,而是将事件放入队列中并按顺序处理。为了不阻塞用户界面功能,处理长时间运行的任务(特别是I/O)时必须异步进行(通过回调和Promise)。
JavaScript没有像
printf()
和类似函数那样的格式化输出功能,D3为数值和日期/时间值提供了替代功能。
2.3 安全地在字符串和数字之间转换
在处理从文件读取数据的程序中,字符串和数字之间的转换是常见任务,JavaScript提供了几种方法,各有不同的权衡。
2.3.1 字符串转数字
-
Number(str):如果变量str去除首尾空格后能精确表示为一个数字,则返回该数字;如果遇到无法转换的字符,则返回NaN。可转换的字符串如"123"、" 123 "、"1.0"和"1.e3",不可转换的字符串如"123a"和"1-2"。空字符串、仅包含空格的字符串和null值转换为0,布尔值true和false分别转换为1和0。 -
parseFloat(str):这是一个全局函数,去除前导空格后,将字符串转换为等效的数值,直到遇到无法转换的字符,并返回该点之前的值。如果根本没有进行转换,则返回NaN,例如"123a"转换为123。布尔值、空字符串和null值都转换为NaN。 -
+str:一元前缀运算符+的行为与Number()函数相同。在算术表达式中,它的优先级高于其他算术运算符,例如3 * +"2"求值为6。一元前缀运算符-的行为与+类似,但会改变数值结果的符号。
注意,
Number()
对特殊值(如空字符串、
null
和布尔值)更宽容,而
parseFloat()
对尾随的不可转换字符更宽容。所有三种方法都将
undefined
转换为
NaN
。
2.3.2 数字转字符串
-
String(num):返回其参数的字符串表示,注意没有字符串格式化功能,例如无法限制小数点后的位数。null、true和false值分别转换为字符串"null"、"true"和"false"。 -
"" + num或'' + num:是String(num)的等效简写。 -
num.toString():如果num是一个既不是null也不是undefined的变量,则toString()成员函数将返回该值的字符串表示。尝试对数字字面量调用toString()(如1.toString())会导致语法错误,对null或undefined调用toString会导致运行时错误。NaN作为接收值是允许的,结果为"NaN"。
JavaScript没有像
printf()
系列函数那样的格式化输出功能,但D3提供了替代功能。
另外,有两个需要注意的地方:
- 不要将
Number()
或
String()
函数与
new
关键字一起使用,这样不会创建基本类型(数字或字符串),而是创建具有不同行为的“包装对象”。
-
parseInt(str, radix)
函数将字符串转换为整数,使用指定的基数(必须在2到36之间)。该函数在遇到无法转换的字符时停止,因此如果字符串采用指数表示法(由于指数表示法中的
e
),可能会给出不正确的结果。
最后,关于对象转换为基本类型(字符串或数字):
-
valueOf()
:所有对象都继承了
valueOf()
方法,它将接收对象转换为基本值。如果对象表示一个数字,则
valueOf()
返回相应的数字类型;否则,返回对象本身(在算术表达式中使用后者的值会导致表达式的值为
NaN
)。
valueOf()
函数通常不会由程序员代码显式调用。
-
toString()
:所有对象也都有
toString()
方法,通常隐式调用以获取对象的字符串表示。但其默认实现不会返回唯一标识对象的描述性字符串,而是返回一个通用常量,因此不能直接将对象用作哈希表的键。
-
JSON.stringify()
:获取任意对象的格式化字符串表示的最简单方法是使用
JSON.stringify()
。
3. 总结与操作建议
3.1 SVG操作总结
3.1.1 变换操作
SVG的变换操作可以通过特定的函数来实现,以下是常见变换的总结:
| 变换类型 | 函数表示 |
| ---- | ---- |
| 平移 |
translate(u,v) = matrix(1, 0, 0, 1, u, v)
|
| 缩放 |
scale(g,h) = matrix(g, 0, 0, h, 0, 0)
|
| 旋转 |
rotate(q) = matrix(cos(q), sin(q), -sin(q), cos(q), 0, 0 )
rotate(q,u,v) = translate(u,v) rotate(q) translate(-u,-v)
|
| 水平倾斜 |
skewX(q) = matrix(1, 0, tan(q), 1, 0, 0)
|
| 垂直倾斜 |
skewY(q) = matrix(1, tan(q), 0, 1, 0, 0)
|
3.1.2 结构元素使用
SVG的结构元素用于组织文档,以下是使用步骤:
1.
<svg>
元素
:作为容器界定SVG文档或片段,可直接包含在HTML5文档中。
2.
<g>
元素
:将元素组合成复合元素,便于整体变换和重用,其属性会被子元素继承。
<g id="group1">
<circle cx="50" cy="50" r="10" fill="blue" />
<rect x="60" y="40" width="20" height="20" fill="red" />
</g>
-
<defs>元素 :收集样式或可重用组件定义,虽非必需但建议使用。
<defs>
<style>
.shape { fill: green; }
</style>
</defs>
-
<use>元素 :引用其他元素并渲染副本。
<use x="100" y="100" href="#group1" />
3.1.3 坐标与视口设置
- 默认坐标 :原点在左上角,水平轴从左到右,垂直轴向下。
-
视口设置
:
<svg>的width和height定义视口大小,viewBox指定显示的图形区域,preserveAspectRatio控制映射细节。
<svg width="200" height="200" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid meet">
<circle cx="50" cy="50" r="20" fill="yellow" />
</svg>
3.1.4 样式设置
-
内部样式
:使用
<defs>中的<style>标签设置样式。
<svg>
<defs>
<style>
circle { stroke: black; stroke-width: 2; }
</style>
</defs>
<circle cx="150" cy="150" r="30" fill="orange" />
</svg>
-
外部样式表
:在HTML的
<head>中链接样式表。
<head>
<link href="styles.css" rel="stylesheet">
</head>
-
内联样式
:直接在元素中使用
style属性。
<rect style="fill: purple; stroke: white; stroke-width: 1" x="200" y="200" width="50" height="50" />
3.2 JavaScript操作总结
3.2.1 基本语法与特性
-
数据类型
:包括字符串、数字、布尔值、
null、undefined等,注意类型转换规则。 -
变量声明
:
var用于函数作用域,let用于块级作用域,const用于常量。
function example() {
var a = 10;
if (true) {
let b = 20;
const c = 30;
}
}
- 函数定义 :有函数声明和函数表达式两种方式。
// 函数声明
function add(a, b) {
return a + b;
}
// 函数表达式
var subtract = function(a, b) {
return a - b;
};
3.2.2 重要变量与对象
-
this变量 :根据函数调用方式指向不同对象,嵌套函数会重置this,可使用that保存值。
var person = {
name: "John",
sayHello: function() {
var that = this;
setTimeout(function() {
console.log("Hello, " + that.name);
}, 1000);
}
};
person.sayHello();
-
arguments对象 :在函数内部可访问所有参数,是只读的类数组对象。
function sum() {
var total = 0;
for (var i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
console.log(sum(1, 2, 3));
3.2.3 集合与对象操作
-
数组操作
:使用
splice()删除元素避免空洞,负索引不能从末尾计数。
var arr = [1, 2, 3, 4];
arr.splice(1, 1); // 删除索引为1的元素
console.log(arr);
- 对象操作 :使用对象字面量创建对象,成员可通过点号或方括号访问。
var obj = {
prop1: "value1",
prop2: function() {
console.log("Hello");
}
};
console.log(obj.prop1);
obj.prop2();
3.2.4 类型转换操作
-
字符串转数字
:可使用
Number()、parseFloat()、+运算符。
var str = "123";
var num1 = Number(str);
var num2 = parseFloat(str);
var num3 = +str;
console.log(num1, num2, num3);
-
数字转字符串
:可使用
String()、"" + num、num.toString()。
var num = 456;
var str1 = String(num);
var str2 = "" + num;
var str3 = num.toString();
console.log(str1, str2, str3);
3.3 操作流程总结
下面是一个SVG与JavaScript结合使用的操作流程mermaid图:
graph LR
A[创建SVG元素] --> B[定义结构元素和样式]
B --> C[添加图形元素]
C --> D[设置变换和视口]
D --> E[使用JavaScript操作SVG]
E --> F[绑定事件和处理逻辑]
这个流程展示了从创建SVG到使用JavaScript进行交互的基本步骤,通过遵循这些步骤,可以更好地实现SVG和JavaScript的协同工作。在实际应用中,可以根据具体需求对每个步骤进行调整和扩展。
超级会员免费看
153

被折叠的 条评论
为什么被折叠?



