向Web中添加矢量图形
向Web中添加矢量图形
矢量图形在网页设计中非常有用,它们不仅文件尺寸小,而且可以无限缩放而不失真。本文将详细介绍如何在网页中添加矢量图形,特别是SVG(可缩放矢量图形)。
前提:你需要了解HTML的基本知识,并知道如何在文档中插入图片。
目标:了解如何在网页中嵌入SVG(矢量)图形。
什么是矢量图形?
在Web上,你会遇到两种类型的图片:位图和矢量图:
位图使用像素网格来定义——位图文件精确包含每个像素的位置和它的色彩信息。流行的Web位图格式包括Bitmap(.bmp)、PNG(.png)、JPEG(.jpg)以及GIF(.gif)。
矢量图使用算法来定义——矢量图文件包含了图形和路径的定义,电脑可以根据这些定义计算出当它们在屏幕上渲染时应该呈现的样子。借助SVG格式,我们可以创造用于Web的精彩矢量图形。
为了让你清楚地认识到两者的区别,我们来看一个例子。你可以在我们的GitHub仓库中在线查看这个例子:vector-versus-raster.html——它并排展示了两个看起来一致的图像,两个图像都是一个红色的五角星以及黑色的阴影。不同的是,左边的是PNG,而右边的是SVG图像。
如果你放大网页,区别就会变得明显起来——随着你的放大,PNG图片显现出了像素块,因为它存储是每个像素的颜色和位置信息。当它放大时,每个像素就被放大然后填满屏幕上更多的像素,所以图像就会开始变得像马赛克。然而矢量图像看起来仍然效果很好且清晰,因为无论它的尺寸如何,系统都使用算法来计算出图像的形状,仅仅是根据放大的倍数来调整算法中的值。
备注:上面的图片实际上都是PNG图片——每个例子中左边的星星图片代表位图,右边的星星图片代表矢量图。还有,请访问vector-versus-raster.html示例来查看真正的例子!
此外,相较于同样的位图,矢量图形通常体积更小,因为它们仅需储存少量的算法,而不是逐个储存每个像素的信息。
SVG是什么?
SVG是用于描述矢量图像的语言,它基于XML。它基本上是像HTML一样的标记,只是它提供了许多不同的元素来定义要显示在图像中的形状,以及要应用于这些形状的效果。SVG用于标记图形,而不是内容。SVG定义了一些用于创建基本图形的元素,如<circle>
和<rect>
,此外SVG还提供了一些复杂一些的元素如<path>
和<polygon>
。更高级的SVG特性包括<feColorMatrix>
(使用变换矩阵转换颜色)、<animate>
(矢量图形的动画部分)和<mask>
(在图像上层应用蒙版)
以下是一个简单的示例,在示例中我们创建一个圆和一个矩形:
<svg
version="1.1"
baseProfile="full"
width="300"
height="200"
xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="black" />
<circle cx="150" cy="100" r="90" fill="blue" />
</svg>
这将创建以下输出:
从上面的例子可以看出,SVG很容易手工编码。是的,你可以在文本编辑器中手动编写简单的SVG,但是随着图像复杂度提升,手动编码很快就开始变得非常困难。为了创建SVG图像,大多数人使用矢量图形编辑器,如Inkscape或Illustrator。借助这些软件包,你可以使用各种图形工具创建插图,也可以创建近似照片的矢量图(例如Inkscape的跟踪位图功能)。
SVG除了前面描述的以外还有其他优点:
- 矢量图像中的文本仍然可访问(这也有利于SEO)。
- SVG可以很好地适应样式/脚本,因为图像的每个组件都是可以通过CSS或通过JavaScript设置样式的元素。
那么为什么会有人想使用位图而不是SVG?好吧,其实SVG也有一些缺点:
- SVG非常容易变得复杂,这意味着文件大小会增加;复杂的SVG也会占用浏览器很长的处理时间。
- SVG可能比位图更难创建,具体取决于你尝试创建哪种图像。
由于上述原因,像照片那样复杂精密的图像更适合使用位图。
备注:在Inkscape中,可以将文件保存为纯SVG以节省空间。另请参阅如何为Web准备SVG。
将SVG添加到页面
在本节中,我们将介绍将SVG矢量图形添加到Web页面的不同方式。
要通过<img>
元素嵌入SVG,你只需要按照预期的方式在src属性中引用它。你将至少需要height或width属性中的一个(或者如果你的SVG没有固有的宽高比,则都需要)。如果你还没使用过img元素,请阅读HTML中的图片教程。
<img src="equilateral.svg" alt="等边三角形" height="87px" width="100px" />
优点
- 快速且熟悉的图像语法,内置的文本等效内容可通过alt属性获取。
- 通过将
<img>
嵌入到<a>
元素中,你可以轻松地将图像转换为超链接。 - 浏览器能够缓存SVG文件,这意味着在未来使用该图像的页面将加载得更快。
缺点
- 无法使用JavaScript操作图像。
- 如果要使用CSS控制SVG内容,则必须在SVG代码中包含内联CSS样式。(从SVG文件内部调用的外部样式表不起作用)
- 不能用CSS伪类来重设图像样式(如
:focus
)。
问题排查和跨浏览器支持
对于不支持SVG(IE 8及更低版本,Android 2.3及更低版本)的浏览器,你可以用src属性引用PNG或JPG,并使用srcset属性(只有较新的浏览器才能识别)来引用SVG。在这种情况下,仅支持SVG的浏览器会加载SVG——较旧的浏览器将加载PNG:
<img src="equilateral.png" alt="等边三角形" srcset="equilateral.svg" />
你还可以使用SVG作为CSS背景图像,如下所示。在下面的代码中,旧版浏览器会加载它们能够理解的PNG,而较新的浏览器将加载SVG:
background: url("fallback.png") no-repeat center;
background-image: url("image.svg");
background-size: contain;
与上文所述的<img>
方法一样,使用CSS背景图片插入SVG意味着SVG无法使用JavaScript进行操作,并且同样受到相同的CSS限制。
如果SVG根本没显示,可能是因为你的服务器设置不正确。如果是这个问题,这篇文章将为你指出正确方向。
如何在HTML中引入SVG代码
你还可以在文本编辑器中打开SVG文件,复制SVG代码,并将其粘贴到HTML文档中——这有时称为将SVG内联或内联SVG。请确保你的SVG代码始于<svg>
标签并终于</svg>
标签。以下是一个非常简单的示例,你可以粘贴到文档中:
<svg width="300" height="200">
<rect width="100%" height="100%" fill="green" />
</svg>
优点
- 将SVG内联会减少HTTP请求,可以减少加载时间。
- 你可以为SVG元素分配class和id,并使用CSS修改样式,无论是在SVG中,还是HTML文档中的CSS样式规则。实际上,你可以使用任何SVG外观属性作为CSS属性。
- 内联SVG是唯一可以让你在SVG图像上使用CSS交互(如
:focus
)和CSS动画的方法(即使在常规样式表中)。 - 你可以通过将SVG标记包在
<a>
元素中,使其成为超链接。
缺点
- 这种方法只适用于SVG在单个地方使用的情况。多次使用会导致资源密集型维护(resource-intensive maintenance)。
- 额外的SVG代码会增加HTML文件的大小。
- 浏览器不能像缓存普通图像资源那样缓存内联SVG,因此包含该图像的页面在加载第一个包含该图像的页面后,加载速度不会更快。
- 你可能会在
<foreignObject>
元素中包含回退,但支持SVG的浏览器仍然会下载所有后备图像。你需要考虑仅仅为支持过时的浏览器,而增加额外开销是否真的值得。
你可以在浏览器中打开SVG图像,就像网页一样。因此,使用<iframe>
嵌入SVG文档就像我们在从<object>
到<iframe>
——其他嵌入技术中学习的一样。以下是一个快速回顾:
<iframe src="triangle.svg" width="500" height="500" sandbox>
<img src="triangle.png" alt="Triangle with three unequal sides" />
</iframe>
这绝对不是最好的方法:
缺点
- 如你所知,
iframe
有一个回退机制,如果浏览器不支持iframe
,则只会显示回退。 - 此外,除非SVG和你当前的网页具有相同的来源,否则你不能在主页面上使用JavaScript来操纵SVG。
动手学习:使用SVG
在本动手学习部分中,我们希望你能够体验一下SVG的乐趣。在下面的输入部分,你会看到我们已经提供了一些样例供你使用。你还可以访问SVG元素参考,了解更多SVG可以玩的细节,当然也可以尝试一下。本部分都是为了锻炼你的研究技巧,并且有一些乐趣。
如果你遇到了困难,无法使你的代码工作,你可以随时使用重置按钮进行重置。
<h2>实时输出</h2>
<div class="output" style="min-height: 50px;"></div>
<h2>可编辑代码</h2>
<p class="a11y-label">按 ESC 退出编辑区域,按 Tab 可插入制表符</p>
<textarea id="code" class="input" style="width: 95%;min-height: 200px;">
<svg width="100%" height="100%">
<rect width="100%" height="100%" fill="red" />
<circle cx="100%" cy="100%" r="150" fill="blue" stroke="black" />
<polygon points="120,0 240,225 0,225" fill="green"/>
<text x="50" y="100" font-family="Verdana" font-size="55"
fill="white" stroke="black" stroke-width="2">
Hello!
</text>
</svg>
</textarea>
<div class="playable-buttons">
<input id="reset" type="button" value="重置" />
<input id="solution" type="button" value="显示答案" disabled />
</div>
html {
font-family: sans-serif;
}
h2 {
font-size: 16px;
}
.a11y-label {
margin: 0;
text-align: right;
font-size: 0.7rem;
width: 98%;
}
body {
margin: 10px;
background: #f5f9fa;
}
const textarea = document.getElementById("code");
const reset = document.getElementById("reset");
const solution = document.getElementById("solution");
const output = document.querySelector(".output");
let code = textarea.value;
let userEntry = textarea.value;
function updateCode() {
output.innerHTML = textarea.value;
}
reset.addEventListener("click", function () {
textarea.value = code;
userEntry = textarea.value;
solutionEntry = htmlSolution;
solution.value = "显示答案";
updateCode();
});
solution.addEventListener("click", function () {
if (solution.value === "显示答案") {
textarea.value = solutionEntry;
solution.value = "隐藏答案";
} else {
textarea.value = userEntry;
solution.value = "显示答案";
}
updateCode();
});
const htmlSolution = "";
let solutionEntry = htmlSolution;
textarea.addEventListener("input", updateCode);
window.addEventListener("load", updateCode);
// 防止 Tab 键使 textarea 失去焦点,
// 转而使其在当前光标位置插入一个制表符
textarea.onkeydown = function (e) {
if (e.code === "Tab") {
e.preventDefault();
insertAtCaret("\t");
}
if (e.code === "Escape") {
textarea.blur();
}
};
function insertAtCaret(text) {
const scrollPos = textarea.scrollTop;
let caretPos = textarea.selectionStart;
const front = textarea.value.substring(0, caretPos);
const back = textarea.value.substring(
textarea.selectionEnd,
textarea.value.length,
);
textarea.value = front + text + back;
caretPos += text.length;
textarea.selectionStart = caretPos;
textarea.selectionEnd = caretPos;
textarea.focus();
textarea.scrollTop = scrollPos;
}
// 每次用户更新文本区域代码时,更新已保存的用户代码
textarea.onkeyup = function () {
// 我们只希望在显示用户代码时保存状态,
// 而不保存答案,以防止答案覆盖用户代码
if (solution.value === "Show solution") {
userEntry = textarea.value;
} else {
solutionEntry = textarea.value;
}
updateCode();
};
总结
本文提供了矢量图形和SVG的简单教程,让你了解他们的作用,以及如何在网页中引入SVG。本文并非学习SVG的完整教程,只是一个指南,让你在Web上遇到SVG时知道它是什么。所以不要因为觉得你不是一个SVG专家而担心。如果你想了解更多它的工作原理,我们在下面列出了一些可能会帮助你的链接。
在本模块的最后一篇文章中,我们将详细探索响应式图像,查看HTML提供的可以让图像更好地跨设备适配的工具。