javascript 在 Angularjs 指令中包含 SVG 模板

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13641105/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 19:21:06  来源:igfitidea点击:

Including SVG template in Angularjs directive

javascriptsvgangularjs

提问by mkly

<div ng-app="testrectApp">
<svg>
    <rect height="10" width="10" style="fill: #00ff00" />
    <testrect />
</svg>
</div>

And this is my directive

这是我的指示

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

But this is what the element ends up looking like in the browser. The fill twice is not a typo.

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

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

Here's a jsfiddle http://jsfiddle.net/RG2CF/

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

Is what I'm trying to do not possible or am I doing something wrong?

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

Thanks

谢谢

EDIT: I should add that the issue may have to do with the fact that the template svg rect is namespaced to xhtml instead of svg, but I'm unsure of how I can force this or namespace it back to svg, if that is actually the solution.

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

回答by Josef Pfleger

There is a templateNamespaceproperty you can set to svg:

templateNamespace您可以设置一个属性svg

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

Here is a linkto the documentation.

这是文档的链接

回答by Jason More

Angular 1.3 Update!

Angular 1.3 更新!

My original answer below was a giant hack at best. What you should really do is update to Angular 1.3 and set the templateNamespace:'svg'property on your directive. Look below at @Josef Pfleger answer for an example: Including SVG template in Angularjs directive

我在下面的原始答案充其量是一个巨大的黑客。你真正应该做的是更新到 Angular 1.3 并templateNamespace:'svg'在你的指令中设置属性。下面以@Josef Pfleger 回答为例:在 Angularjs 指令中包含 SVG 模板



Old Answer

旧答案

In case anyone comes across this in the future, I was able to create a compile function that works with svg templates. Its a bit ugly right now, so refactoring help is much appreciated.

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

live example: http://plnkr.co/edit/DN2M3M?p=preview

现场示例: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');
      };
    }
  })

use case:

用例:

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

回答by David Baird

Using an innerHTML shim for SVG (aka "innerSVG") provides another piece of this puzzle, as an alternative to Jason More's insightful answer. I feel this innerHTML/innerSVG approach is more elegant because it does not require a special compile function like Jason's answer, but it has at least one drawback: it does not work if "replace: true" is set on the directive. (The innerSVG approach is discussed here: Is there some innerHTML replacement in SVG/XML?)

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

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

Here's a Plunker to try: http://plnkr.co/edit/nEqfbbvpUCOVC1XbTKdQ?p=preview

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

The reason why this works is: SVG elements live within the XML namespace, http://www.w3.org/2000/svg, and as such, they must either be parsed with DOMParser() or instantiated with createElementNS() (as in Jason's answer), both of which can set the namespace appropriately. It would be nice if this process of using SVG was as easy as HTML, but unfortunately their DOM structures are subtly different which results in the need to supply our own innerHTML shim. Now, if we could also get "replace: true" to work with this, that will be something good!

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

EDIT: Updated the appendChild() loop to workaround contents changing while loop runs.

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

回答by Ben Lesh

Currently SVG tags don't support dynamic addition of tags, at least in Chrome. It doesn't even display the new rect, nor would it even if you just added it directly with the DOM or JQuery.append(). Have a look at just trying to do it with JQuery

目前 SVG 标签不支持动态添加标签,至少在 Chrome 中是这样。它甚至不显示新的 rect,即使您直接使用 DOM 或 JQuery.append() 添加它也不会显示。看看只是尝试用 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" />');
});

My guess is you're going to see some really crazy behavior no matter what you do in this manner. It seems like it's an issue with the browser, and not so much Angular. I'd recommend reporting it to the Chromium project.

我的猜测是,无论您以这种方式做什么,您都会看到一些非常疯狂的行为。这似乎是浏览器的问题,而不是 Angular。我建议将其报告给 Chromium 项目

EDIT:

编辑:

It looks like it might work if you add it via the DOM 100% programmatically: How to make Chrome redraw SVG dynamically added content?

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

回答by Jason Siefken

angular doesn't pay attentention to namespaces when creating elements (though elements seem to be cloned with namepsace of a parent), so new directives inside <svg>won't work without a custom compile function.

angular 在创建元素时不注意命名空间(尽管元素似乎是用父元素的 namepsace 克隆的),因此<svg>如果没有自定义编译函数,内部的新指令将无法工作。

Something like the following can get around the issue by doing the creation of the dom elements itself. For this to work, you need to have xmlns="http://www.w3.org/2000/svg"as an attribute on the root element of templateand your template must be valid XML (having only one root element).

像下面这样的事情可以通过创建 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);