likes
comments
collection
share

SVG奇淫巧技(五):你只管绘制图案,其余交给marker就好

作者站长头像
站长
· 阅读数 21

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第5篇文章。

很早之前做过一个项目,是用SVG去实现节点之间的关系,并且连线上要有箭头表示数据的走向,当时对于箭头的坐标计算着实费了一番功夫。

本以为当时的方法虽然麻烦但应该算是标准方案了,直到重新学习SVG看到了<marker>元素,才发现当初的方案纯属是力大砖飞的莽夫行为罢了。

可以绘制箭头的<marker>

老规矩先看下MDN上关于<marker>的定义:

marker元素定义了在特定的<path><line><polyline>或者<polygon>上绘制的箭头或者多边标记图形。

从定义上可以看出<marker>主要就是用于绘制箭头或其他图案而存在的,还是看图比较直观:

SVG奇淫巧技(五):你只管绘制图案,其余交给marker就好

<svg width="200" height="200" viewBox="0 0 100 100">
    <defs>
      <!-- 箭头标记 -->
      <marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5"
          markerWidth="6" markerHeight="6"
          orient="auto-start-reverse">
        <path d="M 0 0 L 10 5 L 0 10 z" />
      </marker>
  
      <!-- 单点标记 -->
      <marker id="dot" viewBox="0 0 10 10" refX="5" refY="5"
          markerWidth="5" markerHeight="5">
        <circle cx="5" cy="5" r="5" fill="red" />
      </marker>
    </defs>
  
    <!-- 坐标轴绘制 -->
    <polyline points="10,10 10,90 90,90" fill="none" stroke="black"
     marker-start="url(#arrow)" marker-end="url(#arrow)" />
  
    <!-- line图绘制 -->
    <polyline points="15,80 29,50 43,60 57,30 71,40 85,15" fill="none" stroke="grey" marker-start="url(#dot)" marker-mid="url(#dot)" marker-end="url(#dot)" />
  </svg>

从代码中,我们可以看到 <marker> 是支持 viewBox 属性的,那么同样的它也是支持preserveAspectRatio 属性的,原理与 <svg> 相同。

除此之外,markerWidthmarkerHeight 用于定义图案的宽高,这很好理解。

refXrefY 用于定义图案的标记点坐标,至于标记点嘛,你可以简单理解为线段的终端在图案中的位置。

除此之外,线段上还可以设置 marker-startmarker-midmarker-end 这3个属性,通过名字就可以看出,是用来标识图案在线段上出现的位置,值得注意的是marker-mid属性在仅有单条线段时无效。

以上的属性都很好理解,那么就只剩下一根难啃的骨头了——orient,这个属性代表朝向,顾名思义就是用来定义图案朝向的,它有4个可选值:

auto: 默认值,它显示了标记的方向,使其正x轴指向相对于放置标记的位置的路径的方向;

auto-start-reverse: 此属性与marker-start一起使用,则标记的方向与指定auto时所使用的方向相差180°;

angle: 指定的角度是形状的正x轴与标记之间的距离;

number: 它显示以度为单位的角度。

话不多说,直接上图:

SVG奇淫巧技(五):你只管绘制图案,其余交给marker就好

