ClippathManager.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. var Definable = require("./Definable");
  2. var zrUtil = require("../../core/util");
  3. var matrix = require("../../core/matrix");
  4. /**
  5. * @file Manages SVG clipPath elements.
  6. * @author Zhang Wenli
  7. */
  8. /**
  9. * Manages SVG clipPath elements.
  10. *
  11. * @class
  12. * @extends Definable
  13. * @param {number} zrId zrender instance id
  14. * @param {SVGElement} svgRoot root of SVG document
  15. */
  16. function ClippathManager(zrId, svgRoot) {
  17. Definable.call(this, zrId, svgRoot, 'clipPath', '__clippath_in_use__');
  18. }
  19. zrUtil.inherits(ClippathManager, Definable);
  20. /**
  21. * Update clipPath.
  22. *
  23. * @param {Displayable} displayable displayable element
  24. */
  25. ClippathManager.prototype.update = function (displayable) {
  26. var svgEl = this.getSvgElement(displayable);
  27. if (svgEl) {
  28. this.updateDom(svgEl, displayable.__clipPaths, false);
  29. }
  30. var textEl = this.getTextSvgElement(displayable);
  31. if (textEl) {
  32. // Make another clipPath for text, since it's transform
  33. // matrix is not the same with svgElement
  34. this.updateDom(textEl, displayable.__clipPaths, true);
  35. }
  36. this.markUsed(displayable);
  37. };
  38. /**
  39. * Create an SVGElement of displayable and create a <clipPath> of its
  40. * clipPath
  41. *
  42. * @param {Displayable} parentEl parent element
  43. * @param {ClipPath[]} clipPaths clipPaths of parent element
  44. * @param {boolean} isText if parent element is Text
  45. */
  46. ClippathManager.prototype.updateDom = function (parentEl, clipPaths, isText) {
  47. if (clipPaths && clipPaths.length > 0) {
  48. // Has clipPath, create <clipPath> with the first clipPath
  49. var defs = this.getDefs(true);
  50. var clipPath = clipPaths[0];
  51. var clipPathEl;
  52. var id;
  53. var dom = isText ? '_textDom' : '_dom';
  54. if (clipPath[dom]) {
  55. // Use a dom that is already in <defs>
  56. id = clipPath[dom].getAttribute('id');
  57. clipPathEl = clipPath[dom]; // Use a dom that is already in <defs>
  58. if (!defs.contains(clipPathEl)) {
  59. // This happens when set old clipPath that has
  60. // been previously removed
  61. defs.appendChild(clipPathEl);
  62. }
  63. } else {
  64. // New <clipPath>
  65. id = 'zr' + this._zrId + '-clip-' + this.nextId;
  66. ++this.nextId;
  67. clipPathEl = this.createElement('clipPath');
  68. clipPathEl.setAttribute('id', id);
  69. defs.appendChild(clipPathEl);
  70. clipPath[dom] = clipPathEl;
  71. } // Build path and add to <clipPath>
  72. var svgProxy = this.getSvgProxy(clipPath);
  73. if (clipPath.transform && clipPath.parent.invTransform && !isText) {
  74. /**
  75. * If a clipPath has a parent with transform, the transform
  76. * of parent should not be considered when setting transform
  77. * of clipPath. So we need to transform back from parent's
  78. * transform, which is done by multiplying parent's inverse
  79. * transform.
  80. */
  81. // Store old transform
  82. var transform = Array.prototype.slice.call(clipPath.transform); // Transform back from parent, and brush path
  83. matrix.mul(clipPath.transform, clipPath.parent.invTransform, clipPath.transform);
  84. svgProxy.brush(clipPath); // Set back transform of clipPath
  85. clipPath.transform = transform;
  86. } else {
  87. svgProxy.brush(clipPath);
  88. }
  89. var pathEl = this.getSvgElement(clipPath);
  90. clipPathEl.innerHTML = '';
  91. /**
  92. * Use `cloneNode()` here to appendChild to multiple parents,
  93. * which may happend when Text and other shapes are using the same
  94. * clipPath. Since Text will create an extra clipPath DOM due to
  95. * different transform rules.
  96. */
  97. clipPathEl.appendChild(pathEl.cloneNode());
  98. parentEl.setAttribute('clip-path', 'url(#' + id + ')');
  99. if (clipPaths.length > 1) {
  100. // Make the other clipPaths recursively
  101. this.updateDom(clipPathEl, clipPaths.slice(1), isText);
  102. }
  103. } else {
  104. // No clipPath
  105. if (parentEl) {
  106. parentEl.setAttribute('clip-path', 'none');
  107. }
  108. }
  109. };
  110. /**
  111. * Mark a single clipPath to be used
  112. *
  113. * @param {Displayable} displayable displayable element
  114. */
  115. ClippathManager.prototype.markUsed = function (displayable) {
  116. var that = this; // displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
  117. if (displayable.__clipPaths) {
  118. zrUtil.each(displayable.__clipPaths, function (clipPath) {
  119. if (clipPath._dom) {
  120. Definable.prototype.markUsed.call(that, clipPath._dom);
  121. }
  122. if (clipPath._textDom) {
  123. Definable.prototype.markUsed.call(that, clipPath._textDom);
  124. }
  125. });
  126. }
  127. };
  128. var _default = ClippathManager;
  129. module.exports = _default;