SVG奇淫巧技(二):关于交互那点事
我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第2篇文章。
SVG毕竟也是DOM中的一员,所以除了单纯的展示之外,挂点事件之类的操作也是分内之事,不过,SVG在实践交互的过程中,总是会出现一些奇奇怪怪的现象,今天我们就来聊聊关于SVG交互的那些事。
三、SVG的交互事件
先来举个栗子,一个非填充的path元素绘制的三角形,想实现一个hover时颜色填满的功能,脑补一下怎么实现呢?
-
先来用
path实现一个fill="none"的三角形:
<svg width="200" height="100" viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg"> <path d="M 100 10 L 190 90 H 10 Z" stroke="#cd0000" fill="none"> </svg> -
在CSS中对
path添加hover时修改fill的样式:path:hover { fill: #cd0000; cursor: pointer; }
嗯,颅内渲染了一下,看起来没什么问题,让我们看下实践的效果:

可以看到,鼠标hover到三角形内部时,三角形并没有换色,这和我们最初的想法不一致,难道说SVG压根不支持hover事件?要知道,作为DOM不支持事件,那可以算是妥妥的大逆不道了啊,这明显不符合常识,让我们稍安勿躁,再仔细观察一下:

可以看到,当我们小心翼翼的将鼠标hover到path的stroke上时,三角形被填满了红色。目的实现,看来SVG不支持hover事件是多虑了,但是新的问题又来了,stroke那么小的交互区域,就算能被hover也是于事无补啊,鼠标触发都如此的捉襟见肘,这要是放在手指交互的移动端,我都能脑补出用户为了复现那电光火石般的hover事件手指在屏幕上搓出的火星。
不过,SVG当然不会这么“傻X”的只给用户这么一点点的交互区域,但是在解决这个问题之前,我们需要先了解,为什么一个非填充的<path>只有stroke那么一丁点的区域支持事件交互呢?
这就要从一个CSS属性说起。
CSS有一个属性 pointer-events,它经常被用来实现事件穿透的效果,即pointer-events: none;。
不过,我们今天的主角不是事件穿透的实现,而是pointer-events 属性本身,如果现在提问这个属性有几个可选值,相信大部分小伙伴都能答出来一些,例如:
/* 属性值 */
pointer-events: auto;
pointer-events: none;
/* Global values */
pointer-events: inherit;
pointer-events: initial;
pointer-events: unset;
这些可选值,除了none是常用的之外,auto作为默认值,其余3个都是全局可选值,几乎适用大部分CSS属性。
好,我们已经答出了pointer-events 这个属性一半的可选值了,还有一半的可选值分别是:
pointer-events: visiblePainted; /* 只适用于 SVG */
pointer-events: visibleFill; /* 只适用于 SVG */
pointer-events: visibleStroke; /* 只适用于 SVG */
pointer-events: visible; /* 只适用于 SVG */
pointer-events: painted; /* 只适用于 SVG */
pointer-events: fill; /* 只适用于 SVG */
pointer-events: stroke; /* 只适用于 SVG */
pointer-events: all; /* 只适用于 SVG */
先别管这些值分别代表了什么意思,只看后面的备注就很明显了,这些值都只是适应于SVG元素的,那么问题应该就出现在他们身上,我们现在来看下MDN上对于这些值的定义:
visiblePainted:元素只有在以下情况才会成为鼠标事件的目标:•
visibility属性值为visible,且鼠标指针在元素内部,且fill属性非none。•
visibility属性值为visible,鼠标指针在元素边界上,且stroke属性非none。
visibleStroke: 只有在元素visibility属性值为visible,且鼠标指针在元素边界时,元素才会成为鼠标事件的目标,stroke属性的值不影响事件处理。
visibleFill: 只有在元素visibility属性值为visible,且鼠标指针在元素内部时,元素才会成为鼠标事件的目标,fill属性的值不影响事件处理。
visible: 只有在元素visibility属性值为visible,且鼠标指针在元素内部或边界时,元素才会成为鼠标事件的目标,fill和stroke属性的值不影响事件处理。
painted: 元素只有在以下情况才会成为鼠标事件的目标:• 鼠标指针在元素
内部,且fill属性指定了none之外的值• 鼠标指针在元素边
界上,且stroke属性指定了none之外的值
visibility属性的值不影响事件处理。
fill: 只有鼠标指针在元素内部时,元素才会成为鼠标事件的目标,fill和visibility属性的值不影响事件处理。
stroke: 只有鼠标指针在元素边界上时,元素才会成为鼠标事件的目标,stroke和visibility属性的值不影响事件处理。
all: 只有鼠标指针在元素内部或边界时,元素才会成为鼠标事件的目标fill、stroke和visibility属性的值不影响事件处理。
我承认概念有点多,所以我整理了下面的表格:
| 属性 | 响应范围 | visibility | fill | stroke |
|---|---|---|---|---|
visiblePainted | 内部 or 边界 | visible | 非none | 非none |
visibleStroke | 边界 | visible | -- | 不限制 |
visibleFill | 内部 | visible | 不限制 | -- |
visible | 内部 or 边界 | visible | 不限制 | 不限制 |
painted | 内部 or 边界 | 不限制 | 非none | 非none |
fill | 内部 | 不限制 | 不限制 | -- |
stroke | 边界 | 不限制 | -- | 不限制 |
all | 内部 or 边界 | 不限制 | 不限制 | 不限制 |
OK,这下简单易懂多了吧,现在我们对另一半只适用于SVG的可选值也有了解了,那么解决方案也应该相出来了:
-
不涉及visibility隐藏/显示的解决方案且边框是否响应无所谓的情况:pointer-events: visibleFill / visible / fill / all; -
不涉及visibility隐藏/显示的解决方案且边框需要响应的情况:pointer-events: visible / all; -
涉及visibility隐藏/显示的解决方案且边框需要响应的情况:pointer-events: visible;
现在,我们已经有了解决方案,最通用的无非就是visbible了,我们只需要在CSS中修改这个属性值即可;
path {
pointer-events: visible;
}
不过,虽然问题解决了,却并没有解惑,回到最初的问题,我们没有修改pointer-events 的值,为什么SVG只响应边框而内部却不响应呢?
难道是默认的auto做了什么手脚?我们再来看下关于auto的描述:
auto: 与pointer-events属性未指定时的表现效果相同,对于SVG内容,该值与visiblePainted效果相同。
疑惑解开了,当 pointer-events 为默认值时,其表现与 visiblePainted 相同,我们再来看下 visiblePainted 的定义:
visiblePainted:元素只有在以下情况才会成为鼠标事件的目标:•
visibility属性值为visible,且鼠标指针在元素内部,且fill属性非none。•
visibility属性值为visible,鼠标指针在元素边界上,且stroke属性非none。
所以,三角形的内部不响应是因为默认为 visiblePainted 的前提下,我们只指定了stroke的颜色,而fill="none"。
那我们应该也有了另一种解决方案,将fill设置为transparent,即透明的填充色是不是也能起到一样的效果呢?
<svg width="200" height="100" viewBox="0 0 200 100" xmlns="http://www.w3.org/2000/svg">
<path d="M 100 10 L 190 90 H 10 Z" stroke="#cd0000" fill="transparent">
</svg>

果然可行,看来SVG交互那点事也不过如此了。
总结一下:
遇到只渲染边框的SVG交互时,通用解决方案为:
- 填充透明色,
fill="transparent"; - 修改CSS属性,
pointer-events: visible;
以上这些不知道大家学会了没有,没学会就扔进收藏夹吃灰吧,毕竟收藏就等于学会了嘛。不过,SVG的交互与fill和stroke的值息息相关,这样的话,我们下一篇就来撸撸这俩属性吧。
转载自:https://juejin.cn/post/7140092589725876231