Angular和SVG过滤器

35

我在使用AngularJS处理SVG时遇到一个奇怪的问题。我正在使用$routeProvider服务来配置我的路由。当我在模板中加入这个简单的SVG时,一切正常:

<div id="my-template">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
        <rect fill="red" height="200" width="300" />
    </svg>
    // ...
</div>

但是当我添加一个过滤器,使用例如这段代码:

<div id="my-template">
    <svg xmlns="http://www.w3.org/2000/svg" version="1.1">
        <defs>
            <filter id="blurred">
                <feGaussianBlur stdDeviation="5"/>
            </filter>
        </defs>
        <rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
    </svg>
</div>

然后:

  • 它在我的主页上起作用。
  • 使用Firefox,SVG在其他页面上不再可见,但仍然占据了原来显示的位置。而使用Chrome,SVG是可见的,但没有任何模糊效果。
  • 当我手动移除(使用 Firebug)filter样式时,SVG又重新可见。

这是路由配置:

$routeProvider
    .when('/site/other-page/', {
            templateUrl : 'view/Site/OtherPage.html',
            controller : 'Site.OtherPage'
    })
    .when('/', {
            templateUrl : 'view/Site/Home.html',
            controller : 'Site.Home'
    })
    .otherwise({
        redirectTo : '/'
    })
;

Fiddle

请注意,我无法在 Fiddle 中重现这个问题,虽然在 Firefox 中它“有效”。

我尝试使用 document.createElementNS() 创建整个 SVG,但没有成功。

是否有人知道发生了什么?


1
这是一个特定于浏览器的问题吗?它在Chrome上工作吗? - musically_ut
我认为发生的情况是angularjs将过滤器运算符应用于svg元素,因为在angularjs上下文中url(#blurred)计算为false,它隐藏了<rect>元素。 - fabrizioM
@musically_ut 我已经编辑了我的问题:在Chrome中,rect是可见的,但没有模糊效果。 - Blackhole
@fabrizioM 听起来有点奇怪...为什么不放在主页上呢? - Blackhole
为什么你不使用正常的过滤器语法 <rect filter="url(#blurred)"> ? - Michael Mullany
5个回答

72

问题

问题在于我的HTML页面中有一个<base>标签。因此,用于标识过滤器的IRI不再是相对于当前页面,而是相对于<base>标签中指定的URL。

这个URL也是我的主页URL,例如http://example.com/my-folder/

对于除主页以外的页面,例如http://example.com/my-folder/site/other-page/#blurred会被计算为绝对URLhttp://example.com/my-folder/#blurred。但是,对于没有JavaScript(因此没有AngularJS)的简单GET请求来说,这只是我的基本页面,没有加载模板。因此,在这些页面上,#blurred过滤器不存在

在这种情况下,Firefox不渲染<rect>(这是正常行为,参见W3C建议)。Chrome则根本不应用过滤器。

对于主页,#blurred也被计算为绝对URLhttp://example.com/my-folder/#blurred。但是这一次,这也是当前URL。不需要发送GET请求,因此#blurred过滤器存在

我本应该注意到到http://example.com/my-folder/的额外请求,但为了辩护,它在众多JavaScript文件的请求中被忽略了。

解决方案

如果<base>标签是必需的,则解决方案是使用绝对IRI来标识过滤器。借助AngularJS,这很简单。在控制器或与SVG相关联的指令中注入$location服务,并使用absUrl()获取器:

$scope.absUrl = $location.absUrl();

现在,在SVG中,只需使用这个属性:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
    <defs>
        <filter id="blurred">
            <feGaussianBlur stdDeviation="5"/>
        </filter>
    </defs>
    <rect style="filter:url({{absUrl}}#blurred)" fill="red" height="200" width="300" />
</svg>

相关: 当HTML页面中有BASE标签时,SVG渐变会变成黑色?


感谢您分享您的发现。我点赞它,因为它可能很快就会对我有所帮助 :) - Rémi
在Chrome和Firefox上运行良好,但在IE11上不行。有趣的是,没有修复程序时它在IE11上也能正常工作。很奇怪。 - alans
如果使用Angular2,有一种更好的解决方案来删除基本标签https://dev59.com/xlsW5IYBdhLWcg3w2aGK#34535256 - maxbellec
由于 Angular 路由的原因,动态创建的过滤器或渐变无法正常工作。 - Kira

