aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
435 lines (432 loc) • 13.7 kB
JavaScript
import { InterpolationUtils } from './interpolation.js';
const easingFunctions = {
linear: t => t,
easeInQuad: t => t * t,
easeOutQuad: t => t * (2 - t),
easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
easeInCubic: t => t * t * t,
easeOutCubic: t => --t * t * t + 1,
easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
easeInQuart: t => t * t * t * t,
easeOutQuart: t => 1 - --t * t * t * t,
easeInOutQuart: t => t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t,
easeInSine: t => 1 - Math.cos(t * Math.PI / 2),
easeOutSine: t => Math.sin(t * Math.PI / 2),
easeInOutSine: t => -(Math.cos(Math.PI * t) - 1) / 2,
easeInExpo: t => t === 0 ? 0 : Math.pow(2, 10 * t - 10),
easeOutExpo: t => t === 1 ? 1 : 1 - Math.pow(2, -10 * t),
easeInOutExpo: t => {
if (t === 0) return 0;
if (t === 1) return 1;
return t < 0.5 ? Math.pow(2, 20 * t - 10) / 2 : (2 - Math.pow(2, -20 * t + 10)) / 2;
},
easeInBack: t => {
const c1 = 1.70158;
const c3 = c1 + 1;
return c3 * t * t * t - c1 * t * t;
},
easeOutBack: t => {
const c1 = 1.70158;
const c3 = c1 + 1;
return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2);
},
easeInOutBack: t => {
const c1 = 1.70158;
const c2 = c1 * 1.525;
return t < 0.5 ? Math.pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2) / 2 : (Math.pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2;
},
easeOutElastic: t => {
const c4 = 2 * Math.PI / 3;
return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10 * t) * Math.sin((t * 10 - 0.75) * c4) + 1;
}
};
class ChartAnimationUtils {
/**
* Animate chart data points with various effects
*/
static animateDataPoints(dataPoints, config, onUpdate, onComplete) {
const {
duration = 1000,
stagger = 0,
direction = 'up',
easing = 'easeOutQuad',
delay = 0
} = config;
const startTime = Date.now() + delay;
const easeFn = easingFunctions?.[easing];
const animatedPoints = [...dataPoints];
const animate = () => {
const elapsed = Date.now() - startTime;
const rawProgress = Math.min(elapsed / duration, 1);
const progress = easeFn(rawProgress);
// Apply direction-based animation
dataPoints.forEach((point, index) => {
const staggerDelay = index * stagger;
const pointProgress = Math.max(0, Math.min(1, (elapsed - staggerDelay) / duration));
const easedPointProgress = easeFn(pointProgress);
const animatedPoint = this.applyDirectionalAnimation(point, easedPointProgress, direction);
if (animatedPoints) {
animatedPoints[index] = animatedPoint;
}
});
onUpdate?.(animatedPoints, progress);
if (rawProgress < 1) {
requestAnimationFrame(animate);
} else {
onComplete?.(animatedPoints);
}
};
const timeoutId = setTimeout(() => {
requestAnimationFrame(animate);
}, delay);
return () => clearTimeout(timeoutId);
}
/**
* Animate entire chart series
*/
static animateSeries(series, config, onUpdate, onComplete) {
const {
duration = 1000,
stagger = 100,
direction = 'up',
easing = 'easeOutQuad',
delay = 0
} = config;
const startTime = Date.now() + delay;
const easeFn = easingFunctions?.[easing];
const animatedSeries = series.map(s => ({
...s,
data: [...s.data]
}));
const animate = () => {
const elapsed = Date.now() - startTime;
const rawProgress = Math.min(elapsed / duration, 1);
const progress = easeFn(rawProgress);
series.forEach((seriesItem, seriesIndex) => {
const seriesDelay = seriesIndex * stagger;
const seriesProgress = Math.max(0, Math.min(1, (elapsed - seriesDelay) / duration));
const easedSeriesProgress = easeFn(seriesProgress);
seriesItem.data?.forEach((point, pointIndex) => {
const animatedPoint = this.applyDirectionalAnimation(point, easedSeriesProgress, direction);
if (animatedSeries[seriesIndex].data) {
animatedSeries[seriesIndex].data[pointIndex] = animatedPoint;
}
});
});
onUpdate?.(animatedSeries, progress);
if (rawProgress < 1) {
requestAnimationFrame(animate);
} else {
onComplete?.(animatedSeries);
}
};
const timeoutId = setTimeout(() => {
requestAnimationFrame(animate);
}, delay);
return () => clearTimeout(timeoutId);
}
/**
* Create morphing animation between two data sets
*/
static createMorphingAnimation(fromSeries, toSeries, config, onUpdate, onComplete) {
const {
duration = 1000,
easing = 'easeInOutQuad',
delay = 0
} = config;
const startTime = Date.now() + delay;
const easeFn = easingFunctions?.[easing];
const morphedSeries = toSeries.map(s => ({
...s,
data: [...s.data]
}));
const animate = () => {
const elapsed = Date.now() - startTime;
const rawProgress = Math.min(elapsed / duration, 1);
const progress = easeFn(rawProgress);
toSeries.forEach((toSeriesItem, seriesIndex) => {
const fromSeriesItem = fromSeries?.[seriesIndex];
if (!fromSeriesItem) return;
toSeriesItem.data?.forEach((toPoint, pointIndex) => {
const fromPoint = fromSeriesItem.data?.[pointIndex];
if (!fromPoint) return;
const morphedPoint = {
...toPoint,
x: InterpolationUtils.lerp(fromPoint.x, toPoint.x, progress),
y: InterpolationUtils.lerp(fromPoint.y, toPoint.y, progress)
};
if (morphedSeries[seriesIndex].data) {
morphedSeries[seriesIndex].data[pointIndex] = morphedPoint;
}
});
});
onUpdate?.(morphedSeries, progress);
if (rawProgress < 1) {
requestAnimationFrame(animate);
} else {
onComplete?.(morphedSeries);
}
};
const timeoutId = setTimeout(() => {
requestAnimationFrame(animate);
}, delay);
return () => clearTimeout(timeoutId);
}
/**
* Create entrance animation for chart elements
*/
static createEntranceAnimation(series, effect, config, onUpdate, onComplete) {
const {
duration = 1000,
stagger = 100,
direction = 'up',
delay = 0
} = config;
const effects = {
fadeIn: {
opacity: 0
},
slideIn: this.getDirectionalTransform(direction, 50),
scaleIn: {
scale: 0
},
bounceIn: {
scale: 0.3
},
elasticIn: {
scale: 0,
rotation: -180
}
};
const effectConfig = effects?.[effect];
const animatedSeries = series.map(s => ({
...s,
data: [...s.data]
}));
return this.animateSeries(series, {
...config,
duration,
stagger,
delay
}, (updatedSeries, progress) => {
updatedSeries.forEach((seriesItem, seriesIndex) => {
seriesItem.data?.forEach((point, pointIndex) => {
if (animatedSeries[seriesIndex].data) {
animatedSeries[seriesIndex].data[pointIndex] = {
...point,
...this.interpolatePointProperties(effectConfig, point, progress)
};
}
});
});
onUpdate?.(animatedSeries, progress);
}, onComplete);
}
/**
* Create exit animation for chart elements
*/
static createExitAnimation(series, effect, config, onUpdate, onComplete) {
const {
duration = 500,
stagger = 50,
direction = 'down',
delay = 0
} = config;
const effects = {
fadeOut: {
opacity: 1
},
slideOut: this.getDirectionalTransform(direction, 0),
scaleOut: {
scale: 1
},
bounceOut: {
scale: 1
}
};
const effectConfig = effects?.[effect];
const animatedSeries = series.map(s => ({
...s,
data: [...s.data]
}));
return this.animateSeries(series, {
...config,
duration,
stagger,
delay
}, (updatedSeries, progress) => {
const exitProgress = 1 - progress;
updatedSeries.forEach((seriesItem, seriesIndex) => {
seriesItem.data?.forEach((point, pointIndex) => {
if (animatedSeries[seriesIndex].data) {
animatedSeries[seriesIndex].data[pointIndex] = {
...point,
...this.interpolatePointProperties(effectConfig, point, exitProgress)
};
}
});
});
onUpdate?.(animatedSeries, progress);
}, onComplete);
}
static applyDirectionalAnimation(point, progress, direction) {
const transform = this.getDirectionalTransform(direction, 1 - progress);
return {
...point,
...this.interpolatePointProperties(transform, point, progress)
};
}
static getDirectionalTransform(direction, distance) {
switch (direction) {
case 'up':
return {
y: distance * 50
};
case 'down':
return {
y: -distance * 50
};
case 'left':
return {
x: distance * 50
};
case 'right':
return {
x: -distance * 50
};
case 'center':
return {
scale: distance
};
default:
return {
y: distance * 50
};
}
}
static interpolatePointProperties(transform, point, progress) {
const result = {};
if (transform.x !== undefined) {
result.x = point.x + transform.x * (1 - progress);
}
if (transform.y !== undefined) {
result.y = point.y + transform.y * (1 - progress);
}
if (transform.scale !== undefined) {
// Scale would typically be handled by CSS transform
result.scale = InterpolationUtils.lerp(transform.scale, 1, progress);
}
if (transform.opacity !== undefined) {
result.opacity = InterpolationUtils.lerp(transform.opacity, 1, progress);
}
if (transform.rotation !== undefined) {
result.rotation = InterpolationUtils.lerp(transform.rotation, 0, progress);
}
return result;
}
/**
* Create a smooth transition between chart states
*/
static createChartTransition(transition, onUpdate, onComplete) {
const {
from,
to,
duration,
easing,
stagger
} = transition;
const easeFn = easingFunctions?.[easing];
const startTime = Date.now();
const animatedSeries = to.map(s => ({
...s,
data: [...s.data]
}));
const animate = () => {
const elapsed = Date.now() - startTime;
const rawProgress = Math.min(elapsed / duration, 1);
const progress = easeFn(rawProgress);
to.forEach((toSeriesItem, seriesIndex) => {
const fromSeriesItem = from?.[seriesIndex];
if (!fromSeriesItem) return;
const seriesDelay = seriesIndex * stagger;
const seriesProgress = Math.max(0, Math.min(1, (elapsed - seriesDelay) / duration));
const easedSeriesProgress = easeFn(seriesProgress);
toSeriesItem.data?.forEach((toPoint, pointIndex) => {
const fromPoint = fromSeriesItem.data?.[pointIndex];
if (!fromPoint) return;
if (animatedSeries[seriesIndex].data) {
animatedSeries[seriesIndex].data[pointIndex] = {
...toPoint,
x: InterpolationUtils.lerp(fromPoint.x, toPoint.x, easedSeriesProgress),
y: InterpolationUtils.lerp(fromPoint.y, toPoint.y, easedSeriesProgress)
};
}
});
});
onUpdate?.(animatedSeries, progress);
if (rawProgress < 1) {
requestAnimationFrame(animate);
} else {
onComplete?.(animatedSeries);
}
};
const animationId = requestAnimationFrame(animate);
return () => cancelAnimationFrame(animationId);
}
}
// Predefined animation presets
const chartAnimationPresets = {
gentleFadeIn: {
duration: 800,
easing: 'easeOutQuad',
stagger: 50,
direction: 'up'
},
dramaticEntrance: {
duration: 1200,
easing: 'easeOutBack',
stagger: 100,
direction: 'center'
},
smoothSlideIn: {
duration: 600,
easing: 'easeOutCubic',
stagger: 75,
direction: 'left'
},
bouncyScale: {
duration: 1000,
easing: 'easeOutElastic',
stagger: 150,
direction: 'center'
},
subtleMorph: {
duration: 500,
easing: 'easeInOutQuad',
stagger: 0,
direction: 'up'
}
};
// Utility functions for common chart animations
const animateChart = {
fadeIn: (series, config, callbacks) => ChartAnimationUtils.createEntranceAnimation(series, 'fadeIn', {
...chartAnimationPresets.gentleFadeIn,
...config
}, callbacks?.onUpdate, callbacks?.onComplete),
slideIn: (series, direction = 'up', config, callbacks) => ChartAnimationUtils.createEntranceAnimation(series, 'slideIn', {
...chartAnimationPresets.smoothSlideIn,
direction,
...config
}, callbacks?.onUpdate, callbacks?.onComplete),
scaleIn: (series, config, callbacks) => ChartAnimationUtils.createEntranceAnimation(series, 'scaleIn', {
...chartAnimationPresets.bouncyScale,
...config
}, callbacks?.onUpdate, callbacks?.onComplete),
morph: (from, to, config, callbacks) => ChartAnimationUtils.createMorphingAnimation(from, to, {
...chartAnimationPresets.subtleMorph,
...config
}, callbacks?.onUpdate, callbacks?.onComplete),
transition: (transition, callbacks) => ChartAnimationUtils.createChartTransition(transition, callbacks?.onUpdate, callbacks?.onComplete)
};
export { ChartAnimationUtils, animateChart, chartAnimationPresets };
//# sourceMappingURL=chartAnimations.js.map