156 lines
4.4 KiB
JavaScript
156 lines
4.4 KiB
JavaScript
|
var Definable = require("./Definable");
|
||
|
|
||
|
var zrUtil = require("../../core/util");
|
||
|
|
||
|
var matrix = require("../../core/matrix");
|
||
|
|
||
|
/**
|
||
|
* @file Manages SVG clipPath elements.
|
||
|
* @author Zhang Wenli
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Manages SVG clipPath elements.
|
||
|
*
|
||
|
* @class
|
||
|
* @extends Definable
|
||
|
* @param {number} zrId zrender instance id
|
||
|
* @param {SVGElement} svgRoot root of SVG document
|
||
|
*/
|
||
|
function ClippathManager(zrId, svgRoot) {
|
||
|
Definable.call(this, zrId, svgRoot, 'clipPath', '__clippath_in_use__');
|
||
|
}
|
||
|
|
||
|
zrUtil.inherits(ClippathManager, Definable);
|
||
|
/**
|
||
|
* Update clipPath.
|
||
|
*
|
||
|
* @param {Displayable} displayable displayable element
|
||
|
*/
|
||
|
|
||
|
ClippathManager.prototype.update = function (displayable) {
|
||
|
var svgEl = this.getSvgElement(displayable);
|
||
|
|
||
|
if (svgEl) {
|
||
|
this.updateDom(svgEl, displayable.__clipPaths, false);
|
||
|
}
|
||
|
|
||
|
var textEl = this.getTextSvgElement(displayable);
|
||
|
|
||
|
if (textEl) {
|
||
|
// Make another clipPath for text, since it's transform
|
||
|
// matrix is not the same with svgElement
|
||
|
this.updateDom(textEl, displayable.__clipPaths, true);
|
||
|
}
|
||
|
|
||
|
this.markUsed(displayable);
|
||
|
};
|
||
|
/**
|
||
|
* Create an SVGElement of displayable and create a <clipPath> of its
|
||
|
* clipPath
|
||
|
*
|
||
|
* @param {Displayable} parentEl parent element
|
||
|
* @param {ClipPath[]} clipPaths clipPaths of parent element
|
||
|
* @param {boolean} isText if parent element is Text
|
||
|
*/
|
||
|
|
||
|
|
||
|
ClippathManager.prototype.updateDom = function (parentEl, clipPaths, isText) {
|
||
|
if (clipPaths && clipPaths.length > 0) {
|
||
|
// Has clipPath, create <clipPath> with the first clipPath
|
||
|
var defs = this.getDefs(true);
|
||
|
var clipPath = clipPaths[0];
|
||
|
var clipPathEl;
|
||
|
var id;
|
||
|
var dom = isText ? '_textDom' : '_dom';
|
||
|
|
||
|
if (clipPath[dom]) {
|
||
|
// Use a dom that is already in <defs>
|
||
|
id = clipPath[dom].getAttribute('id');
|
||
|
clipPathEl = clipPath[dom]; // Use a dom that is already in <defs>
|
||
|
|
||
|
if (!defs.contains(clipPathEl)) {
|
||
|
// This happens when set old clipPath that has
|
||
|
// been previously removed
|
||
|
defs.appendChild(clipPathEl);
|
||
|
}
|
||
|
} else {
|
||
|
// New <clipPath>
|
||
|
id = 'zr' + this._zrId + '-clip-' + this.nextId;
|
||
|
++this.nextId;
|
||
|
clipPathEl = this.createElement('clipPath');
|
||
|
clipPathEl.setAttribute('id', id);
|
||
|
defs.appendChild(clipPathEl);
|
||
|
clipPath[dom] = clipPathEl;
|
||
|
} // Build path and add to <clipPath>
|
||
|
|
||
|
|
||
|
var svgProxy = this.getSvgProxy(clipPath);
|
||
|
|
||
|
if (clipPath.transform && clipPath.parent.invTransform && !isText) {
|
||
|
/**
|
||
|
* If a clipPath has a parent with transform, the transform
|
||
|
* of parent should not be considered when setting transform
|
||
|
* of clipPath. So we need to transform back from parent's
|
||
|
* transform, which is done by multiplying parent's inverse
|
||
|
* transform.
|
||
|
*/
|
||
|
// Store old transform
|
||
|
var transform = Array.prototype.slice.call(clipPath.transform); // Transform back from parent, and brush path
|
||
|
|
||
|
matrix.mul(clipPath.transform, clipPath.parent.invTransform, clipPath.transform);
|
||
|
svgProxy.brush(clipPath); // Set back transform of clipPath
|
||
|
|
||
|
clipPath.transform = transform;
|
||
|
} else {
|
||
|
svgProxy.brush(clipPath);
|
||
|
}
|
||
|
|
||
|
var pathEl = this.getSvgElement(clipPath);
|
||
|
clipPathEl.innerHTML = '';
|
||
|
/**
|
||
|
* Use `cloneNode()` here to appendChild to multiple parents,
|
||
|
* which may happend when Text and other shapes are using the same
|
||
|
* clipPath. Since Text will create an extra clipPath DOM due to
|
||
|
* different transform rules.
|
||
|
*/
|
||
|
|
||
|
clipPathEl.appendChild(pathEl.cloneNode());
|
||
|
parentEl.setAttribute('clip-path', 'url(#' + id + ')');
|
||
|
|
||
|
if (clipPaths.length > 1) {
|
||
|
// Make the other clipPaths recursively
|
||
|
this.updateDom(clipPathEl, clipPaths.slice(1), isText);
|
||
|
}
|
||
|
} else {
|
||
|
// No clipPath
|
||
|
if (parentEl) {
|
||
|
parentEl.setAttribute('clip-path', 'none');
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
/**
|
||
|
* Mark a single clipPath to be used
|
||
|
*
|
||
|
* @param {Displayable} displayable displayable element
|
||
|
*/
|
||
|
|
||
|
|
||
|
ClippathManager.prototype.markUsed = function (displayable) {
|
||
|
var that = this; // displayable.__clipPaths can only be `null`/`undefined` or an non-empty array.
|
||
|
|
||
|
if (displayable.__clipPaths) {
|
||
|
zrUtil.each(displayable.__clipPaths, function (clipPath) {
|
||
|
if (clipPath._dom) {
|
||
|
Definable.prototype.markUsed.call(that, clipPath._dom);
|
||
|
}
|
||
|
|
||
|
if (clipPath._textDom) {
|
||
|
Definable.prototype.markUsed.call(that, clipPath._textDom);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var _default = ClippathManager;
|
||
|
module.exports = _default;
|