bf9c4d8493efdc7fe056871716dca670727cc3961fd96c3d322eac5783913d9774b66ac91883a0bd49ce0cc74e3f7769a21f5352cd5392417caa929de106c1 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. /**
  2. * @file Manages SVG shadow elements.
  3. * @author Zhang Wenli
  4. */
  5. import Definable from './Definable';
  6. import Displayable from '../../graphic/Displayable';
  7. import { Dictionary } from '../../core/types';
  8. import { getIdURL, getShadowKey, hasShadow, normalizeColor } from '../../svg/helper';
  9. import { createElement } from '../../svg/core';
  10. type DisplayableExtended = Displayable & {
  11. _shadowDom: SVGElement
  12. }
  13. /**
  14. * Manages SVG shadow elements.
  15. *
  16. */
  17. export default class ShadowManager extends Definable {
  18. private _shadowDomMap: Dictionary<SVGFilterElement> = {}
  19. private _shadowDomPool: SVGFilterElement[] = []
  20. constructor(zrId: number, svgRoot: SVGElement) {
  21. super(zrId, svgRoot, ['filter'], '__filter_in_use__', '_shadowDom');
  22. }
  23. /**
  24. * Add a new shadow tag in <defs>
  25. *
  26. * @param displayable zrender displayable element
  27. * @return created DOM
  28. */
  29. private _getFromPool(): SVGFilterElement {
  30. let shadowDom = this._shadowDomPool.pop(); // Try to get one from trash.
  31. if (!shadowDom) {
  32. shadowDom = createElement('filter') as SVGFilterElement;
  33. shadowDom.setAttribute('id', 'zr' + this._zrId + '-shadow-' + this.nextId++);
  34. const domChild = createElement('feDropShadow');
  35. shadowDom.appendChild(domChild);
  36. this.addDom(shadowDom);
  37. }
  38. return shadowDom;
  39. }
  40. /**
  41. * Update shadow.
  42. */
  43. update(svgElement: SVGElement, displayable: Displayable) {
  44. const style = displayable.style;
  45. if (hasShadow(style)) {
  46. // Try getting shadow from cache.
  47. const shadowKey = getShadowKey(displayable);
  48. let shadowDom = (displayable as DisplayableExtended)._shadowDom = this._shadowDomMap[shadowKey];
  49. if (!shadowDom) {
  50. shadowDom = this._getFromPool();
  51. this._shadowDomMap[shadowKey] = shadowDom;
  52. }
  53. this.updateDom(svgElement, displayable, shadowDom);
  54. }
  55. else {
  56. // Remove shadow
  57. this.remove(svgElement, displayable);
  58. }
  59. }
  60. /**
  61. * Remove DOM and clear parent filter
  62. */
  63. remove(svgElement: SVGElement, displayable: Displayable) {
  64. if ((displayable as DisplayableExtended)._shadowDom != null) {
  65. (displayable as DisplayableExtended)._shadowDom = null;
  66. svgElement.removeAttribute('filter');
  67. }
  68. }
  69. /**
  70. * Update shadow dom
  71. *
  72. * @param displayable zrender displayable element
  73. * @param shadowDom DOM to update
  74. */
  75. updateDom(svgElement: SVGElement, displayable: Displayable, shadowDom: SVGElement) {
  76. let domChild = shadowDom.children[0];
  77. const style = displayable.style;
  78. const globalScale = displayable.getGlobalScale();
  79. const scaleX = globalScale[0];
  80. const scaleY = globalScale[1];
  81. if (!scaleX || !scaleY) {
  82. return;
  83. }
  84. // TODO: textBoxShadowBlur is not supported yet
  85. const offsetX = style.shadowOffsetX || 0;
  86. const offsetY = style.shadowOffsetY || 0;
  87. const blur = style.shadowBlur;
  88. const normalizedColor = normalizeColor(style.shadowColor);
  89. domChild.setAttribute('dx', offsetX / scaleX + '');
  90. domChild.setAttribute('dy', offsetY / scaleY + '');
  91. domChild.setAttribute('flood-color', normalizedColor.color);
  92. domChild.setAttribute('flood-opacity', normalizedColor.opacity + '');
  93. // Divide by two here so that it looks the same as in canvas
  94. // See: https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowblur
  95. const stdDx = blur / 2 / scaleX;
  96. const stdDy = blur / 2 / scaleY;
  97. const stdDeviation = stdDx + ' ' + stdDy;
  98. domChild.setAttribute('stdDeviation', stdDeviation);
  99. // Fix filter clipping problem
  100. shadowDom.setAttribute('x', '-100%');
  101. shadowDom.setAttribute('y', '-100%');
  102. shadowDom.setAttribute('width', '300%');
  103. shadowDom.setAttribute('height', '300%');
  104. // Store dom element in shadow, to avoid creating multiple
  105. // dom instances for the same shadow element
  106. (displayable as DisplayableExtended)._shadowDom = shadowDom;
  107. svgElement.setAttribute('filter', getIdURL(shadowDom.getAttribute('id')));
  108. }
  109. removeUnused() {
  110. const defs = this.getDefs(false);
  111. if (!defs) {
  112. // Nothing to remove
  113. return;
  114. }
  115. let shadowDomsPool = this._shadowDomPool;
  116. // let currentUsedShadow = 0;
  117. const shadowDomMap = this._shadowDomMap;
  118. for (let key in shadowDomMap) {
  119. if (shadowDomMap.hasOwnProperty(key)) {
  120. shadowDomsPool.push(shadowDomMap[key]);
  121. }
  122. // currentUsedShadow++;
  123. }
  124. // Reset the map.
  125. this._shadowDomMap = {};
  126. }
  127. }