187 lines
5.3 KiB
JavaScript
187 lines
5.3 KiB
JavaScript
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)) {
|
|
// Create dom in <defs> if not exists
|
|
var dom;
|
|
|
|
if (displayable._shadowDom) {
|
|
// Gradient exists
|
|
dom = displayable._shadowDom;
|
|
var defs = this.getDefs(true);
|
|
|
|
if (!defs.contains(displayable._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'); // 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.
|
|
|
|
displayable._shadowDomId = displayable._shadowDomId || this.nextId++;
|
|
dom.setAttribute('id', 'zr' + this._zrId + '-shadow-' + displayable._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 () {
|
|
that.updateDom(displayable, displayable._shadowDom);
|
|
});
|
|
} else {
|
|
// Remove shadow
|
|
this.remove(svgElement, displayable);
|
|
}
|
|
};
|
|
/**
|
|
* Remove DOM and clear parent filter
|
|
*/
|
|
|
|
|
|
ShadowManager.prototype.remove = function (svgElement, displayable) {
|
|
if (displayable._shadowDomId != null) {
|
|
this.removeDom(svgElement);
|
|
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;
|
|
var offsetY;
|
|
var blur;
|
|
var 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
|
|
|
|
displayable._shadowDom = dom;
|
|
};
|
|
/**
|
|
* Mark a single shadow to be used
|
|
*
|
|
* @param {Displayable} displayable displayable element
|
|
*/
|
|
|
|
|
|
ShadowManager.prototype.markUsed = function (displayable) {
|
|
if (displayable._shadowDom) {
|
|
Definable.prototype.markUsed.call(this, displayable._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; |