kute.js/src/components/svgCubicMorph.js
thednp 933d61de19 Changes:
* added ESLint and updated all code base 
* updated SVGPathCommander, CubicBezier, shorter-js, minifill
* updated polyfills
* minor CSS fixes
2021-03-30 09:23:29 +00:00

191 lines
5.9 KiB
JavaScript

import parsePathString from 'svg-path-commander/src/process/parsePathString.js';
import pathToAbsolute from 'svg-path-commander/src/convert/pathToAbsolute.js';
import pathToCurve from 'svg-path-commander/src/convert/pathToCurve.js';
import pathToString from 'svg-path-commander/src/convert/pathToString.js';
import reverseCurve from 'svg-path-commander/src/process/reverseCurve.js';
import getDrawDirection from 'svg-path-commander/src/util/getDrawDirection.js';
import clonePath from 'svg-path-commander/src/process/clonePath.js';
import splitCubic from 'svg-path-commander/src/process/splitCubic.js';
import splitPath from 'svg-path-commander/src/process/splitPath.js';
import getSegCubicLength from 'svg-path-commander/src/util/getSegCubicLength.js';
import distanceSquareRoot from 'svg-path-commander/src/math/distanceSquareRoot.js';
import { onStartCubicMorph } from './svgCubicMorphBase.js';
import numbers from '../interpolation/numbers.js';
import selector from '../util/selector.js';
/* SVGMorph = {
property: 'path',
defaultValue: [],
interpolators: {numbers},
functions = { prepareStart, prepareProperty, onStart, crossCheck }
} */
// Component Util
function getCurveArray(pathString) {
return pathToCurve(splitPath(pathToString(pathToAbsolute(pathString)))[0])
.map((segment, i, pathArray) => {
const segmentData = i && pathArray[i - 1].slice(-2).concat(segment.slice(1));
const curveLength = i ? getSegCubicLength.apply(0, segmentData) : 0;
let subsegs;
if (i) {
// must be [segment,segment]
subsegs = curveLength ? splitCubic(segmentData) : [segment, segment];
} else {
subsegs = [segment];
}
return {
s: segment,
ss: subsegs,
l: curveLength,
};
});
}
function equalizeSegments(path1, path2, TL) {
const c1 = getCurveArray(path1);
const c2 = getCurveArray(path2);
const L1 = c1.length;
const L2 = c2.length;
const l1 = c1.filter((x) => x.l).length;
const l2 = c2.filter((x) => x.l).length;
const m1 = c1.filter((x) => x.l).reduce((a, { l }) => a + l, 0) / l1 || 0;
const m2 = c2.filter((x) => x.l).reduce((a, { l }) => a + l, 0) / l2 || 0;
const tl = TL || Math.max(L1, L2);
const mm = [m1, m2];
const dif = [tl - L1, tl - L2];
let canSplit = 0;
const result = [c1, c2]
.map((x, i) => (x.l === tl
? x.map((y) => y.s)
: x.map((y, j) => {
canSplit = j && dif[i] && y.l >= mm[i];
dif[i] -= canSplit ? 1 : 0;
return canSplit ? y.ss : [y.s];
}).flat()));
return result[0].length === result[1].length
? result
: equalizeSegments(result[0], result[1], tl);
}
function getRotations(a) {
const segCount = a.length;
const pointCount = segCount - 1;
return a.map((f, idx) => a.map((p, i) => {
let oldSegIdx = idx + i;
let seg;
if (i === 0 || (a[oldSegIdx] && a[oldSegIdx][0] === 'M')) {
seg = a[oldSegIdx];
return ['M'].concat(seg.slice(-2));
}
if (oldSegIdx >= segCount) oldSegIdx -= pointCount;
return a[oldSegIdx];
}));
}
function getRotatedCurve(a, b) {
const segCount = a.length - 1;
const lineLengths = [];
let computedIndex = 0;
let sumLensSqrd = 0;
const rotations = getRotations(a);
rotations.forEach((r, i) => {
a.slice(1).forEach((s, j) => {
sumLensSqrd += distanceSquareRoot(a[(i + j) % segCount].slice(-2), b[j % segCount].slice(-2));
});
lineLengths[i] = sumLensSqrd;
sumLensSqrd = 0;
});
computedIndex = lineLengths.indexOf(Math.min.apply(null, lineLengths));
return rotations[computedIndex];
}
// Component Functions
function getCubicMorph(/* tweenProp, value */) {
return this.element.getAttribute('d');
}
function prepareCubicMorph(tweenProp, value) {
// get path d attribute or create a path from string value
const pathObject = {};
// remove newlines, they break some JSON strings
const pathReg = new RegExp('\\n', 'ig');
let el = null;
if (value instanceof SVGElement) {
el = value;
} else if (/^\.|^#/.test(value)) {
el = selector(value);
}
// make sure to return pre-processed values
if (typeof (value) === 'object' && value.curve) {
return value;
} if (el && /path|glyph/.test(el.tagName)) {
pathObject.original = el.getAttribute('d').replace(pathReg, '');
// maybe it's a string path already
} else if (!el && typeof (value) === 'string') {
pathObject.original = value.replace(pathReg, '');
}
return pathObject;
}
function crossCheckCubicMorph(tweenProp) {
if (this.valuesEnd[tweenProp]) {
const pathCurve1 = this.valuesStart[tweenProp].curve;
const pathCurve2 = this.valuesEnd[tweenProp].curve;
if (!pathCurve1 || !pathCurve2
|| (pathCurve1 && pathCurve2 && pathCurve1[0][0] === 'M' && pathCurve1.length !== pathCurve2.length)) {
const path1 = this.valuesStart[tweenProp].original;
const path2 = this.valuesEnd[tweenProp].original;
const curves = equalizeSegments(path1, path2);
const curve0 = getDrawDirection(curves[0]) !== getDrawDirection(curves[1])
? reverseCurve(curves[0])
: clonePath(curves[0]);
this.valuesStart[tweenProp].curve = curve0;
this.valuesEnd[tweenProp].curve = getRotatedCurve(curves[1], curve0);
}
}
}
// All Component Functions
const svgCubicMorphFunctions = {
prepareStart: getCubicMorph,
prepareProperty: prepareCubicMorph,
onStart: onStartCubicMorph,
crossCheck: crossCheckCubicMorph,
};
// Component Full
const svgCubicMorph = {
component: 'svgCubicMorph',
property: 'path',
defaultValue: [],
Interpolate: { numbers, pathToString },
functions: svgCubicMorphFunctions,
// export utils to global for faster execution
Util: {
pathToCurve,
pathToAbsolute,
pathToString,
parsePathString,
getRotatedCurve,
getRotations,
equalizeSegments,
reverseCurve,
clonePath,
getDrawDirection,
splitCubic,
getCurveArray,
},
};
export default svgCubicMorph;