【问题标题】:SVG gooey effect not working on the latest version of FireFoxSVG 粘糊糊的效果在最新版本的 FireFox 上不起作用
【发布时间】:2017-11-28 12:05:54
【问题描述】:

我遇到一个问题,即我的 feGuassian 模糊在使用除黑色以外的任何其他颜色时无法正常工作。

在 chrome 上它可以完美运行。我还没有在 safari 上测试过。

我在 jsFiddle 上创建了一个示例:

HTML:

<div>
<div class="bigLogo">
    <div class="blobs">
    <div class="blob"></div>
    <div class="blob"></div>
    <div class="blob"></div>
    <div class="connect12">
        <div class="part1"></div>
        <div class="part2"></div>
        <div class="part3"></div>
    </div>
    <div class="connect23">
        <div class="part3"></div>
    </div>
    <div class="connect31">
        <div class="part3"></div>
    </div>
</div>
<svg xmlns="http://www,w3.org" version='1.1'>
    <defs>
        <filter id="goo">
            <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur"/>
            <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0                                                                                       0 1 0 0 0                                                                                   0 0 1 0 0                                                                                   0 0 0 18 -7" result="goo"/>
            <feBlend in="SourceGraphic" in2="goo" />
        </filter>
    </defs>
</svg>
</div>
</div>

<div class="break">
<div class="smallLogo">
        <div class="blobs">
            <div class="blob"></div>
            <div class="blob"></div>
            <div class="blob"></div>
            <div class="connect12">
                <div class="part1"></div>
                <div class="part2"></div>
                <div class="part3"></div>
            </div>
            <div class="connect23">
                <div class="part3"></div>
            </div>
            <div class="connect31">
                <div class="part3"></div>
            </div>
        </div>

        <svg xmlns="http://www,w3.org" version='1.1'>
            <defs>
                <filter id="goo2">
                    <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur"/>
                    <feColorMatrix in="blur" mode="matrix" values="0 0 0 0 0
                                                                   0 0 0 0 0
                                                                   0 0 0 0 0
                                                                   0 0 0 18 -7" result="goo"/>
                    <feBlend in="SourceGraphic" in2="goo" />
                </filter>
            </defs>
        </svg>
    </div>
</div>

SCSS:

@-webkit-keyframes rotate-logo {
    0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
    }
    100% {
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);
    }
}

@keyframes rotate-logo {
    0% {
        -webkit-transform: rotate(0deg);
        transform: rotate(0deg);
    }
    100% {
        -webkit-transform: rotate(360deg);
        transform: rotate(360deg);
    }
}

@-webkit-keyframes rotate-logo-small {
    0% {
        -webkit-transform: rotate(0deg) scale(0.3);
        transform: rotate(0deg) scale(0.3);
    }
    100% {
        -webkit-transform: rotate(360deg) scale(0.3);
        transform: rotate(360deg) scale(0.3);
    }
}

@keyframes rotate-logo-small {
    0% {
        -webkit-transform: rotate(0deg) scale(0.3);
        transform: rotate(0deg) scale(0.3);
    }
    100% {
        -webkit-transform: rotate(360deg) scale(0.3);
        transform: rotate(360deg) scale(0.3);
    }
}

@-webkit-keyframes blob-anim-left {
    0% {
        -webkit-transform: translateX(6px) translateY(10px);
        transform: translateX(6px) translateY(10px);
    }
    100% {
        -webkit-transform: translateX(-43px) translateY(25px);
        transform: translateX(-43px) translateY(25px);
    }
}

@keyframes blob-anim-left {
    0% {
        -webkit-transform: translateX(0);
        transform: translateX(0);
    }
    100% {
        -webkit-transform: translateX(-43px) translateY(25px);
        transform: translateX(-43px) translateY(25px);
    }
}

@-webkit-keyframes blob-anim-right {
    0% {
        -webkit-transform: translateX(0);
        transform: translateX(0);
    }
    100% {
        -webkit-transform: translateX(59px) translateY(-61px);
        transform: translateX(59px) translateY(-61px);
    }
}

