kute.js/src/components/svgDraw.js
thednp 2a5bac2bb3 Changes V2.2.0:
* major JSDoc write up
* removed ESLint `no-bitwise` exception, it only applies to specific functions and not the entire code
* the `SVGCubicMorph` component will remove un-necessary `Z` path commands when is the case for better out of the box animation
* fixed a minor disambiguation with `filterEffects` and `drop-shadow` property and its `dropshadow` interpolation function
* TypeScript strong: all files are modules, easy to implement in any third party app
* updated `CubicBezier` and SVGPathCommander
* code cleanup
2021-12-08 23:43:31 +02:00

211 lines
5.7 KiB
JavaScript

import getStyleForProperty from '../process/getStyleForProperty';
import numbers from '../interpolation/numbers';
import { onStartDraw } from './svgDrawBase';
// Component Util
/**
* Convert a `<path>` length percent value to absolute.
* @param {string} v raw value
* @param {number} l length value
* @returns {number} the absolute value
*/
function percent(v, l) {
return (parseFloat(v) / 100) * l;
}
/**
* Returns the `<rect>` length.
* It doesn't compute `rx` and / or `ry` of the element.
* @see http://stackoverflow.com/a/30376660
* @param {SVGRectElement} el target element
* @returns {number} the `<rect>` length
*/
function getRectLength(el) {
const w = el.getAttribute('width');
const h = el.getAttribute('height');
return (w * 2) + (h * 2);
}
/**
* Returns the `<polyline>` / `<polygon>` length.
* @param {SVGPolylineElement | SVGPolygonElement} el target element
* @returns {number} the element length
*/
function getPolyLength(el) {
const points = el.getAttribute('points').split(' ');
let len = 0;
if (points.length > 1) {
const coord = (p) => {
const c = p.split(',');
if (c.length !== 2) { return 0; } // return undefined
if (Number.isNaN(c[0] * 1) || Number.isNaN(c[1] * 1)) { return 0; }
return [parseFloat(c[0]), parseFloat(c[1])];
};
const dist = (c1, c2) => {
if (c1 !== undefined && c2 !== undefined) {
return Math.sqrt((c2[0] - c1[0]) ** 2 + (c2[1] - c1[1]) ** 2);
}
return 0;
};
if (points.length > 2) {
for (let i = 0; i < points.length - 1; i += 1) {
len += dist(coord(points[i]), coord(points[i + 1]));
}
}
len += el.tagName === 'polygon'
? dist(coord(points[0]), coord(points[points.length - 1])) : 0;
}
return len;
}
/**
* Returns the `<line>` length.
* @param {SVGLineElement} el target element
* @returns {number} the element length
*/
function getLineLength(el) {
const x1 = el.getAttribute('x1');
const x2 = el.getAttribute('x2');
const y1 = el.getAttribute('y1');
const y2 = el.getAttribute('y2');
return Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
}
/**
* Returns the `<circle>` length.
* @param {SVGCircleElement} el target element
* @returns {number} the element length
*/
function getCircleLength(el) {
const r = el.getAttribute('r');
return 2 * Math.PI * r;
}
// returns the length of an ellipse
/**
* Returns the `<ellipse>` length.
* @param {SVGEllipseElement} el target element
* @returns {number} the element length
*/
function getEllipseLength(el) {
const rx = el.getAttribute('rx');
const ry = el.getAttribute('ry');
const len = 2 * rx;
const wid = 2 * ry;
return ((Math.sqrt(0.5 * ((len * len) + (wid * wid)))) * (Math.PI * 2)) / 2;
}
/**
* Returns the shape length.
* @param {SVGPathCommander.shapeTypes} el target element
* @returns {number} the element length
*/
function getTotalLength(el) {
if (el.tagName === 'rect') {
return getRectLength(el);
} if (el.tagName === 'circle') {
return getCircleLength(el);
} if (el.tagName === 'ellipse') {
return getEllipseLength(el);
} if (['polygon', 'polyline'].includes(el.tagName)) {
return getPolyLength(el);
} if (el.tagName === 'line') {
return getLineLength(el);
}
// ESLint
return 0;
}
/**
* Returns the property tween object.
* @param {SVGPathCommander.shapeTypes} element the target element
* @param {string | KUTE.drawObject} value the property value
* @returns {KUTE.drawObject} the property tween object
*/
function getDraw(element, value) {
const length = /path|glyph/.test(element.tagName)
? element.getTotalLength()
: getTotalLength(element);
let start;
let end;
let dasharray;
let offset;
if (value instanceof Object && Object.keys(value).every((v) => ['s', 'e', 'l'].includes(v))) {
return value;
} if (typeof value === 'string') {
const v = value.split(/,|\s/);
start = /%/.test(v[0]) ? percent(v[0].trim(), length) : parseFloat(v[0]);
end = /%/.test(v[1]) ? percent(v[1].trim(), length) : parseFloat(v[1]);
} else if (typeof value === 'undefined') {
offset = parseFloat(getStyleForProperty(element, 'stroke-dashoffset'));
dasharray = getStyleForProperty(element, 'stroke-dasharray').split(',');
start = 0 - offset;
end = parseFloat(dasharray[0]) + start || length;
}
return { s: start, e: end, l: length };
}
/**
* Reset CSS properties associated with the `draw` property.
* @param {SVGPathCommander.shapeTypes} element target
*/
function resetDraw(elem) {
/* eslint-disable no-param-reassign -- impossible to satisfy */
elem.style.strokeDashoffset = '';
elem.style.strokeDasharray = '';
/* eslint-disable no-param-reassign -- impossible to satisfy */
}
// Component Functions
/**
* Returns the property tween object.
* @returns {KUTE.drawObject} the property tween object
*/
function getDrawValue(/* prop, value */) {
return getDraw(this.element);
}
/**
* Returns the property tween object.
* @param {string} _ the property name
* @param {string | KUTE.drawObject} value the property value
* @returns {KUTE.drawObject} the property tween object
*/
function prepareDraw(_, value) {
return getDraw(this.element, value);
}
// All Component Functions
const svgDrawFunctions = {
prepareStart: getDrawValue,
prepareProperty: prepareDraw,
onStart: onStartDraw,
};
// Component Full
const SvgDrawProperty = {
component: 'svgDraw',
property: 'draw',
defaultValue: '0% 0%',
Interpolate: { numbers },
functions: svgDrawFunctions,
// Export to global for faster execution
Util: {
getRectLength,
getPolyLength,
getLineLength,
getCircleLength,
getEllipseLength,
getTotalLength,
resetDraw,
getDraw,
percent,
},
};
export default SvgDrawProperty;