angularjs - icon - Filtros angulares y SVG
icon angular 2 (5)
Me encontré con un comportamiento extraño al usar SVG con AngularJS. Estoy usando el servicio $routeProvider
para configurar mis rutas. Cuando pongo este simple SVG en mis plantillas, todo está bien:
<div id="my-template">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<rect fill="red" height="200" width="300" />
</svg>
// ...
</div>
Pero cuando agrego un filtro, con este código, por ejemplo:
<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>
Entonces:
- Funciona en mi página de inicio .
- Con Firefox , el SVG ya no está visible en las otras páginas , pero aún deja espacio donde debería haber estado. Con Chrome , el SVG es visible, pero no se ve borroso en absoluto.
- El SVG es visible nuevamente cuando elimino manualmente (con Firebug) el estilo de
filter
.
Aquí está la configuración de rutas:
$routeProvider
.when(''/site/other-page/'', {
templateUrl : ''view/Site/OtherPage.html'',
controller : ''Site.OtherPage''
})
.when(''/'', {
templateUrl : ''view/Site/Home.html'',
controller : ''Site.Home''
})
.otherwise({
redirectTo : ''/''
})
;
Tenga en cuenta que no reproduje el problema con Chrome en un Fiddle, aunque "funciona" con Firefox.
Intenté en vano crear mi SVG completo con document.createElementNS()
.
¿Alguien tiene una idea de lo que está sucediendo?
El problema
El problema es que hay una etiqueta <base>
en mi página HTML. Por lo tanto, el IRI utilizado para identificar el filtro ya no es relativo a la página actual, sino a la URL indicada en la etiqueta <base>
.
Esta URL también era la URL de mi página de inicio, http://example.com/my-folder/
, por ejemplo.
Para las páginas que no sean la página de inicio, http://example.com/my-folder/site/other-page/
por ejemplo, #blurred
se calculó en la URL absoluta http://example.com/my-folder/#blurred
. Pero para una simple solicitud GET, sin JavaScript, y por lo tanto sin AngularJS, esta es simplemente mi página base, sin plantilla cargada. Por lo tanto, el filtro #blurred
no existe en estas páginas.
En tales casos, Firefox no representa el <rect>
(que es el comportamiento normal , consulte la recomendación del W3C ). Chrome simplemente no aplica el filtro.
Para la página de inicio, #blurred
también se calcula en la URL absoluta http://example.com/my-folder/#blurred
. Pero esta vez, esta es también la URL actual. No es necesario enviar una solicitud GET y, por lo #blurred
existe el filtro #blurred
.
Debería haber visto la solicitud adicional a http://example.com/my-folder/
, pero en mi defensa, se perdió en una plétora de otras solicitudes a archivos JavaScript.
La solución
Si la etiqueta <base>
es obligatoria, la solución es usar un IRI absoluto para identificar el filtro. Con la ayuda de AngularJS, esto es bastante simple. En el controlador o en la directiva que está vinculada al SVG, inyecte el servicio $location
y use el getter absUrl()
:
$scope.absUrl = $location.absUrl();
Ahora, en el SVG, solo usa esta propiedad:
<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>
Relacionado: ¿ Gradiente SVG se vuelve negro cuando hay una etiqueta BASE en la página HTML?
Acabo de toparme con este problema. Expandiéndome de la respuesta de @ Blackhole, mi solución fue agregar una directiva que cambia el valor de cada atributo de relleno.
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));
}
};
});
Parece un comportamiento que observé antes. La causa principal es que termina teniendo múltiples elementos (filtros) con la misma identificación (borrosa). Los diferentes navegadores lo manejan de manera diferente ...
Aquí está lo que hice para reproducir su caso: http://jsfiddle.net/z5cwZ/ Tiene dos svg y uno está oculto, firefox no muestra ninguno.
Hay dos posibilidades para evitar identificadores conflictivos. Primero, puede generar identificadores únicos a partir de su plantilla (no puedo evitar hacerlo con angularjs resistente). Aquí hay un ejemplo: http://jsfiddle.net/KbCLB/1/
La segunda posibilidad, y puede ser más fácil con angularjs, es poner el filtro fuera de los svgs individuales ( 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>
Recientemente me encontré con este problema, si el svg se usa en diferentes rutas, tendrá que agregar $ ubicación a cada ruta. Una mejor forma de implementar esto es simplemente usar window.location.href en lugar del servicio $ location.
También tuve problemas con svg link: hrefs, y sí, el problema es debido a la <base>
: tuve errores al concatenar la url absoluta debido a las restricciones de Estricto Desplazamiento Contextual .
Mi solución fue escribir un filtro que agregara la URL absoluta y también confiara en la concatenación.
angular.module(''myApp'').filter(''absUrl'', [''$location'', ''$sce'', function ($location, $sce) {
return function (id) {
return $sce.trustAsResourceUrl($location.absUrl() + id);
};
}]);
Y luego usarlo así: {{''#SVGID_67_'' | absUrl}
{{''#SVGID_67_'' | absUrl}
Resultado: http://www.example.com/deep/url/to/page#SVGID_67_
y sin errores de $ sce