@keyframes blob-anim-right {
    0% {
        -webkit-transform: translateX(0);
        transform: translateX(0);
    }
    100% {
        -webkit-transform: translateX(59px) translateY(-61px);
        transform: translateX(59px) translateY(-61px);
    }
}

@-webkit-keyframes blob-anim-top-right {
    0% {
        -webkit-transform: translateX(0);
        transform: translateX(0);
    }
    100% {
        -webkit-transform: translateX(28px) translateY(36px);
        transform: translateX(28px) translateY(36px);
    }
}

@keyframes blob-anim-top-right {
    0% {
        -webkit-transform: translateX(0);
        transform: translateX(0);
    }
    100% {
        -webkit-transform: translateX(28px) translateY(36px);
        transform: translateX(28px) translateY(36px);
    }
}

@keyframes line-anim-con12 {
    0% {
        -webkit-transform: translateX(-30px) translateY(-24px);
        transform: translateX(-30px) translateY(-24px);
        width: 0px;
    }
    100% {
        -webkit-transform: rotate(-37deg) translateX(-37px) translateY(-83px);
        transform: rotate(-37deg) translateX(-37px) translateY(-83px);
        width: 120px;
    }
}

@keyframes line-anim-con23 {
    0% {
        -webkit-transform: translateX(-30px) translateY(-24px);
        transform: translateX(-30px) translateY(-24px);
        height: 1px;
    }
    100% {
        -webkit-transform: rotate(13deg) translateX(19px) translateY(-46px);
        transform: rotate(13deg) translateX(19px) translateY(-46px);
        height: 25px;
    }
}

@keyframes line-anim-con31 {
    0% {
        -webkit-transform: translateX(-30px) translateY(-24px);
        transform: translateX(-30px) translateY(-24px);
        width: 1px;
    }
    100% {
        -webkit-transform: translateX(-46px) translateY(8px) rotate(25deg);
        transform: translateX(-46px) translateY(8px) rotate(25deg);
        width: 23px;
    }
}

.bigLogo{
    position: absolute;
    width: 208.8px;
    height: 208.8px;
    top: 0;
    left: 50%;
    transform: translateX(-50%);
}

.smallLogo{
    position: absolute;
    width: 75px;
    height: 75px;
    top: 0;
    left: 50%;
    transform: translateX(-50%);

    .blobs{
        -webkit-filter: url('#goo2') !important;
        filter: url('#goo2') !important;
        -webkit-animation: rotate-logo ease-in-out 9s infinite alternate !important;
        animation: rotate-logo ease-in-out 9s infinite alternate !important;
        .blob{
            background: #000 !important;
        }
        .part1{
            background-color: #000 !important;
        }
        .part2{
            background-color: #000 !important;
        }
        .part3{
            background-color: #000 !important;
        }
    }
}

.blobs {
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    width: 208.8px;
    height: 208.8px;
    margin: auto;
    -webkit-filter: url('#goo');
    filter: url('#goo');
    -webkit-animation: rotate-logo ease-in-out 9s infinite alternate;
    animation: rotate-logo ease-in-out 9s infinite alternate;
}

.blob {
    background: #2a4059;
    width: 50px;
    height: 50px;
    position: absolute;
    left: 50%;
    top: 60%;
    margin-top: -50px;
    margin-left: -50px;
    border-radius: 100%;
}

.blob:nth-child(1) {
    -webkit-animation: blob-anim-left ease-in-out 2.5s infinite alternate;
    animation: blob-anim-left ease-in-out 2.5s infinite alternate;
}

.blob:nth-child(2) {
    -webkit-animation: blob-anim-right ease-in-out 2.5s infinite alternate;
    animation: blob-anim-right ease-in-out 2.5s infinite alternate;
    width: 72px;
    height: 72px;

}

.blob:nth-child(3) {
    -webkit-animation: blob-anim-top-right ease-in-out 2.5s infinite alternate;
    animation: blob-anim-top-right ease-in-out 2.5s infinite alternate;
    width: 84px;
    height: 84px;
}

.line{
    width: 150px;
    height: 10px;
    margin: 150px;
    position: relative;
    filter: url('#goo');

}

.part1{
    width: 50%;
    height: 100%;
    background: #2a4059;
    transform: perspective(70px) rotateY(50deg);
    position: absolute;
    left: 0;
}

