| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- /**
- * @file Manages SVG shadow elements.
- * @author Zhang Wenli
- */
- import Definable from './Definable';
- import Displayable from '../../graphic/Displayable';
- import { Dictionary } from '../../core/types';
- import { getIdURL, getShadowKey, hasShadow, normalizeColor } from '../../svg/helper';
- import { createElement } from '../../svg/core';
- type DisplayableExtended = Displayable & {
- _shadowDom: SVGElement
- }
- /**
- * Manages SVG shadow elements.
- *
- */
- export default class ShadowManager extends Definable {
- private _shadowDomMap: Dictionary<SVGFilterElement> = {}
- private _shadowDomPool: SVGFilterElement[] = []
- constructor(zrId: number, svgRoot: SVGElement) {
- super(zrId, svgRoot, ['filter'], '__filter_in_use__', '_shadowDom');
- }
- /**
- * Add a new shadow tag in <defs>
- *
- * @param displayable zrender displayable element
- * @return created DOM
- */
- private _getFromPool(): SVGFilterElement {
- let shadowDom = this._shadowDomPool.pop(); // Try to get one from trash.
- if (!shadowDom) {
- shadowDom = createElement('filter') as SVGFilterElement;
- shadowDom.setAttribute('id', 'zr' + this._zrId + '-shadow-' + this.nextId++);
- const domChild = createElement('feDropShadow');
- shadowDom.appendChild(domChild);
- this.addDom(shadowDom);
- }
- return shadowDom;
- }
- /**
- * Update shadow.
- */
- update(svgElement: SVGElement, displayable: Displayable) {
- const style = displayable.style;
- if (hasShadow(style)) {
- // Try getting shadow from cache.
- const shadowKey = getShadowKey(displayable);
- let shadowDom = (displayable as DisplayableExtended)._shadowDom = this._shadowDomMap[shadowKey];
- if (!shadowDom) {
- shadowDom = this._getFromPool();
- this._shadowDomMap[shadowKey] = shadowDom;
- }
- this.updateDom(svgElement, displayable, shadowDom);
- }
- else {
- // Remove shadow
- this.remove(svgElement, displayable);
- }
- }
- /**
- * Remove DOM and clear parent filter
- */
- remove(svgElement: SVGElement, displayable: Displayable) {
- if ((displayable as DisplayableExtended)._shadowDom != null) {
- (displayable as DisplayableExtended)._shadowDom = null;
- svgElement.removeAttribute('filter');
- }
- }
- /**
- * Update shadow dom
- *
- * @param displayable zrender displayable element
- * @param shadowDom DOM to update
- */
- updateDom(svgElement: SVGElement, displayable: Displayable, shadowDom: SVGElement) {
- let domChild = shadowDom.children[0];
- const style = displayable.style;
- const globalScale = displayable.getGlobalScale();
- const scaleX = globalScale[0];
- const scaleY = globalScale[1];
- if (!scaleX || !scaleY) {
- return;
- }
- // TODO: textBoxShadowBlur is not supported yet
- const offsetX = style.shadowOffsetX || 0;
- const offsetY = style.shadowOffsetY || 0;
- const blur = style.shadowBlur;
- const normalizedColor = normalizeColor(style.shadowColor);
- domChild.setAttribute('dx', offsetX / scaleX + '');
- domChild.setAttribute('dy', offsetY / scaleY + '');
- domChild.setAttribute('flood-color', normalizedColor.color);
- domChild.setAttribute('flood-opacity', normalizedColor.opacity + '');
- // 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
- const stdDx = blur / 2 / scaleX;
- const stdDy = blur / 2 / scaleY;
- const stdDeviation = stdDx + ' ' + stdDy;
- domChild.setAttribute('stdDeviation', stdDeviation);
- // Fix filter clipping problem
- shadowDom.setAttribute('x', '-100%');
- shadowDom.setAttribute('y', '-100%');
- shadowDom.setAttribute('width', '300%');
- shadowDom.setAttribute('height', '300%');
- // Store dom element in shadow, to avoid creating multiple
- // dom instances for the same shadow element
- (displayable as DisplayableExtended)._shadowDom = shadowDom;
- svgElement.setAttribute('filter', getIdURL(shadowDom.getAttribute('id')));
- }
- removeUnused() {
- const defs = this.getDefs(false);
- if (!defs) {
- // Nothing to remove
- return;
- }
- let shadowDomsPool = this._shadowDomPool;
- // let currentUsedShadow = 0;
- const shadowDomMap = this._shadowDomMap;
- for (let key in shadowDomMap) {
- if (shadowDomMap.hasOwnProperty(key)) {
- shadowDomsPool.push(shadowDomMap[key]);
- }
- // currentUsedShadow++;
- }
- // Reset the map.
- this._shadowDomMap = {};
- }
- }
|