1
看起来像我之前观察到的一种行为。根本原因是您最终拥有具有相同ID(模糊)的多个元素(过滤器)。不同的浏览器处理方式不同...
以下是我重现您的情况所做的事情:http://jsfiddle.net/z5cwZ/ 它有两个SVG,其中一个被隐藏,Firefox不显示任何内容。
避免冲突ID有两种可能性。首先,您可以从模板生成唯一的ID(我无法帮助使用AngularJS进行此操作)。这是一个例子:http://jsfiddle.net/KbCLB/1/ 第二种可能性,使用AngularJS可能更容易,即将过滤器放在单个SVG之外(http://jsfiddle.net/zAbgr/1/):
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="0" height="0">
    <defs>
        <filter id="blurred">
            <feGaussianBlur stdDeviation="10" />
        </filter>
    </defs>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" style="display:none">
    <rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
</svg>
<br/>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
    <rect style="filter:url(#blurred)" fill="red" height="200" width="300" />
</svg>

问题不在这里 ;-). 当我改变页面时,先前的 filter,带有ID,会被Angular删除,就像页面的其余部分一样。此外,如果我在每个页面上以不同的名称命名过滤器,也无法解决问题。 - Blackhole
我并不是指当你切换页面时旧元素仍然存在。我的意思是在你的模板中有一个id="blurred",并且在所有从模板生成的元素中(在给定页面上)都有这个id。我很想知道我的第二个解决方案是否解决了这个问题(将过滤器放在模板外部并在模板中使用它)。 - Rémi
好的,谢谢。太糟糕了:( 你能复制你的DOM(使用火狐浏览器,打开检查器,右键点击HTML标签并选择“复制外部HTML”)并分享给我们看看吗? - Rémi
我在考虑整个(失败的)页面(或两者)的DOM,以便我们可以尝试黑客攻击。如果您可以分享它(在pastebin.com或piratepad.net或任何其他地方),我很乐意查看。 - Rémi
我现在更好地理解您如何生成页面了,谢谢。我已经在fiddle上进行了一些修改,给过滤器上放置了不同的id,至少这样可以解决我的火狐浏览器的问题:http://jsfiddle.net/PjUbL/ - Rémi
显示剩余4条评论

0

最近我自己遇到了这个问题,如果SVG在不同的路由上使用,你必须为每个路径添加$location。更好的实现方式是使用window.location.href而不是$location服务。


0

我也遇到了SVG链接的问题,是因为<base>标签导致的。由于Strict Contextual Escaping的限制,当我尝试连接绝对URL时,会抛出错误。

我的解决方案是编写一个过滤器,添加绝对URL并信任连接操作。

angular.module('myApp').filter('absUrl', ['$location', '$sce', function ($location, $sce) {
    return function (id) {
        return $sce.trustAsResourceUrl($location.absUrl() + id);
    };
}]);

然后像这样使用它:{{'#SVGID_67_' | absUrl}

结果:http://www.example.com/deep/url/to/page#SVGID_67_,没有 $sce 错误


0

刚遇到了这个问题。在@Blackhole的答案基础上,我的解决方案是添加一个指令,改变每个填充属性的值。

angular.module('myApp').directive('fill', function(
    $location
) { 'use strict';
    var absUrl = 'url(' + $location.absUrl() + '#';

    return {
        restrict: 'A',
        link: function($scope, $element, $attrs) {
            $attrs.$set('fill', $attrs.fill.replace(/url\(#/g, absUrl));
        }
    };
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接