.part2{
    width: 50%;
    height: 100%;
    right: 0;
    position: absolute;
    background: #2a4059;
    transform: perspective(70px) rotateY(-50deg);
}

.part3{
    background: #2a4059;
    width: 100%;
    height: 100%;
}

.connect12{
    height: 14px;
    position: absolute;
    left: 50%;
    top: 60%;
    -webkit-animation: line-anim-con12 ease-in-out 2.5s infinite alternate;
    animation: line-anim-con12 ease-in-out 2.5s infinite alternate;
    .part3{
        height: 78% !important;
        top: 50%;
        transform: translateY(-50%);
        position: absolute;
    }
}

.connect23{
    width: 11px;
    position: absolute;
    left: 50%;
    top: 60%;
    -webkit-animation: line-anim-con23 ease-in-out 2.5s infinite alternate;
    animation: line-anim-con23 ease-in-out 2.5s infinite alternate;
}

.connect31{
    height: 11px;
    position: absolute;
    left: 50%;
    top: 60%;
    -webkit-animation: line-anim-con31 ease-in-out 2.5s infinite alternate;
    animation: line-anim-con31 ease-in-out 2.5s infinite alternate;
}

.break{
    position: relative;
    top: 350px;
}

https://jsfiddle.net/lastgiven/kqmabvxs/2/

如果有人可以看看他们是否可以帮助我解决这个问题,我将不胜感激。

提前致谢

【问题讨论】:

  • 我不知道我应该在这里看到什么表明某些东西不起作用。
  • 在新版本的 firefox 上,灰色徽标在粘糊糊效果的动画中带有白色部分,而在 chrome 上它可以完美运行。
  • 我们需要元素是纯色的。
  • 它在 Mac 上的 Firefox 57 上对我来说没有显示为白色。
  • 我在运行 jsfiddle 时看不到这一点。你在什么平台上使用什么版本的 Firefox?

标签: html css firefox svg


【解决方案1】:

“黏糊糊的效果”似乎是通过this post on css-tricks 流行起来的。虽然它很好地描述了它的工作原理,但它未能探索 SVG 过滤器为制定过滤器提供的不同可能性。看起来,过滤器原语的不同实现和 SVG 或 HTML 命名空间中元素上的应用程序目前在跨浏览器的呈现方面存在差异。

让我先解释一下可能性,然后给出一个跨浏览器比较的测试用例。

模糊输入图形

过滤器以

开头
<feGaussianBlur stdDeviation="20" />

生成模糊图像。虽然它会“涂抹”不同的颜色,但它的主要目的是在 alpha 颜色值不同的图像源边界上生成宽像素带。走得越远,部分透明度增加得越多。

提高 Alpha 通道的对比度

最初的想法利用了feColorMatrix 原语:

<feColorMatrix mode="matrix"
               values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 19 -9"
               result="cutoff" />

这有一个明显的缺点,即没有人了解这些数字的含义。理论上,使用feComponentTransfer 原语有一个数学上相同的版本:

<feComponentTransfer result="cutoff">
    <feFuncA type="linear" slope="19" intercept="-9" />
</feComponentTransfer>

它们都使用函数转换 RGBA 颜色的 alpha 值

(alpha) => slope * alpha + intercept

并将结果钳制在区间 [0, 1] 内,这样

alpha < (0 - intercept) / slope => 0
alpha > (1 - intercept) / slope => 1

有一小部分输入 alpha 在输出中被转换为部分透明度。 (对于值 19 和 -9,它介于 0.46...0.54 之间。)效果是将宽的模糊边界减少为非常细的线,现在“软化”所有角落并在附近的源对象之间产生“粘糊糊”的效果。

但是为什么要在输出中留下那条部分透明的细带呢?可以在完全不透明和完全透明之间进行严格区分:

<feComponentTransfer result="cutoff">
  <feFuncA type="discrete" tableValues="0 1" />
</feComponentTransfer>

输入值的范围减半:

alpha <  0.5 => 0
alpha >= 0.5 => 1

这可能会导致可见的抗锯齿伪影。对结果进行非常小的模糊可能会有优势:

