我们在较早的文章中已经了解了如何开始使用Snap.svg。 在本文中,我们将仔细研究第一篇文章中提到的新功能。
让我们从回忆起如何创建绘图表面,简单形状并加载图像开始:
var paper = Snap(800, 600),
img = paper.image('bigImage.jpg', 10, 10, 300, 300),
bigCircle = s.circle(150, 150, 100);
圆圈暂时覆盖了图像的中心。
可惜的是,您只能拥有矩形图像。 也许您的设计师创建了漂亮的圆形按钮或图像。 当然,有几种解决方案,但是它们都给您带来了另一个问题:最佳情况是,设计人员可以为您提供图像,使其外部与页面背景相匹配,从而使其看起来呈圆形。 但是,假设您的背景是纯色,则必须更改其颜色,则必须编辑图像。 您可以使用透明性,但是要么需要PNG等较重的格式,要么需要使用GIF降低质量。 也许几年后,所有浏览器将完全支持WebP ,这将解决这个难题。 无论哪种方式,如果您需要图像的交互性,您都将陷入一个矩形,以响应诸如mouseenter
, mouseout
, click
等事件。
过去使用Flash已有很长时间,因此SVG中最令人沮丧的事情之一就是无法使用SVG 1.1中引入的masks )。 在Snap中,对任何元素(包括图像)应用蒙版非常简单:
bigCircle.attr('fill', '#fff'); //This is IMPORTANT
img.attr({
mask: bigCircle
});
基本上,我们只需要将mask属性分配给我们的元素。 我们必须谨慎对待用作实际蒙版的元素。 由于最终元素的不透明度将与mask元素中的白色水平成正比,因此,如果我们希望图像具有完全的不透明度,则必须用白色填充圆圈。 乍一看这很烦人,但它为惊人的效果开辟了许多可能性,我们将在下一部分中看到。
您显然可以将不同的形状组合在一起以创建复杂的蒙版。 Snap提供了一些语法糖来帮助您:
var smallRect = paper.rect(180, 30, 50, 40),
bigCircle = paper.circle(150, 150, 100),
mask = paper.mask(bigCircle, smallRect);
mask.attr('fill', 'white');
img.attr({
mask: mask
});
Paper.mask()
方法等效于Paper.g()
,并且实际上可以用它无缝替换。
剪切路径限制了可以应用绘画的区域,因此,在当前活动剪切路径所界定的区域之外的图形的任何部分都不会绘制。 剪切路径可以认为是具有可见区域(在剪切路径内)的遮罩的alpha值为1,而隐藏区域的alpha值为0。一个区别是,尽管遮罩隐藏的区域仍会响应事件,剪裁区域则不会。
Snap没有剪切的快捷方式,但是您可以使用attr()
方法设置任何元素的clip
, clip-path
和clip-route
属性。
SVG 1.1允许使用渐变填充形状。 当然,如果使用这些形状填充蒙版,则可以通过更改蒙版的填充来利用可能性来指定最终图形的Alpha级别,并创建惊人的效果。 Snap提供了创建渐变的快捷方式,以后可以将其分配给其他元素的fill
属性。 如果我们稍微修改一下先前的代码,例如:
var gradient = paper.gradient('r()#fff-#000');
mask.attr('fill', gradient);
如果您测试此代码,最终效果将与您预期的完全不同。 那是因为我们使用了相对辐射梯度类型 ,由上面的小写字母“ r”表示。 为组中的每个元素分别创建相对梯度(作为复合蒙版)。 如果您希望整个组使用单个渐变,则可以使用命令的绝对版本。 'R()#fff-#000'
是一个绝对辐射渐变,从中间的白色填充开始,到边界处的黑色退化。
通过为任何元素的fill
属性指定SVG梯度,我们可以获得相同的结果:
mask.attr('fill', 'L(0, 0, 300, 300)#000-#f00:25-#fff');
在最后一个示例中,我们显示了更复杂的渐变。 除了不同的类型( 绝对线性 )外,此梯度从(0,0)到(300,300),从黑色到红色(25%)到白色。
gradient()
方法接受一个字符串。 Snap的文档中说明了更多详细信息。
也可以使用页面中任何svg元素的现有渐变:
<svg id="svg-test">
<defs>
<linearGradient id="MyGradient">
<stop offset="5%" stop-color="#F60" />
<stop offset="95%" stop-color="#FF6" />
</linearGradient>
</defs>
</svg>
paper.circle(50, 50, 50, 50).attr('fill', Snap('#svg-test').select('#MyGradient'));
图案允许通过重复出现另一种svg形状,渐变或图像来填充形状。 Snap提供了Element.toPattern()
方法(以前已弃用pattern()
,该方法可从任何Snap元素中创建图案。
创建模式并用它填充元素非常简单:
var p = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
fill: "none",
stroke: "#bada55",
strokeWidth: 5
}).toPattern(0, 0, 10, 10),
c = paper.circle(200, 200, 100).attr({
fill: p
});
相反,如果我们想将渐变和图案结合起来,那就是另一回事了,而稍微复杂一点!
作为示例,让我们看一下如何创建一个结合了辐射渐变和类似于上面的图案的蒙版:
//assuming the shapes bigCircle and smallRect have already been defined, as well as 'paper'
var mask = paper.g(bigCircle, smallRect),
gradient = paper.gradient("R()#fff-#000"),
pattern = paper.path("M10-5-10,15M15,0,0,15M0-5-20,15").attr({
fill: "none",
stroke: "#bada55",
strokeWidth: 5
}).toPattern(0, 0, 10, 10);
mask.attr('fill', pattern); //we need to set this before calling clone!
mask.attr({
mask: mask.clone() //makes a deep copy of current mask
});
img.attr({
mask: mask
});
我们基本上必须创建一个两级地图。 我们在图像上使用的最终地图(由渐变填充)本身具有由渐变填充的地图。 结果非常令人印象深刻! 事实证明,这也是向您介绍clone()
方法的好机会,该方法可以实现您的想象–创建被调用元素的深层副本。
动画是Snap.svg最好的制作功能之一。 有几种处理动画的方法,它们的行为略有不同。
Element.animate()
我们将从最简单的动画方法Element.animate()
。 此方法允许用户为所有数量的元素属性设置动画,全部同步。 该属性的初始值当然是其当前值,而最后一个是在animate()
的第一个参数中指定的。 除了要更改的属性外,还可以传递动画的持续时间,动画的易用性以及动画完成后将调用的回调。
一个例子将使一切变得更清晰:
bigCircle.animate({r: 10}, 2000);
这只会在两秒钟内将蒙版中的大圆圈缩小为较小的半径。
Set.animate()
您可以单独为组(集合)中的元素设置动画。 但是,如果要同步设置整个动画集怎么办? 简单! 您可以使用Set.animate()
。 这将对集合中的所有元素应用相同的变换,从而确保各种动画之间的同步,并通过将所有更改集中在一起来增强性能。
mask.animate({'opacity': 0.1}, 1000);
您还可以独立但同步地设置集中的每个元素的动画。 Set.animate()
接受可变数量的参数,因此您可以为需要设置动画的每个子元素传递一个带有参数的数组:
var set = mask.selectAll('circle'); //Create a set containing all the circle elements in mask's subtree (1 element)
paper.selectAll('rect') //Select all the rect in the drawing surface (2 elements)
.forEach(function(e) {set.push(e);}); //Add each of those rectangles to the set previously defined
set.animate([{r: 10}, 500], [{x: 20}, 1500, mina.easein], [{x: 20}, 1500, mina.easein]); //Animate the three elements in the set
假设到目前为止您已经正确地遵循了示例代码(在CodePen上尝试 ),在浏览器控制台中运行上面的代码,您将看到三个元素如何同步但独立地动画。 上面的代码也有机会介绍集合(作为select()
和selectAll()
方法的结果)以及在其上定义的一些有用方法的机会。
创建集合的另一种方法是将元素数组传递给Snap构造函数方法:
var set2 = Snap([bigCircle, smallRect]);
Snap.animate()
您可以设置任何数字属性的animate()
,但是animate()
在其他类型上不起作用,例如,如果尝试为其text
属性设置动画,它将使text
元素混乱。 还有另一种获得这种效果的方法,即在Snap中调用animate()
的第三种方法。
通过调用Snap对象的animate方法,可以进一步详细指定将在动画的每个步骤执行的动作。 这有助于将复杂的动画分组在一起并使其同步运行(尽管Set.animate()
方法将是处理此问题的正确方法),以及对复杂的非数字属性进行动画处理。
例如,让我们创建一个文本元素并为其设置动画:
var labelEl = paper.text(300, 150, "TEST"),
labels = ["TEST", "TETT","TEUT","TEVT","TEXT","TES-","TE--","T---", "----", "C---", "CH--", "CHE-", "CHEC-", "CHECK"];
Snap.animate(0, 13, function (val) {
labelEl.attr({
text: labels[Math.floor(val)]
});
}, 1000);
回到蒙版和图像之间的初始比较,您可以获得与上一节中显示的动画gif(类似)相同的效果。 但是,如果您想响应用户交互来重现此行为,则使用SVG进行的改进就更加重要。 您仍然可以找到一种使用多个gif使其工作的方法,但是,除了失去灵活性之外,您将无法通过很少的努力获得相同的质量:
img.click(function(evt) {
this.minified = !this.minified;
bigCircle.animate({
r: !this.minified ? 100 : 10
}, 1500);
});
以后可以使用Element.unclick()
方法删除Click处理程序。
可以类似处理的其他事件包括dblclick
, mousedown
和mouseup
, mousemove
, mouseout
和mouseover
,以及许多面向移动的事件,例如touchstart
和touchend
。
对于那些习惯使用jQuery或D3接口的读者,Snap中没有on()
方法可以手动处理其他事件。 如果您需要超越Snap提供的处理程序的自定义行为,则可以检索任何元素的node
属性,而该属性又包含对关联的DOM元素的引用,并且(可以将其包装在jQuery中)可以添加处理程序及其属性直接:
img.node.onclick = function () {
img.attr("opacity", 0.1);
};
Snap使使用Element.drag()
方法为任何元素,组或集合激活拖放特别容易。 如果不需要任何自定义行为,则可以不带任何参数地调用它:
labelEl.drag(); //handle drag and drop for you
但是,如果需要某些特殊行为,则可onmove
, ondragstart
和ondragend
事件传递自定义回调和上下文。 请注意,如果要传递下一个回调,则不能省略onmove
回调。
添加拖动处理程序不会隐藏click
事件,除非明确阻止,否则将在ondragend
事件之后触发该ondragend
。
这个功能强大的库最吸引人的地方之一就是它支持重用现有的SVG代码。 您可以将其“插入”为字符串,甚至更好的是,您可以读取现有文件,然后进行更改。
您可以自己尝试。 将此漂亮的svg图下载并保存到项目的根目录中。 接下来将其加载到您的页面中,根据需要更改其样式或结构,甚至在将其添加到DOM树,添加事件处理程序等之前也是如此。
Snap.load('ringing-phone.svg', function (phone) {
// Note that we traverse and change attr before SVG is even added to the page (improving performance)
phone.selectAll("path[fill='#ff0000']").attr({fill: "#00ff00"});
var g = phone.select("g");
paper.append(g); //Now we add the SVG element to the page
});
注意 :由于浏览器中的同源策略,您需要在本地服务器中运行示例以测试加载方法。
处理DOM时提高性能的一种方法是使用DocumentFragments 。 片段是DOM节点的最小容器。 它们是几年前推出的,它们使您可以廉价地操作整个子树,然后使用2个方法调用(而不是n
克隆并向页面添加具有n
节点的整个子树。 实际差异将在John Resig的博客中详细说明。
Snap还可以通过两种方法原生使用片段:
Snap.parse(svg)
接受一个参数,即带有SVG代码的字符串,对其进行解析并返回一个片段,该片段可以稍后附加到任何绘图表面。
Snap.fragment(varargs)
接收可变数量的元素或字符串,并创建一个包含所有提供的元素的片段。
尤其是对于大型svg图纸,如果正确使用片段,可以节省大量性能。
到此结束我们有关高级Snap.svg的文章。 现在,读者应该对使用此库可以做什么有一个清晰的认识。 如果您有兴趣学习更多信息,则Snap文档是一个不错的起点。
几个有用的链接: