【问题标题】:Including SVG template in Angularjs directive在 Angularjs 指令中包含 SVG 模板
【发布时间】:2012-11-18 10:20:43
【问题描述】:
<div ng-app="testrectApp">
<svg>
    <rect height="10" width="10" style="fill: #00ff00" />
    <testrect />
</svg>
</div>

这是我的指令

module.directive('testrect', function() {
    return {
        restrict: 'E',
        template: '<rect top="20" left="20" height="10" width="10" style="fill: #ff00ff" />',
        replace: true
    };
});

但这就是元素最终在浏览器中的样子。填充两次不是错字。

<rect top="20" left="20" height="10" width="10" style="fill: #ff00ff;fill: #ff00ff"></rect>

这是一个 jsfiddle http://jsfiddle.net/RG2CF/

我试图做的事情是不可能的,还是我做错了什么?

谢谢

编辑:我应该补充一点,这个问题可能与模板 svg rect 被命名为 xhtml 而不是 svg 的事实有关,但我不确定如何强制这个或命名空间回到 svg,如果这实际上是解决方案。

【问题讨论】:

标签: javascript svg angularjs


【解决方案1】:

目前 SVG 标签不支持动态添加标签,至少在 Chrome 中是这样。它甚至不会显示新的矩形,即使您只是直接使用 DOM 或 JQuery.append() 添加它也不会显示。 Have a look at just trying to do it with JQuery

// this doesn't even work right. Slightly different error than yours.
$(function() {
    $('svg').append('<rect top="20" left="20" height="10" width="10" style="fill: #ff00ff" />');
});

我的猜测是,无论你以这种方式做什么,你都会看到一些非常疯狂的行为。这似乎是浏览器的问题,而不是 Angular 的问题。 I'd recommend reporting it to the Chromium project.

编辑:

如果您以编程方式通过 DOM 100% 添加它,它看起来可能会起作用:How to make Chrome redraw SVG dynamically added content?

【讨论】:

  • 我也遇到了 Firefox 问题。您真的发现这在您的 Firefox 版本中有效吗?
  • 是的,我在 FF 中也遇到了问题。
  • 如果您希望将来可以使用此功能,请联系 whatwg.org 和/或 w3.org/html/wg。因为根据当前的规范,上面的 rect 只会成为 DOM 中的 HTMLUnknownElement。
【解决方案2】:

Angular 1.3 更新!

我在下面的原始答案充其量是一个巨大的黑客攻击。你真正应该做的是更新到 Angular 1.3 并在你的指令上设置 templateNamespace:'svg' 属性。请看下面@Josef Pfleger 的答案示例:Including SVG template in Angularjs directive


旧答案

如果将来有人遇到这种情况,我可以创建一个与 svg 模板一起使用的编译函数。它现在有点难看,因此非常感谢重构帮助。

实时示例:http://plnkr.co/edit/DN2M3M?p=preview

app.service('svgService', function(){

    var xmlns = "http://www.w3.org/2000/svg";
    var compileNode = function(angularElement){
      var rawElement = angularElement[0];

      //new lines have no localName
      if(!rawElement.localName){
        var text = document.createTextNode(rawElement.wholeText);
        return angular.element(text);
      }
      var replacement = document.createElementNS(xmlns,rawElement.localName);
      var children = angularElement.children();

      angular.forEach(children, function (value) {
        var newChildNode = compileNode(angular.element(value));
        replacement.appendChild(newChildNode[0]);
      });

      if(rawElement.localName === 'text'){
        replacement.textContent = rawElement.innerText;
      }

      var attributes = rawElement.attributes;
      for (var i = 0; i < attributes.length ; i++) {
        replacement.setAttribute(attributes[i].name, attributes[i].value);
      }

      angularElement.replaceWith(replacement);

      return angular.element(replacement);
    };

    this.compile = function(elem, attrs, transclude) {
      compileNode(elem);

      return function postLink(scope, elem,attrs,controller){
//        console.log('link called');
      };
    }
  })

用例:

app.directive('ngRectAndText', function(svgService) {
  return {
    restrict: 'E',
    template: 
    '<g>'
    + '<rect x="140" y="150" width="25" height="25" fill="red"></rect>'
    + '<text x="140" y="150">Hi There</text>'
    + '</g>'
    ,
    replace: true,
    compile: svgService.compile
  };
});

【讨论】:

  • 老兄,在谷歌搜索如何创建 svg 元素 3 或 4 小时后,您的服务是第一个对我有用的代码。谢谢伙计,你是一个活生生的救星!
  • 这很好,但我发现它不适用于 defs 部分中的模式。它们被插入并渲染结束,但渲染看起来与原始的非常不同
  • 还有一个:如果我复制并粘贴生成的 svg 并将整个内容再次插入到某个 div 中,它看起来就像预期的那样,即使在 IE 中也是如此。
  • 感谢这段代码!到目前为止,我发现了一个问题,对于&lt;text&gt; 元素,您没有在replacement 中插入文本。我会尝试修改代码,但我对 DOM 了解不多。
  • 现在有一个templateNamespace 属性可以设置为svg(见我的回答)。
【解决方案3】:

为 SVG 使用 innerHTML shim(又名“innerSVG”)提供了另一个难题,作为 Jason More 富有洞察力的答案的替代方案。我觉得这种 innerHTML/innerSVG 方法更优雅,因为它不需要像 Jason 的答案那样的特殊编译函数,但它至少有一个缺点:如果在指令上设置了“replace: true”,它就不起作用。 (innerSVG 方法在这里讨论:Is there some innerHTML replacement in SVG/XML?

// Inspiration for this derived from a shim known as "innerSVG" which adds
// an "innerHTML" attribute to SVGElement:
Object.defineProperty(SVGElement.prototype, 'innerHTML', {
  get: function() {
    // TBD!
  },
  set: function(markup) {
    // 1. Remove all children
    while (this.firstChild) {
      this.removeChild(this.firstChild);
    }

    // 2. Parse the SVG
    var doc = new DOMParser().parseFromString(
        '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
        + markup
        + '</svg>',
        'application/xml'
    );

    // 3. Import the parsed SVG and grab the childNodes
    var contents = this.ownerDocument.importNode(doc.documentElement, true).childNodes;

    // 4. Append the childNodes to this node
    // Note: the following for loop won't work because as items are appended,
    // they are removed from contents (you'd end up with only every other
    // element getting appended and the other half remaining still in the
    // original document tree):
    //for (var i = 0; i < contents.length; i++) {
    //  this.appendChild(contents[i]);
    //}
    while (contents.length) {
      this.appendChild(contents[0]);
    }
  },
  enumerable: false,
  configurable: true
});

这是一个可以尝试的 Plunker:http://plnkr.co/edit/nEqfbbvpUCOVC1XbTKdQ?p=preview

这样做的原因是:SVG 元素存在于 XML 命名空间 http://www.w3.org/2000/svg 中,因此,它们必须使用 DOMParser() 解析或使用 createElementNS() 实例化(如 Jason 的回答),两者可以适当地设置命名空间。如果这个使用 SVG 的过程像 HTML 一样简单,那就太好了,但不幸的是,它们的 DOM 结构略有不同,这导致需要提供我们自己的 innerHTML shim。现在,如果我们也可以让“replace: true”来处理它,那将是一件好事!

编辑:将 appendChild() 循环更新为在循环运行时更改内容的解决方法。

【讨论】:

    【解决方案4】:

    angular 在创建元素时不注意命名空间(尽管元素似乎是用父级的命名空间克隆的),所以&lt;svg&gt; 中的新指令在没有自定义编译函数的情况下将无法工作。

    通过创建 dom 元素本身可以解决以下问题。为此,您需要将xmlns="http://www.w3.org/2000/svg" 作为template 的根元素的属性,并且您的模板必须是有效的XML(只有一个根元素)。

    directiveGenerator = function($compile) {
      var ret, template;
      template = "<g xmlns=\"http://www.w3.org/2000/svg\"> +
                 "<rect top=\"20\" left=\"20\" height=\"10\" width=\"10\" style=\"fill: #ff00ff\" />" +
                 "</g>";
      ret = {
        restrict: 'E',
        compile: function(elm, attrs, transclude) {
          var templateElms;
          templateElms = (new DOMParser).parseFromString(template, 'application/xml');
          elm.replaceWith(templateElms.documentElement);
          return function(scope, elm, attrs) {
            // Your link function
          };
        }
      };
      return ret;
    };
    
    angular.module('svg.testrect', []).directive('testrec', directiveGenerator);
    

    【讨论】:

      【解决方案5】:

      有一个templateNamespace 属性可以设置为svg

      module.directive('testrect', function() {
          return {
              restrict: 'E',
              templateNamespace: 'svg',
              template: '<rect .../>',
              replace: true
          };
      });
      

      这是文档的link

      【讨论】:

      • 这是正确的答案。我将更新我的答案以指向您的答案
      猜你喜欢
      • 1970-01-01
      • 2013-11-03
      • 2015-09-13
      • 2016-09-11
      • 1970-01-01
      • 2018-04-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多