<feComponentTransfer>
  <feFuncA type="discrete" tableValues="0 1" />
</feComponentTransfer>
<feGaussianBlur stdDeviation="1" result="cutoff" />

叠加原图

为避免对象内部不同颜色的“涂抹”,您可以将原件叠加在“goo”上。有几个过滤器原语,有些带有可以实现这一点的参数。基本叠加可以表述为

<feBlend in="SourceGraphic" in2="cutoff" />
// or
<feMerge>
    <feMergeNode in="cutoff" />
    <feMergeNode in="SourceGraphic" />
</feMerge>
// or
<feComposite operator="over" in="SourceGraphic" in2="cutoff" />

限制叠加到“goo”区域可以通过

<feComposite operator="atop" in="SourceGraphic" in2="cutoff" />

渲染结果

理论上,除了最后一个区别外,结果应该是一样的。我设计了一个测试用例,您可以在其中使用不同的过滤器及其设置。过滤器可以应用于 SVG 圆形和矩形,或 HTML div。您可以比较多色和单色对象的效果。这应该有助于找到具有最佳跨浏览器一致性的过滤器。

const filters = new (function () {
    const fBlur = d3.selectAll('feGaussianBlur:first-child');
    const pBlur = d3.select('feComponentTransfer + feGaussianBlur');
    const fFunc = d3.select('feFuncA[type=linear]');
    const fMatr = d3.select('feColorMatrix');
    let fComp = d3.selectAll('feComposite');
    let active;

    const ids = {
        fcd: '#component-discrete',
        fcl: '#component-linear',
        fmx: '#colormatrix'
    };
    const xmls = new XMLSerializer();
    const pre = d3.select('pre');

    this.activate = function (a) {
        active = ids[a];
        this.showSource();
    };

    this.showSource = function () {
        const source = xmls.serializeToString(d3.select(active).node())
          .replace(/>\s*</g, '><')
          .replace(/<fe/g, '\n<fe')
          .replace(/(<\/\w+>)(<\/\w+>)/g, '$1\n$2')
          .replace(/(<feFuncA|<feMergeNode)/g, '    $1')
          .trim()
        pre.text(source);
    };

    this.blur = function (deviation) {
        fBlur.attr('stdDeviation', deviation);
        this.showSource();
    };

    this.postBlur = function (deviation) {
        pBlur.attr('stdDeviation', deviation);
        this.showSource();
    };

    this.contrast = function (slope, intercept) {
        fFunc.attr('slope', slope)
            .attr('intercept', intercept);
        fMatr.attr('values', [
            "1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0", 
            slope, 
            intercept
        ].join(' '));
        this.showSource();
    };

    this.combinate = function (operator) {
        fComp.remove();
        switch (operator) {
        case 'none':
            return this.showSource();
        case 'merge':
            fComp = d3.selectAll('filter')
                .append('feMerge');
            fComp.append('feMergeNode')
                .attr('in', "cutoff");
            fComp.append('feMergeNode')
                .attr('in', "SourceGraphic");
            return this.showSource();
        case 'blend':
            fComp = d3.selectAll('filter')
                .append('feBlend');
            break;
        default:
            fComp = d3.selectAll('filter')
                .append('feComposite')
                .attr('operator', operator);
            break;
        }
        fComp.attr('in', "SourceGraphic")
            .attr('in2', "cutoff");
        this.showSource();
    };
})();

const tweaks = new (function () {
    this.blur = d3.select('#blur-select');
    const check = d3.select('#blur-select input');
    this.linear = d3.select('#linear-select');
    const selects = d3.selectAll('#linear-select input');
    const slope = selects.filter('#slope');
    const intercept = selects.filter('#intercept');
    const visSlope = d3.select('#visSlope');

    this.activate = function (active) {
        this.blur.classed('inactive', active !== 'fcd');
        check.property('disabled', active !== 'fcd');
        this.linear.classed('inactive', active === 'fcd');
        selects.property('disabled', active === 'fcd');
    };

    this.limit = function () {
        const min = 1 - slope.property('value');
        intercept.attr('min', min);
        if (min > intercept.property('value')) {
            intercept.property('value', min);
        }
        return [
            slope.property('value'), 
            intercept.property('value')
        ];
    };

    this.visualize = function (slope, intercept) {
        visSlope.attr('x1', -intercept * 100 / slope)
            .attr('x2', (1 - intercept) * 100 / slope);
    };
})();

