ClippathManager.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. /**
  2. * @file Manages SVG clipPath elements.
  3. * @author Zhang Wenli
  4. */
  5. import Definable from './Definable';
  6. import * as zrUtil from '../../core/util';
  7. import * as matrix from '../../core/matrix';
  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 (
  47. parentEl,
  48. clipPaths,
  49. isText
  50. ) {
  51. if (clipPaths && clipPaths.length > 0) {
  52. // Has clipPath, create <clipPath> with the first clipPath
  53. var defs = this.getDefs(true);
  54. var clipPath = clipPaths[0];
  55. var clipPathEl;
  56. var id;
  57. var dom = isText ? '_textDom' : '_dom';
  58. if (clipPath[dom]) {
  59. // Use a dom that is already in <defs>
  60. id = clipPath[dom].getAttribute('id');
  61. clipPathEl = clipPath[dom];
  62. // Use a dom that is already in <defs>
  63. if (!defs.contains(clipPathEl)) {
  64. // This happens when set old clipPath that has
  65. // been previously removed
  66. defs.appendChild(clipPathEl);
  67. }
  68. }
  69. else {
  70. // New <clipPath>
  71. id = 'zr' + this._zrId + '-clip-' + this.nextId;
  72. ++this.nextId;
  73. clipPathEl = this.createElement('clipPath');
  74. clipPathEl.setAttribute('id', id);
  75. defs.appendChild(clipPathEl);
  76. clipPath[dom] = clipPathEl;
  77. }
  78. // Build path and add to <clipPath>
  79. var svgProxy = this.getSvgProxy(clipPath);
  80. if (clipPath.transform
  81. && clipPath.parent.invTransform
  82. && !isText
  83. ) {
  84. /**
  85. * If a clipPath has a parent with transform, the transform
  86. * of parent should not be considered when setting transform
  87. * of clipPath. So we need to transform back from parent's
  88. * transform, which is done by multiplying parent's inverse
  89. * transform.
  90. */
  91. // Store old transform
  92. var transform = Array.prototype.slice.call(
  93. clipPath.transform
  94. );
  95. // Transform back from parent, and brush path
  96. matrix.mul(
  97. clipPath.transform,
  98. clipPath.parent.invTransform,
  99. clipPath.transform
  100. );
  101. svgProxy.brush(clipPath);
  102. // Set back transform of clipPath
  103. clipPath.transform = transform;
  104. }
  105. else {
  106. svgProxy.brush(clipPath);
  107. }
  108. var pathEl = this.getSvgElement(clipPath);
  109. clipPathEl.innerHTML = '';
  110. /**
  111. * Use `cloneNode()` here to appendChild to multiple parents,
  112. * which may happend when Text and other shapes are using the same
  113. * clipPath. Since Text will create an extra clipPath DOM due to
  114. * different transform rules.
  115. */
  116. clipPathEl.appendChild(pathEl.cloneNode());
  117. parentEl.setAttribute('clip-path', 'url(#' + id + ')');
  118. if (clipPaths.length > 1) {
  119. // Make the other clipPaths recursively
  120. this.updateDom(clipPathEl, clipPaths.slice(1), isText);
  121. }
  122. }
  123. else {
  124. // No clipPath
  125. if (parentEl) {
  126. parentEl.setAttribute('clip-path', 'none');
  127. }
  128. }
  129. };
  130. /**
  131. * Mark a single clipPath to be used
  132. *
  133. * @param {Displayable} displayable displayable element
  134. */
  135. ClippathManager.prototype.markUsed = function (displayable) {
  136. var that = this;
  137. if (displayable.__clipPaths && displayable.__clipPaths.length > 0) {
  138. zrUtil.each(displayable.__clipPaths, function (clipPath) {
  139. if (clipPath._dom) {
  140. Definable.prototype.markUsed.call(that, clipPath._dom);
  141. }
  142. if (clipPath._textDom) {
  143. Definable.prototype.markUsed.call(that, clipPath._textDom);
  144. }
  145. });
  146. }
  147. };
  148. export default ClippathManager;