ShadowManager.js 5.29 KB
var Definable = require("./Definable");

var zrUtil = require("../../core/util");

/**
 * @file Manages SVG shadow elements.
 * @author Zhang Wenli
 */

/**
 * Manages SVG shadow elements.
 *
 * @class
 * @extends Definable
 * @param   {number}     zrId    zrender instance id
 * @param   {SVGElement} svgRoot root of SVG document
 */
function ShadowManager(zrId, svgRoot) {
  Definable.call(this, zrId, svgRoot, ['filter'], '__filter_in_use__', '_shadowDom');
}

zrUtil.inherits(ShadowManager, Definable);
/**
 * Create new shadow DOM for fill or stroke if not exist,
 * but will not update shadow if exists.
 *
 * @param {SvgElement}  svgElement   SVG element to paint
 * @param {Displayable} displayable  zrender displayable element
 */

ShadowManager.prototype.addWithoutUpdate = function (svgElement, displayable) {
  if (displayable && hasShadow(displayable.style)) {
    var style = displayable.style; // Create dom in <defs> if not exists

    var dom;

    if (style._shadowDom) {
      // Gradient exists
      dom = style._shadowDom;
      var defs = this.getDefs(true);

      if (!defs.contains(style._shadowDom)) {
        // _shadowDom is no longer in defs, recreate
        this.addDom(dom);
      }
    } else {
      // New dom
      dom = this.add(displayable);
    }

    this.markUsed(displayable);
    var id = dom.getAttribute('id');
    svgElement.style.filter = 'url(#' + id + ')';
  }
};
/**
 * Add a new shadow tag in <defs>
 *
 * @param {Displayable} displayable  zrender displayable element
 * @return {SVGFilterElement} created DOM
 */


ShadowManager.prototype.add = function (displayable) {
  var dom = this.createElement('filter');
  var style = displayable.style; // Set dom id with shadow id, since each shadow instance
  // will have no more than one dom element.
  // id may exists before for those dirty elements, in which case
  // id should remain the same, and other attributes should be
  // updated.

  style._shadowDomId = style._shadowDomId || this.nextId++;
  dom.setAttribute('id', 'zr' + this._zrId + '-shadow-' + style._shadowDomId);
  this.updateDom(displayable, dom);
  this.addDom(dom);
  return dom;
};
/**
 * Update shadow.
 *
 * @param {Displayable} displayable  zrender displayable element
 */


ShadowManager.prototype.update = function (svgElement, displayable) {
  var style = displayable.style;

  if (hasShadow(style)) {
    var that = this;
    Definable.prototype.update.call(this, displayable, function (style) {
      that.updateDom(displayable, style._shadowDom);
    });
  } else {
    // Remove shadow
    this.remove(svgElement, style);
  }
};
/**
 * Remove DOM and clear parent filter
 */


ShadowManager.prototype.remove = function (svgElement, style) {
  if (style._shadowDomId != null) {
    this.removeDom(style);
    svgElement.style.filter = '';
  }
};
/**
 * Update shadow dom
 *
 * @param {Displayable} displayable  zrender displayable element
 * @param {SVGFilterElement} dom DOM to update
 */


ShadowManager.prototype.updateDom = function (displayable, dom) {
  var domChild = dom.getElementsByTagName('feDropShadow');

  if (domChild.length === 0) {
    domChild = this.createElement('feDropShadow');
  } else {
    domChild = domChild[0];
  }

  var style = displayable.style;
  var scaleX = displayable.scale ? displayable.scale[0] || 1 : 1;
  var scaleY = displayable.scale ? displayable.scale[1] || 1 : 1; // TODO: textBoxShadowBlur is not supported yet

  var offsetX, offsetY, blur, color;

  if (style.shadowBlur || style.shadowOffsetX || style.shadowOffsetY) {
    offsetX = style.shadowOffsetX || 0;
    offsetY = style.shadowOffsetY || 0;
    blur = style.shadowBlur;
    color = style.shadowColor;
  } else if (style.textShadowBlur) {
    offsetX = style.textShadowOffsetX || 0;
    offsetY = style.textShadowOffsetY || 0;
    blur = style.textShadowBlur;
    color = style.textShadowColor;
  } else {
    // Remove shadow
    this.removeDom(dom, style);
    return;
  }

  domChild.setAttribute('dx', offsetX / scaleX);
  domChild.setAttribute('dy', offsetY / scaleY);
  domChild.setAttribute('flood-color', color); // Divide by two here so that it looks the same as in canvas
  // See: https://html.spec.whatwg.org/multipage/canvas.html#dom-context-2d-shadowblur

  var stdDx = blur / 2 / scaleX;
  var stdDy = blur / 2 / scaleY;
  var stdDeviation = stdDx + ' ' + stdDy;
  domChild.setAttribute('stdDeviation', stdDeviation); // Fix filter clipping problem

  dom.setAttribute('x', '-100%');
  dom.setAttribute('y', '-100%');
  dom.setAttribute('width', Math.ceil(blur / 2 * 200) + '%');
  dom.setAttribute('height', Math.ceil(blur / 2 * 200) + '%');
  dom.appendChild(domChild); // Store dom element in shadow, to avoid creating multiple
  // dom instances for the same shadow element

  style._shadowDom = dom;
};
/**
 * Mark a single shadow to be used
 *
 * @param {Displayable} displayable displayable element
 */


ShadowManager.prototype.markUsed = function (displayable) {
  var style = displayable.style;

  if (style && style._shadowDom) {
    Definable.prototype.markUsed.call(this, style._shadowDom);
  }
};

function hasShadow(style) {
  // TODO: textBoxShadowBlur is not supported yet
  return style && (style.shadowBlur || style.shadowOffsetX || style.shadowOffsetY || style.textShadowBlur || style.textShadowOffsetX || style.textShadowOffsetY);
}

var _default = ShadowManager;
module.exports = _default;