const primitives = d3.selectAll('.primitive');
const examples =  d3.selectAll('.example');
const goo = d3.selectAll('.goo');

d3.selectAll('input[name=variant]').on('change', function () {
    if (!this.checked) return;
    examples.classed('hide', true)
        .filter(this.id).classed('hide', false);
}).dispatch('change');

d3.select('#color').on('change', function () {
    primitives.classed('bw', this.checked);
}).dispatch('change');

d3.select('#filter-select').on('change', function () {
    goo.attr('class', 'goo ' + this.value);
    filters.activate(this.value);
    tweaks.activate(this.value);
}).dispatch('change');

d3.select('#deviation').on('input change', function () {
    d3.select('#devNum').text(this.value);
    filters.blur(this.value);
}).dispatch('change');

tweaks.blur.on('change', function () {
    filters.postBlur(this.checked ? 1 : 0);
}).dispatch('change');

tweaks.linear.on('change', function () {
    const params = tweaks.limit();
    tweaks.visualize.apply(tweaks, params);
    filters.contrast.apply(filters, params);
}).dispatch('change');

d3.select('#combine-select').on('change', function (e) {
  filters.combinate(this.value);
}).dispatch('change');

function tweenFactory (forth) {
    return function () {
        let interpolator;
        const node = this,
            from = forth ? 0 : 500,
            to   = forth ? 500 : 0;
        if (this instanceof SVGElement) {
            interpolator = d3.interpolateTransformSvg(
                'translate(0, ' + from + ')',
                'translate(0, ' + to + ')'
            );
            return function (t) {
                node.setAttribute("transform", interpolator(t));
            };
        } else {
            interpolator = d3.interpolateTransformCss(
                'translate(0px, ' + from + 'px)',
                'translate(0px, ' + to + 'px)'
            );
            return function (t) {
                node.style.transform = interpolator(t);
            };
        }
    };
}

d3.selectAll('.move').transition()
    .on('start', function repeat () {
        d3.active(this)
            .duration(2000)
            .ease(d3.easeSinInOut)
            .tween('forth', tweenFactory(true))
        .transition()
            .duration(2000)
            .ease(d3.easeSinInOut)
            .tween('back', tweenFactory(false))
        .transition()
            .on('start', repeat);
    });