<svg width="200" height="200" viewBox="0 0 100 100">
    <defs>
       <!-- arrowhead auto -->
      <marker id="arrow-auto" viewBox="0 0 10 10" refX="5" refY="5"
          markerWidth="6" markerHeight="6"
          orient="auto">
        <path d="M 0 0 L 10 5 L 0 10 z" />
      </marker>

       <!-- arrowhead auto-start-revers -->
      <marker id="arrow-reverse" viewBox="0 0 10 10" refX="5" refY="5"
          markerWidth="6" markerHeight="6"
          orient="auto-start-reverse">
        <path d="M 0 0 L 10 5 L 0 10 z" />
      </marker>

      <!-- arrowhead angle -->
      <marker id="arrow-angle" viewBox="0 0 10 10" refX="5" refY="5"
          markerWidth="6" markerHeight="6"
          orient="90deg">
        <path d="M 0 0 L 10 5 L 0 10 z" />
      </marker>

      <!-- arrowhead number -->
      <marker id="arrow-number" viewBox="0 0 10 10" refX="5" refY="5"
          markerWidth="6" markerHeight="6"
          orient="90">
        <path d="M 0 0 L 10 5 L 0 10 z" />
      </marker>
  
    </defs>

     <line x1="10" y1="20" x2="10" y2="80" stroke="black" marker-start="url(#arrow-auto)" marker-end="url(#arrow-auto)" />
     <line x1="35" y1="20" x2="35" y2="80" stroke="black" marker-start="url(#arrow-reverse)" marker-end="url(#arrow-reverse)" />
     <line x1="60" y1="20" x2="60" y2="80" stroke="black" marker-start="url(#arrow-angle)" marker-end="url(#arrow-angle)" />
     <line x1="85" y1="20" x2="85" y2="80" stroke="black" marker-start="url(#arrow-number)" marker-end="url(#arrow-number)" />
  </svg>

可以看到,autoauto-start-reverse 只是在marker-start时表现为朝向的区别,而anglenumber其实是等价的,只是写法不一样,从代码上可以很清楚的看到这一点。

只是,以上案例中,都是实心的箭头和圆点,那如果是空心的是否还适用呢?

SVG奇淫巧技(五):你只管绘制图案,其余交给marker就好

答案是可以,但是不完美,箭头还可以通过调整refXrefY让线段不再超出到箭头内部。但是通过marker-mid来实现的图案就只能爱莫能助了。

SVG奇淫巧技(五):你只管绘制图案,其余交给marker就好

所以,<marker>虽好,但也只相对于通过fill填充的图案,对于非填充的图案就显得捉襟见肘了。

OK,以上就是关于<marker>的知识啦,不过,凡事都怕琢磨,不知道是否有聪明的小伙伴会有疑问,如果是多起点的<path>路径<marker>的表现又如何呢?

多个起点M<path>

首先,我们要知道<path>是可以支持多个M起点的,其实就相当于画画的时候不一定非要一笔画下来,可以很多笔画一个图案,例如:

SVG奇淫巧技(五):你只管绘制图案,其余交给marker就好

<svg width="200" height="200" viewBox="0 0 100 100">
   <path d="M 10 20 V 80
            M 35 20 V 80
            M 60 20 V 80
            M 85 20 V 80"
         stroke="black" />
</svg>

但是,如果结合<marker>的话,并不是每个起点都算是start,还是按照代码顺序决定的,例如:

SVG奇淫巧技(五):你只管绘制图案,其余交给marker就好

<path d="M 10 20 V 80
         M 35 20 V 80
         M 60 20 V 80
         M 85 20 V 80"
      marker-start="url(#arrow-reverse)"
      marker-end="url(#arrow-reverse)"
      stroke="black" />

不过,这时如果加上 marker-mid 其他起点也会生成marker只是arrow-reverse属性不再对它们生效:

SVG奇淫巧技(五):你只管绘制图案,其余交给marker就好

另外,值得注意一点,虽然<path> 支持多起点,我们也可以通过多个起点来组成一个图形例如矩形,不过理论上它们仍属于一条条独立的线段,只是在视觉上让他们看起来围在了一起,但这并不算闭合图形,所以这样实现的图形是无法被fill填充的,也无法被z属性闭合,比如:

SVG奇淫巧技(五):你只管绘制图案,其余交给marker就好

<svg width="200" height="200" viewBox="0 0 100 100">
     <path d="M 10 20 V 80 M 10 80 H 80 M 80 80 V 20 M 80 20 H 10 Z" stroke="black" fill="red" />
</svg>

好啦,看到这里想来如此好用的<marker>大家已经清楚了,下次再用SVG实现类似含有箭头的线段时记得学以致用哈,提前祝大家中秋快乐,就是这样再见吧。