UNPKG

lottie-web

Version:

After Effects plugin for exporting animations to SVG + JavaScript or canvas + JavaScript

262 lines (237 loc) 9.12 kB
import { bmPow, bmMax, bmMin, bmSqrt, } from '../../utils/common'; import { extendPrototype, } from '../../utils/functionExtensions'; import createNS from '../../utils/helpers/svg_elements'; import RenderableElement from '../helpers/RenderableElement'; import BaseElement from '../BaseElement'; import TransformElement from '../helpers/TransformElement'; import HierarchyElement from '../helpers/HierarchyElement'; import FrameElement from '../helpers/FrameElement'; import HBaseElement from './HBaseElement'; import HSolidElement from './HSolidElement'; import SVGShapeElement from '../svgElements/SVGShapeElement'; function HShapeElement(data, globalData, comp) { // List of drawable elements this.shapes = []; // Full shape data this.shapesData = data.shapes; // List of styles that will be applied to shapes this.stylesList = []; // List of modifiers that will be applied to shapes this.shapeModifiers = []; // List of items in shape tree this.itemsData = []; // List of items in previous shape tree this.processedElements = []; // List of animated components this.animatedContents = []; this.shapesContainer = createNS('g'); this.initElement(data, globalData, comp); // Moving any property that doesn't get too much access after initialization because of v8 way of handling more than 10 properties. // List of elements that have been created this.prevViewData = []; this.currentBBox = { x: 999999, y: -999999, h: 0, w: 0, }; } extendPrototype([BaseElement, TransformElement, HSolidElement, SVGShapeElement, HBaseElement, HierarchyElement, FrameElement, RenderableElement], HShapeElement); HShapeElement.prototype._renderShapeFrame = HShapeElement.prototype.renderInnerContent; HShapeElement.prototype.createContent = function () { var cont; this.baseElement.style.fontSize = 0; if (this.data.hasMask) { this.layerElement.appendChild(this.shapesContainer); cont = this.svgElement; } else { cont = createNS('svg'); var size = this.comp.data ? this.comp.data : this.globalData.compSize; cont.setAttribute('width', size.w); cont.setAttribute('height', size.h); cont.appendChild(this.shapesContainer); this.layerElement.appendChild(cont); } this.searchShapes(this.shapesData, this.itemsData, this.prevViewData, this.shapesContainer, 0, [], true); this.filterUniqueShapes(); this.shapeCont = cont; }; HShapeElement.prototype.getTransformedPoint = function (transformers, point) { var i; var len = transformers.length; for (i = 0; i < len; i += 1) { point = transformers[i].mProps.v.applyToPointArray(point[0], point[1], 0); } return point; }; HShapeElement.prototype.calculateShapeBoundingBox = function (item, boundingBox) { var shape = item.sh.v; var transformers = item.transformers; var i; var len = shape._length; var vPoint; var oPoint; var nextIPoint; var nextVPoint; if (len <= 1) { return; } for (i = 0; i < len - 1; i += 1) { vPoint = this.getTransformedPoint(transformers, shape.v[i]); oPoint = this.getTransformedPoint(transformers, shape.o[i]); nextIPoint = this.getTransformedPoint(transformers, shape.i[i + 1]); nextVPoint = this.getTransformedPoint(transformers, shape.v[i + 1]); this.checkBounds(vPoint, oPoint, nextIPoint, nextVPoint, boundingBox); } if (shape.c) { vPoint = this.getTransformedPoint(transformers, shape.v[i]); oPoint = this.getTransformedPoint(transformers, shape.o[i]); nextIPoint = this.getTransformedPoint(transformers, shape.i[0]); nextVPoint = this.getTransformedPoint(transformers, shape.v[0]); this.checkBounds(vPoint, oPoint, nextIPoint, nextVPoint, boundingBox); } }; HShapeElement.prototype.checkBounds = function (vPoint, oPoint, nextIPoint, nextVPoint, boundingBox) { this.getBoundsOfCurve(vPoint, oPoint, nextIPoint, nextVPoint); var bounds = this.shapeBoundingBox; boundingBox.x = bmMin(bounds.left, boundingBox.x); boundingBox.xMax = bmMax(bounds.right, boundingBox.xMax); boundingBox.y = bmMin(bounds.top, boundingBox.y); boundingBox.yMax = bmMax(bounds.bottom, boundingBox.yMax); }; HShapeElement.prototype.shapeBoundingBox = { left: 0, right: 0, top: 0, bottom: 0, }; HShapeElement.prototype.tempBoundingBox = { x: 0, xMax: 0, y: 0, yMax: 0, width: 0, height: 0, }; HShapeElement.prototype.getBoundsOfCurve = function (p0, p1, p2, p3) { var bounds = [[p0[0], p3[0]], [p0[1], p3[1]]]; for (var a, b, c, t, b2ac, t1, t2, i = 0; i < 2; ++i) { // eslint-disable-line no-plusplus b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; c = 3 * p1[i] - 3 * p0[i]; b |= 0; // eslint-disable-line no-bitwise a |= 0; // eslint-disable-line no-bitwise c |= 0; // eslint-disable-line no-bitwise if (a === 0 && b === 0) { // } else if (a === 0) { t = -c / b; if (t > 0 && t < 1) { bounds[i].push(this.calculateF(t, p0, p1, p2, p3, i)); } } else { b2ac = b * b - 4 * c * a; if (b2ac >= 0) { t1 = (-b + bmSqrt(b2ac)) / (2 * a); if (t1 > 0 && t1 < 1) bounds[i].push(this.calculateF(t1, p0, p1, p2, p3, i)); t2 = (-b - bmSqrt(b2ac)) / (2 * a); if (t2 > 0 && t2 < 1) bounds[i].push(this.calculateF(t2, p0, p1, p2, p3, i)); } } } this.shapeBoundingBox.left = bmMin.apply(null, bounds[0]); this.shapeBoundingBox.top = bmMin.apply(null, bounds[1]); this.shapeBoundingBox.right = bmMax.apply(null, bounds[0]); this.shapeBoundingBox.bottom = bmMax.apply(null, bounds[1]); }; HShapeElement.prototype.calculateF = function (t, p0, p1, p2, p3, i) { return bmPow(1 - t, 3) * p0[i] + 3 * bmPow(1 - t, 2) * t * p1[i] + 3 * (1 - t) * bmPow(t, 2) * p2[i] + bmPow(t, 3) * p3[i]; }; HShapeElement.prototype.calculateBoundingBox = function (itemsData, boundingBox) { var i; var len = itemsData.length; for (i = 0; i < len; i += 1) { if (itemsData[i] && itemsData[i].sh) { this.calculateShapeBoundingBox(itemsData[i], boundingBox); } else if (itemsData[i] && itemsData[i].it) { this.calculateBoundingBox(itemsData[i].it, boundingBox); } else if (itemsData[i] && itemsData[i].style && itemsData[i].w) { this.expandStrokeBoundingBox(itemsData[i].w, boundingBox); } } }; HShapeElement.prototype.expandStrokeBoundingBox = function (widthProperty, boundingBox) { var width = 0; if (widthProperty.keyframes) { for (var i = 0; i < widthProperty.keyframes.length; i += 1) { var kfw = widthProperty.keyframes[i].s; if (kfw > width) { width = kfw; } } width *= widthProperty.mult; } else { width = widthProperty.v * widthProperty.mult; } boundingBox.x -= width; boundingBox.xMax += width; boundingBox.y -= width; boundingBox.yMax += width; }; HShapeElement.prototype.currentBoxContains = function (box) { return this.currentBBox.x <= box.x && this.currentBBox.y <= box.y && this.currentBBox.width + this.currentBBox.x >= box.x + box.width && this.currentBBox.height + this.currentBBox.y >= box.y + box.height; }; HShapeElement.prototype.renderInnerContent = function () { this._renderShapeFrame(); if (!this.hidden && (this._isFirstFrame || this._mdf)) { var tempBoundingBox = this.tempBoundingBox; var max = 999999; tempBoundingBox.x = max; tempBoundingBox.xMax = -max; tempBoundingBox.y = max; tempBoundingBox.yMax = -max; this.calculateBoundingBox(this.itemsData, tempBoundingBox); tempBoundingBox.width = tempBoundingBox.xMax < tempBoundingBox.x ? 0 : tempBoundingBox.xMax - tempBoundingBox.x; tempBoundingBox.height = tempBoundingBox.yMax < tempBoundingBox.y ? 0 : tempBoundingBox.yMax - tempBoundingBox.y; // var tempBoundingBox = this.shapeCont.getBBox(); if (this.currentBoxContains(tempBoundingBox)) { return; } var changed = false; if (this.currentBBox.w !== tempBoundingBox.width) { this.currentBBox.w = tempBoundingBox.width; this.shapeCont.setAttribute('width', tempBoundingBox.width); changed = true; } if (this.currentBBox.h !== tempBoundingBox.height) { this.currentBBox.h = tempBoundingBox.height; this.shapeCont.setAttribute('height', tempBoundingBox.height); changed = true; } if (changed || this.currentBBox.x !== tempBoundingBox.x || this.currentBBox.y !== tempBoundingBox.y) { this.currentBBox.w = tempBoundingBox.width; this.currentBBox.h = tempBoundingBox.height; this.currentBBox.x = tempBoundingBox.x; this.currentBBox.y = tempBoundingBox.y; this.shapeCont.setAttribute('viewBox', this.currentBBox.x + ' ' + this.currentBBox.y + ' ' + this.currentBBox.w + ' ' + this.currentBBox.h); var shapeStyle = this.shapeCont.style; var shapeTransform = 'translate(' + this.currentBBox.x + 'px,' + this.currentBBox.y + 'px)'; shapeStyle.transform = shapeTransform; shapeStyle.webkitTransform = shapeTransform; } } }; export default HShapeElement;