ShadowManager.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. var Definable = require("./Definable");
  2. var zrUtil = require("../../core/util");
  3. /**
  4. * @file Manages SVG shadow elements.
  5. * @author Zhang Wenli
  6. */
  7. /**
  8. * Manages SVG shadow elements.
  9. *
  10. * @class
  11. * @extends Definable
  12. * @param {number} zrId zrender instance id
  13. * @param {SVGElement} svgRoot root of SVG document
  14. */
  15. function ShadowManager(zrId, svgRoot) {
  16. Definable.call(this, zrId, svgRoot, ['filter'], '__filter_in_use__', '_shadowDom');
  17. }
  18. zrUtil.inherits(ShadowManager, Definable);
  19. /**
  20. * Create new shadow DOM for fill or stroke if not exist,
  21. * but will not update shadow if exists.
  22. *
  23. * @param {SvgElement} svgElement SVG element to paint
  24. * @param {Displayable} displayable zrender displayable element
  25. */
  26. ShadowManager.prototype.addWithoutUpdate = function (svgElement, displayable) {
  27. if (displayable && hasShadow(displayable.style)) {
  28. // Create dom in <defs> if not exists
  29. var dom;
  30. if (displayable._shadowDom) {
  31. // Gradient exists
  32. dom = displayable._shadowDom;
  33. var defs = this.getDefs(true);
  34. if (!defs.contains(displayable._shadowDom)) {
  35. // _shadowDom is no longer in defs, recreate
  36. this.addDom(dom);
  37. }
  38. } else {
  39. // New dom
  40. dom = this.add(displayable);
  41. }
  42. this.markUsed(displayable);
  43. var id = dom.getAttribute('id');
  44. svgElement.style.filter = 'url(#' + id + ')';
  45. }
  46. };
  47. /**
  48. * Add a new shadow tag in <defs>
  49. *
  50. * @param {Displayable} displayable zrender displayable element
  51. * @return {SVGFilterElement} created DOM
  52. */
  53. ShadowManager.prototype.add = function (displayable) {
  54. var dom = this.createElement('filter'); // Set dom id with shadow id, since each shadow instance
  55. // will have no more than one dom element.
  56. // id may exists before for those dirty elements, in which case
  57. // id should remain the same, and other attributes should be
  58. // updated.
  59. displayable._shadowDomId = displayable._shadowDomId || this.nextId++;
  60. dom.setAttribute('id', 'zr' + this._zrId + '-shadow-' + displayable._shadowDomId);
  61. this.updateDom(displayable, dom);
  62. this.addDom(dom);
  63. return dom;
  64. };
  65. /**
  66. * Update shadow.
  67. *
  68. * @param {Displayable} displayable zrender displayable element
  69. */
  70. ShadowManager.prototype.update = function (svgElement, displayable) {
  71. var style = displayable.style;
  72. if (hasShadow(style)) {
  73. var that = this;
  74. Definable.prototype.update.call(this, displayable, function () {
  75. that.updateDom(displayable, displayable._shadowDom);
  76. });
  77. } else {
  78. // Remove shadow
  79. this.remove(svgElement, displayable);
  80. }
  81. };
  82. /**
  83. * Remove DOM and clear parent filter
  84. */
  85. ShadowManager.prototype.remove = function (svgElement, displayable) {
  86. if (displayable._shadowDomId != null) {
  87. this.removeDom(svgElement);
  88. svgElement.style.filter = '';
  89. }
  90. };
  91. /**
  92. * Update shadow dom
  93. *
  94. * @param {Displayable} displayable zrender displayable element
  95. * @param {SVGFilterElement} dom DOM to update
  96. */
  97. ShadowManager.prototype.updateDom = function (displayable, dom) {
  98. var domChild = dom.getElementsByTagName('feDropShadow');
  99. if (domChild.length === 0) {
  100. domChild = this.createElement('feDropShadow');
  101. } else {
  102. domChild = domChild[0];
  103. }
  104. var style = displayable.style;
  105. var scaleX = displayable.scale ? displayable.scale[0] || 1 : 1;
  106. var scaleY = displayable.scale ? displayable.scale[1] || 1 : 1; // TODO: textBoxShadowBlur is not supported yet
  107. var offsetX;
  108. var offsetY;
  109. var blur;
  110. var color;
  111. if (style.shadowBlur || style.shadowOffsetX || style.shadowOffsetY) {
  112. offsetX = style.shadowOffsetX || 0;
  113. offsetY = style.shadowOffsetY || 0;
  114. blur = style.shadowBlur;
  115. color = style.shadowColor;
  116. } else if (style.textShadowBlur) {
  117. offsetX = style.textShadowOffsetX || 0;
  118. offsetY = style.textShadowOffsetY || 0;
  119. blur = style.textShadowBlur;
  120. color = style.textShadowColor;
  121. } else {
  122. // Remove shadow
  123. this.removeDom(dom, style);
  124. return;
  125. }
  126. domChild.setAttribute('dx', offsetX / scaleX);
  127. domChild.setAttribute('dy', offsetY / scaleY);
  128. domChild.setAttribute('flood-color', color); // Divide by two here so that it looks the same as in canvas
  129. // See: https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowblur
  130. var stdDx = blur / 2 / scaleX;
  131. var stdDy = blur / 2 / scaleY;
  132. var stdDeviation = stdDx + ' ' + stdDy;
  133. domChild.setAttribute('stdDeviation', stdDeviation); // Fix filter clipping problem
  134. dom.setAttribute('x', '-100%');
  135. dom.setAttribute('y', '-100%');
  136. dom.setAttribute('width', Math.ceil(blur / 2 * 200) + '%');
  137. dom.setAttribute('height', Math.ceil(blur / 2 * 200) + '%');
  138. dom.appendChild(domChild); // Store dom element in shadow, to avoid creating multiple
  139. // dom instances for the same shadow element
  140. displayable._shadowDom = dom;
  141. };
  142. /**
  143. * Mark a single shadow to be used
  144. *
  145. * @param {Displayable} displayable displayable element
  146. */
  147. ShadowManager.prototype.markUsed = function (displayable) {
  148. if (displayable._shadowDom) {
  149. Definable.prototype.markUsed.call(this, displayable._shadowDom);
  150. }
  151. };
  152. function hasShadow(style) {
  153. // TODO: textBoxShadowBlur is not supported yet
  154. return style && (style.shadowBlur || style.shadowOffsetX || style.shadowOffsetY || style.textShadowBlur || style.textShadowOffsetX || style.textShadowOffsetY);
  155. }
  156. var _default = ShadowManager;
  157. module.exports = _default;