SVG过滤器相对于边界框可能为零长度边,calc()替代?

时间:2017-09-13 15:38:25

标签: svg svg-filters

这有点是Humongous height value for <filter> not preventing cutoff的延续:我仍在尝试在<filter>上应用<path>,但我遇到了被剪裁的问题。

通过使用x上的y / <filter>属性移动过滤器画布的中心,在另一个主题中解决了问题,但所有内容都是百分比,因此相对于你试图应用效果的东西的大小,但问题是边长可以是0,即使你看到的东西,见例如以下示例中的第一行:

.pathWrapper path {
  stroke: grey;
  fill: none;
  stroke-width: 1.5;
  marker-start: url(#circle);
  marker-end: url(#arrow);
}

.pathWrapper:hover {
  filter: url(#colorFilter);
}
<svg style="height:400px;width:100%;background-color:LightCyan">

<defs>
  <filter id="colorFilter" x="-300%" y="-300%" width="600%" height="600%">
    <feGaussianBlur in="SourceAlpha" stdDeviation="1" result="blur"></feGaussianBlur>
    <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 255 0 0 0 0 0 0 0 0 3 0" result="lightenedBlur"></feColorMatrix>
    <feMerge>
      <feMergeNode in="lightenedBlur"></feMergeNode>
      <feMergeNode in="SourceGraphic"></feMergeNode>
    </feMerge>
  </filter>
  <marker id="arrow" viewBox="0 -5 10 10" refX="0" refY="0" markerWidth="8" markerHeight="8" orient="auto" style="fill: grey;">
    <path d="M0,-5L10,0L0,5"></path>
  </marker>
  <marker id="circle" viewBox="0 -4 8 8" refX="0" refY="0" markerWidth="8" markerHeight="8" orient="auto-start-reverse" style="fill: grey;"><circle r="4" cx="4"></circle></marker>
</defs>

<g transform="scale(2)">

  <g class="pathWrapper" transform="translate(70,20)">
    <path d="M52,10L45,10L-30,10L-37,10"></path>
  </g>
  
  <g class="pathWrapper" transform="translate(70,50)">
    <path d="M42,20L35,20L30,10L-30,10L-37,10"></path>
  </g>

  <g class="pathWrapper" transform="translate(200,20)">
    <path d="M42,140L35,140L-30,70L-30,10L-30,10L-37,10"></path>
  </g>

</g>

</svg>

将鼠标悬停在线条上以查看在那里应用的滤镜效果。 您会看到顶行在悬停时变得不可见。这是因为边界框高度为0,笔触宽度和标记小符号不计算在内:

height zero demo

我可以使用绝对单位,例如如下例所示:

.pathWrapper path {
  stroke: grey;
  fill: none;
  stroke-width: 1.5;
  marker-start: url(#circle);
  marker-end: url(#arrow);
}

.pathWrapper:hover {
  filter: url(#colorFilter);
}
<svg style="height:400px;width:100%;background-color:LightCyan">

<defs>
  <filter id="colorFilter" filterUnits="userSpaceOnUse" x="-125" y="-125" width="250" height="250">
    <feGaussianBlur in="SourceAlpha" stdDeviation="1" result="blur"></feGaussianBlur>
    <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 255 0 0 0 0 0 0 0 0 3 0" result="lightenedBlur"></feColorMatrix>
    <feMerge>
      <feMergeNode in="lightenedBlur"></feMergeNode>
      <feMergeNode in="SourceGraphic"></feMergeNode>
    </feMerge>
  </filter>
  <marker id="arrow" viewBox="0 -5 10 10" refX="0" refY="0" markerWidth="8" markerHeight="8" orient="auto" style="fill: grey;">
    <path d="M0,-5L10,0L0,5"></path>
  </marker>
  <marker id="circle" viewBox="0 -4 8 8" refX="0" refY="0" markerWidth="8" markerHeight="8" orient="auto-start-reverse" style="fill: grey;"><circle r="4" cx="4"></circle></marker>
</defs>

<g transform="scale(2)">

  <g class="pathWrapper" transform="translate(70,20)">
    <path d="M52,10L45,10L-30,10L-37,10"></path>
  </g>
  
  <g class="pathWrapper" transform="translate(70,50)">
    <path d="M42,20L35,20L30,10L-30,10L-37,10"></path>
  </g>

  <g class="pathWrapper" transform="translate(200,20)">
    <path d="M42,140L35,140L-30,70L-30,10L-30,10L-37,10"></path>
  </g>

</g>

</svg>

问题在于,在我的用例中,应用效果的元素的大小可能会有很大变化,因此我必须在那里投入巨大的价值,否则总是会有变化足够大(如跨越最大高度的线的示例所示)。

我发现使用CSS calc()可能是一个解决方案:

#colorFilter {
  width: calc(100% + 100);
  height: calc(100% + 100);
  x: calc(-50% - 50);
  y: calc(-50% - 50);
}

.pathWrapper path {
  stroke: grey;
  fill: none;
  stroke-width: 1.5;
  marker-start: url(#circle);
  marker-end: url(#arrow);
}

.pathWrapper:hover {
  filter: url(#colorFilter);
}
<svg style="height:400px;width:100%;background-color:LightCyan">

<defs>
  <filter id="colorFilter" filterUnits="userSpaceOnUse">
    <feGaussianBlur in="SourceAlpha" stdDeviation="1" result="blur"></feGaussianBlur>
    <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 255 0 0 0 0 0 0 0 0 3 0" result="lightenedBlur"></feColorMatrix>
    <feMerge>
      <feMergeNode in="lightenedBlur"></feMergeNode>
      <feMergeNode in="SourceGraphic"></feMergeNode>
    </feMerge>
  </filter>
  <marker id="arrow" viewBox="0 -5 10 10" refX="0" refY="0" markerWidth="8" markerHeight="8" orient="auto" style="fill: grey;">
    <path d="M0,-5L10,0L0,5"></path>
  </marker>
  <marker id="circle" viewBox="0 -4 8 8" refX="0" refY="0" markerWidth="8" markerHeight="8" orient="auto-start-reverse" style="fill: grey;"><circle r="4" cx="4"></circle></marker>
</defs>

<g transform="scale(2)">

  <g class="pathWrapper" transform="translate(70,20)">
    <path d="M52,10L45,10L-30,10L-37,10"></path>
  </g>
  
  <g class="pathWrapper" transform="translate(70,50)">
    <path d="M42,20L35,20L30,10L-30,10L-37,10"></path>
  </g>

  <g class="pathWrapper" transform="translate(200,20)">
    <path d="M42,140L35,140L-30,70L-30,10L-30,10L-37,10"></path>
  </g>

</g>

</svg>

这适用于当前版本的Google Chrome和Mozilla Firefox,但似乎无法在Micosoft Edge或IE11中使用(而且从一点搜索看起来似乎calc()支持已经非常敏感,即使对于HTML内容也是如此,请参阅https://caniuse.com/#search=calc上的已知问题部分。

那么是否存在更好的calc()方法替代方案?

(也许值得注意的是我正在使用动态d3.js生成的内容。)

2 个答案:

答案 0 :(得分:3)

当您在d3中处理生成的内容时,最好的方法是在g.pathWrapper中插入一个覆盖所需过滤区域的不可见矩形。

我在这里徘徊,你可能需要根据你的逻辑进行调整(并且可能会为他们的ES5等价物交换ES6结构)。假设您有一个从以下位置生成路径数据的点列表:

var points = [[52,10], [45,10], [-30,10], [-37,10]];

// get the min/max values
var x1 = d3.min(points,(p) => p[0]),
    x2 = d3.max(points,(p) => p[0]),
    y1 = d3.min(points,(p) => p[1]),
    y2 = d3.max(points,(p) => p[1]);

// construct the path
var path = d3.path();
path.moveto(...points[0]);
for (var point of points.slice(1)) {
     path.lineto(...point);
}

// wrapper
var wrapper = d3.select('svg > g').append('g')
    .classed('pathWrapper');

// invisible rect with +5px in each direction as filter region
wrapper.append('rect')
    .attr('fill', 'none')
    .attr('x', x1 - 5)
    .attr('y', y1 - 5)
    .attr('width', x2 - x1 + 10)
    .attr('height', y2 - y1 + 10);

// and the path itself
wrapper.append('path')
   .attr('d', path);

之后,滤镜效果区域的默认值应按原样运行。

答案 1 :(得分:1)

仅仅因为您在过滤器中使用userSpaceOnUse值,并不意味着您仍然无法使用百分比。只是百分比是相对于SVG的宽度和高度而不是元素的宽度和高度。

当然这意味着过滤器正在应用于很多的额外的,不必要的像素。过滤器应用于SVG大小的区域,而不是元素大小的区域。但是,由于您只是在悬停时过滤了一个元素,因此浏览器所做的额外工作导致的任何缓慢都不应该引人注意。

.pathWrapper path {
  stroke: grey;
  fill: none;
  stroke-width: 1.5;
  marker-start: url(#circle);
  marker-end: url(#arrow);
}

.pathWrapper:hover {
  filter: url(#colorFilter);
}
<svg style="height:400px;width:100%;background-color:LightCyan">

<defs>
  <filter id="colorFilter" filterUnits="userSpaceOnUse">
    <feGaussianBlur in="SourceAlpha" stdDeviation="1" result="blur"></feGaussianBlur>
    <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 255 0 0 0 0 0 0 0 0 3 0" result="lightenedBlur"></feColorMatrix>
    <feMerge>
      <feMergeNode in="lightenedBlur"></feMergeNode>
      <feMergeNode in="SourceGraphic"></feMergeNode>
    </feMerge>
  </filter>
  <marker id="arrow" viewBox="0 -5 10 10" refX="0" refY="0" markerWidth="8" markerHeight="8" orient="auto" style="fill: grey;">
    <path d="M0,-5L10,0L0,5"></path>
  </marker>
  <marker id="circle" viewBox="0 -4 8 8" refX="0" refY="0" markerWidth="8" markerHeight="8" orient="auto-start-reverse" style="fill: grey;"><circle r="4" cx="4"></circle></marker>
</defs>

<g transform="scale(2)">

  <g class="pathWrapper" transform="translate(70,20)">
    <path d="M52,10L45,10L-30,10L-37,10"></path>
  </g>
  
  <g class="pathWrapper" transform="translate(70,50)">
    <path d="M42,20L35,20L30,10L-30,10L-37,10"></path>
  </g>

  <g class="pathWrapper" transform="translate(200,20)">
    <path d="M42,140L35,140L-30,70L-30,10L-30,10L-37,10"></path>
  </g>

</g>

</svg>