body {
  font-family: sans-serif;
  line-height: 2em;
}
.inactive {
  opacity: 0.5;
}
input[type=number] {
  width: 3em;
}
#vis {
   fill:none;
   stroke:black;
   margin-left:10px;
   vertical-align: middle;
}
pre {
  padding: 10px;
  border: 1px solid black;
  min-height: 10em;
  overflow-x: auto;
  clear: both;
}
.floating {
  float: left;
  margin-right: 30px;
}
.floating.example {
   width: 250px;
   height: 400px;
}
div.goo {
   position: relative;
   width: 500px;
   height: 800px;
   transform: scale(0.5);
   transform-origin: 0% 0%;
}
div.goo div {
  position: absolute;
  width: 200px;
  height: 200px;
  border-radius: 100%;
}
div.goo .move {
  width: 160px;
  height: 160px;
  border-radius: 0;
}
.hide {
  display: none;
}
.bw {
  fill: black;
  background: black !important;
}
.fcd {
  filter: url(#component-discrete);
}
.fcl {
  filter: url(#component-linear);
}
.fmx {
  filter: url(#colormatrix);
}
<script src="https://d3js.org/d3.v4.min.js"></script>

<svg xmlns="http://www.w3.org/2000/svg" class="floating" height="0" width="0">
  <filter id="colormatrix" filterUnits="userSpaceOnUse" x="0" y="0" width="500" height="800">
    <feGaussianBlur stdDeviation="20" />
    <feColorMatrix mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 19 -9" result="cutoff" />
    <feComposite operator="atop" in="SourceGraphic" in2="cutoff" />
  </filter>
  <filter id="component-linear" filterUnits="userSpaceOnUse" x="0" y="0" width="500" height="800">
    <feGaussianBlur stdDeviation="20" />
    <feComponentTransfer result="cutoff">
      <feFuncA type="linear" slope="19" intercept="-9" />
    </feComponentTransfer>
    <feComposite operator="atop" in="SourceG forraphic" in2="cutoff" />
  </filter>
  <filter id="component-discrete" filterUnits="userSpaceOnUse" x="0" y="0" width="500" height="800">
    <feGaussianBlur stdDeviation="20" />
    <feComponentTransfer>
      <feFuncA type="discrete" tableValues="0 1" />
    </feComponentTransfer>
    <feGaussianBlur id="post-blur" stdDeviation="1" result="cutoff" />
    <feComposite operator="atop" in="SourceGraphic" in2="cutoff" />
  </filter>
</svg>

<svg xmlns="http://www.w3.org/2000/svg" class="floating example" viewBox="0 0 500 800">
  <g class="goo fcd">
    <circle class="primitive" r="100" cx="150" cy="400" fill="#ff0000" />
    <circle class="primitive" r="100" cx="350" cy="400" fill="#00ff00" />
    <rect class="primitive move" x="170" y="70" width="160" height="160" fill="#0000ff" />
  </g>
</svg>

<div class="floating example hide">
<div class="goo fcd">
  <div class="primitive" style="background:#ff0000;left:50px;top:300px"></div>
  <div class="primitive" style="background:#00ff00;right:50px;top:300px"></div>
  <div class="primitive move" style="background:#0000ff;left:170px;top:70px"></div>
</div>
</div>

<div id="controls">
  <p>Filtered Objects
  <input name="variant" id="svg" type="radio" checked="checked"></input><label>SVG primitives</label>
  <input name="variant" id="div" type="radio"></input><label>HTML divs</label></p>

  <p><input id="color" type="checkbox"></input>
  <label>Set black and white</label></p>

  <p><label>Blur value</label><br/>
  <input id="deviation" type="range" min="5" max="50" step="5" value="20"></input>
  <span id="devNum">20</span></p>

  <p><label>Cutoff Filter</label><br/>
  <select id="filter-select">
    <option value="fcd" selected="selected">Component/discrete</option>
    <option value="fcl">Component/linear</option>
    <option value="fmx">ColorMatrix</option>
  </select></p>

  <p id="blur-select"><input type="checkbox" checked="checked"></input>
  <label>Post-Blur for discrete</label></p>

  <p id="linear-select"><label>slope</label> <input id="slope" type="number" min="2" step="1" value="19"></input>
  <label>intercept</label> <input id="intercept" type="number" min="-18" max="0" step="1" value="-9"></input> for linear
  <svg id="vis" width="100" height="20">
    <rect x="0.5" y="0.5" width="99" height="19" />
    <line x1="50" y1="20" x2="50" y2="0" style="stroke:red;opacity:0.7" />
    <line id="visSlope" x1="50" y1="20" x2="50" y2="0" />
  </svg></p>

  <p><label>Combining Filter</label><br/>
  <select id="combine-select">
    <option value="atop" selected="selected">Composite atop</option>
    <option value="over">Composite over</option>
    <option value="blend">Blend normal</option>
    <option value="merge">Merge</option>
    <option value="none">none</option>
  </select></p>
</div>

<pre></pre>

Codepen 上的相同外部。

到目前为止,我知道这些渲染错误:

  • Safari、Edge 和 IE 似乎不支持过滤 HTML 对象
  • ColorMatrix 在应用于 SVG 对象时在 Chrome 中似乎有问题
  • 据报道,ColorMatrix 和 Component/linear 在 Firefox 57/Windows 中存在错误

我还没有看到所有这些错误,所以请随时添加到此列表中或更正它。

相关帖子:SVG feGaussianBlur and feColorMatrix filters not working in Chrome?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-12-22
    • 2021-04-07
    • 2017-03-17
    • 2021-08-17
    • 2018-06-03
    • 1970-01-01
    • 2012-08-12
    相关资源
    最近更新 更多