14b3537e0540c6b7df15f72a5855ff8e99345164f7f1a9bf5226e4243003cef878a3802f6527158d167c9fc61eaad40a738bfd05f626e402e48278dfaf400b 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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 Displayable from '../../graphic/Displayable';
  8. import Path from '../../graphic/Path';
  9. import {path} from '../graphic';
  10. import { Dictionary } from '../../core/types';
  11. import { isClipPathChanged } from '../../canvas/helper';
  12. import { getClipPathsKey, getIdURL } from '../../svg/helper';
  13. import { createElement } from '../../svg/core';
  14. type PathExtended = Path & {
  15. _dom: SVGElement
  16. }
  17. export function hasClipPath(displayable: Displayable) {
  18. const clipPaths = displayable.__clipPaths;
  19. return clipPaths && clipPaths.length > 0;
  20. }
  21. /**
  22. * Manages SVG clipPath elements.
  23. */
  24. export default class ClippathManager extends Definable {
  25. private _refGroups: Dictionary<SVGElement> = {};
  26. private _keyDuplicateCount: Dictionary<number> = {};
  27. constructor(zrId: number, svgRoot: SVGElement) {
  28. super(zrId, svgRoot, 'clipPath', '__clippath_in_use__');
  29. }
  30. markAllUnused() {
  31. super.markAllUnused();
  32. const refGroups = this._refGroups;
  33. for (let key in refGroups) {
  34. if (refGroups.hasOwnProperty(key)) {
  35. this.markDomUnused(refGroups[key]);
  36. }
  37. }
  38. this._keyDuplicateCount = {};
  39. }
  40. private _getClipPathGroup(displayable: Displayable, prevDisplayable: Displayable) {
  41. if (!hasClipPath(displayable)) {
  42. return;
  43. }
  44. const clipPaths = displayable.__clipPaths;
  45. const keyDuplicateCount = this._keyDuplicateCount;
  46. let clipPathKey = getClipPathsKey(clipPaths);
  47. if (isClipPathChanged(clipPaths, prevDisplayable && prevDisplayable.__clipPaths)) {
  48. keyDuplicateCount[clipPathKey] = keyDuplicateCount[clipPathKey] || 0;
  49. keyDuplicateCount[clipPathKey] && (clipPathKey += '-' + keyDuplicateCount[clipPathKey]);
  50. keyDuplicateCount[clipPathKey]++;
  51. }
  52. return this._refGroups[clipPathKey]
  53. || (this._refGroups[clipPathKey] = createElement('g'));
  54. }
  55. /**
  56. * Update clipPath.
  57. *
  58. * @param displayable displayable element
  59. */
  60. update(displayable: Displayable, prevDisplayable: Displayable) {
  61. const clipGroup = this._getClipPathGroup(displayable, prevDisplayable);
  62. if (clipGroup) {
  63. this.markDomUsed(clipGroup);
  64. this.updateDom(clipGroup, displayable.__clipPaths);
  65. }
  66. return clipGroup;
  67. };
  68. /**
  69. * Create an SVGElement of displayable and create a <clipPath> of its
  70. * clipPath
  71. */
  72. updateDom(parentEl: SVGElement, clipPaths: Path[]) {
  73. if (clipPaths && clipPaths.length > 0) {
  74. // Has clipPath, create <clipPath> with the first clipPath
  75. const defs = this.getDefs(true);
  76. const clipPath = clipPaths[0] as PathExtended;
  77. let clipPathEl;
  78. let id;
  79. if (clipPath._dom) {
  80. // Use a dom that is already in <defs>
  81. id = clipPath._dom.getAttribute('id');
  82. clipPathEl = clipPath._dom;
  83. // Use a dom that is already in <defs>
  84. if (!defs.contains(clipPathEl)) {
  85. // This happens when set old clipPath that has
  86. // been previously removed
  87. defs.appendChild(clipPathEl);
  88. }
  89. }
  90. else {
  91. // New <clipPath>
  92. id = 'zr' + this._zrId + '-clip-' + this.nextId;
  93. ++this.nextId;
  94. clipPathEl = createElement('clipPath');
  95. clipPathEl.setAttribute('id', id);
  96. defs.appendChild(clipPathEl);
  97. clipPath._dom = clipPathEl;
  98. }
  99. // Build path and add to <clipPath>
  100. path.brush(clipPath);
  101. const pathEl = this.getSvgElement(clipPath);
  102. clipPathEl.innerHTML = '';
  103. clipPathEl.appendChild(pathEl);
  104. parentEl.setAttribute('clip-path', getIdURL(id));
  105. if (clipPaths.length > 1) {
  106. // Make the other clipPaths recursively
  107. this.updateDom(clipPathEl, clipPaths.slice(1));
  108. }
  109. }
  110. else {
  111. // No clipPath
  112. if (parentEl) {
  113. parentEl.setAttribute('clip-path', 'none');
  114. }
  115. }
  116. };
  117. /**
  118. * Mark a single clipPath to be used
  119. *
  120. * @param displayable displayable element
  121. */
  122. markUsed(displayable: Displayable) {
  123. // displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
  124. if (displayable.__clipPaths) {
  125. zrUtil.each(displayable.__clipPaths, (clipPath: PathExtended) => {
  126. if (clipPath._dom) {
  127. super.markDomUsed(clipPath._dom);
  128. }
  129. });
  130. }
  131. };
  132. removeUnused() {
  133. super.removeUnused();
  134. const newRefGroupsMap: Dictionary<SVGElement> = {};
  135. const refGroups = this._refGroups;
  136. for (let key in refGroups) {
  137. if (refGroups.hasOwnProperty(key)) {
  138. const group = refGroups[key];
  139. if (!this.isDomUnused(group)) {
  140. newRefGroupsMap[key] = group;
  141. }
  142. else if (group.parentNode) {
  143. group.parentNode.removeChild(group);
  144. }
  145. }
  146. }
  147. this._refGroups = newRefGroupsMap;
  148. }
  149. }