【问题标题】:SVG filter relative to bounding box with possibly zero length side, calc() alternative?相对于边界框的SVG过滤器,边长可能为零,calc()替代方案?
【发布时间】:2017-09-19 19:53:47
【问题描述】:

这在某种程度上是Humongous height value for <filter> not preventing cutoff 的延续:我仍在尝试在&lt;path&gt; 上应用&lt;filter&gt;,但我遇到了被剪裁的问题。

通过使用&lt;filter&gt; 上的x/y 属性移动过滤器画布的中心,问题在另一个线程中得到了解决,仍然所有内容都以百分比表示,因此相对于你的东西的大小正在尝试应用效果,但问题是边长可以是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,笔划宽度和标记小玩意不计在内:

我可以使用绝对单位,例如就像下面的例子:

.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 生成的内容。)

【问题讨论】:

    标签: svg svg-filters


    【解决方案1】:

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

    我只是在这里,您可能需要根据您的逻辑调整它(并且可能将 ES6 构造替换为它们的 ES5 等效项)。假设您有一个从中生成路径数据的点列表:

    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);
    

    之后,滤镜效果区域的默认设置应按原样工作。

    【讨论】:

    • 效果很好。在您发布该答案之前,我正在考虑使用不同的过滤器,但您的方法更好并且效果很好。
    【解决方案2】:

    仅仅因为您在过滤器中使用了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>

    【讨论】:

    • 悬停仅用于演示目的,但如果绘图空间不太大,也是一个好主意。就我而言,它相当大,因为可见空间只显示了我的绘图空间的一部分(用户可以平移/缩放),而且我还没有实现某种等效的截锥体剔除(还没有?)。
    猜你喜欢
    • 1970-01-01
    • 2017-05-28
    • 1970-01-01
    • 2012-07-12
    • 1970-01-01
    • 2016-01-08
    • 1970-01-01
    • 1970-01-01
    • 2013-04-08
    相关资源
    最近更新 更多