Top Related Projects
GSAP (GreenSock Animation Platform), a JavaScript animation library for the modern web
Beautiful React SVG maps with d3-geo and topojson using a declarative api.
Render After Effects animations natively on Web, Android and iOS, and React Native. http://airbnb.io/lottie/
JavaScript 3D Library.
✌️ A spring physics based React animation library
Quick Overview
Flubber is a JavaScript library for smoothly interpolating between shapes. It provides tools for morphing SVG paths, transforming arrays of 2D points, and creating in-between states for animations. Flubber is particularly useful for creating fluid transitions between different shapes or data visualizations.
Pros
- Smooth and visually appealing shape interpolations
- Supports various input formats (SVG paths, point arrays, circles, rectangles)
- Offers fine-grained control over interpolation settings
- Lightweight and easy to integrate with other animation libraries
Cons
- Limited to 2D shape interpolations
- May require additional processing for complex shapes
- Performance can degrade with very large or intricate shapes
- Documentation could be more comprehensive for advanced use cases
Code Examples
Basic shape interpolation:
import { interpolate } from 'flubber';
const interpolator = interpolate('M0,0 L100,0 L100,100 L0,100 Z', 'M0,0 Q50,-50 100,0 Q150,50 100,100 Q50,150 0,100 Z');
// Get the shape at 50% of the interpolation
const halfwayShape = interpolator(0.5);
Interpolating between arrays of points:
import { interpolateAll } from 'flubber';
const startPoints = [[0, 0], [100, 0], [100, 100], [0, 100]];
const endPoints = [[50, 50], [150, 50], [150, 150], [50, 150]];
const interpolator = interpolateAll(startPoints, endPoints);
// Get all points at 25% of the interpolation
const quarterWayPoints = interpolator(0.25);
Custom interpolation settings:
import { interpolate } from 'flubber';
const interpolator = interpolate('M0,0 H100 V100 H0 Z', 'M50,50 L150,50 L150,150 L50,150 Z', {
maxSegmentLength: 10,
string: false
});
// Get the shape as an array of points at 75% of the interpolation
const threeQuartersShape = interpolator(0.75);
Getting Started
To use Flubber in your project, first install it via npm:
npm install flubber
Then, import and use the desired functions in your JavaScript code:
import { interpolate } from 'flubber';
const shape1 = 'M0,0 L100,0 L100,100 L0,100 Z';
const shape2 = 'M0,0 Q50,-50 100,0 Q150,50 100,100 Q50,150 0,100 Z';
const interpolator = interpolate(shape1, shape2);
// Use the interpolator in your animation loop
function animate(t) {
const currentShape = interpolator(t);
// Apply the currentShape to your SVG or canvas
}
This basic setup allows you to interpolate between two shapes. Adjust the t
value (0 to 1) to control the interpolation progress.
Competitor Comparisons
GSAP (GreenSock Animation Platform), a JavaScript animation library for the modern web
Pros of GSAP
- More comprehensive animation toolkit with a wider range of features
- Better performance for complex animations and large-scale projects
- Extensive documentation and community support
Cons of GSAP
- Larger file size and potential overhead for simpler projects
- Steeper learning curve due to its extensive API
- Commercial license required for some use cases
Code Comparison
GSAP:
gsap.to(".box", {duration: 2, x: 100, y: 50, rotation: 360});
Flubber:
const interpolator = flubber.interpolate(shape1, shape2);
const tween = t => path.attr("d", interpolator(t));
Summary
GSAP is a more robust animation library with a wider range of features and better performance for complex projects. However, it comes with a larger file size and a steeper learning curve. Flubber, on the other hand, is specifically designed for shape morphing and is lighter and easier to use for this particular task. The choice between the two depends on the project's specific requirements and the complexity of the animations needed.
Beautiful React SVG maps with d3-geo and topojson using a declarative api.
Pros of react-simple-maps
- Specifically designed for React applications, offering seamless integration
- Provides a higher-level abstraction for creating interactive maps
- Includes built-in components for common map elements (e.g., Marker, Line)
Cons of react-simple-maps
- Limited to geographical data visualization, less versatile for general shape morphing
- May have a steeper learning curve for developers not familiar with map projections
- Potentially larger bundle size due to included map data and components
Code Comparison
react-simple-maps:
import { ComposableMap, Geographies, Geography } from "react-simple-maps"
const Map = () => (
<ComposableMap>
<Geographies geography={geoUrl}>
{({ geographies }) => geographies.map(geo => <Geography key={geo.rsmKey} geography={geo} />)}
</Geographies>
</ComposableMap>
)
flubber:
import { interpolate } from 'flubber'
const interpolator = interpolate(shape1, shape2)
const tweenedShape = interpolator(0.5)
While react-simple-maps focuses on rendering interactive maps in React applications, flubber is a more general-purpose library for shape interpolation and morphing. react-simple-maps offers a higher-level API with React components, whereas flubber provides low-level functions for shape manipulation, making it more flexible but requiring more setup for complex visualizations.
Render After Effects animations natively on Web, Android and iOS, and React Native. http://airbnb.io/lottie/
Pros of Lottie-web
- Supports complex animations with keyframes, shapes, and effects
- Integrates seamlessly with Adobe After Effects for design-to-code workflow
- Extensive documentation and community support
Cons of Lottie-web
- Larger file size and potentially higher performance overhead
- Steeper learning curve for developers not familiar with After Effects
- Limited control over individual animation elements programmatically
Code Comparison
Flubber (shape morphing):
const interpolator = flubber.interpolate(shape1, shape2);
const tweenedPath = interpolator(0.5);
Lottie-web (loading and playing animation):
const animation = lottie.loadAnimation({
container: element,
renderer: 'svg',
loop: true,
autoplay: true,
path: 'animation.json'
});
Summary
Flubber focuses on shape morphing and interpolation, offering a lightweight solution for specific animation needs. Lottie-web provides a comprehensive animation framework, supporting complex animations created in After Effects. While Flubber excels in simplicity and performance for shape transitions, Lottie-web offers a broader range of animation capabilities at the cost of increased complexity and resource usage.
JavaScript 3D Library.
Pros of three.js
- Comprehensive 3D graphics library with extensive features
- Large, active community and ecosystem
- Well-documented with numerous examples and tutorials
Cons of three.js
- Steeper learning curve due to complexity
- Larger file size and potentially higher performance overhead
- May be overkill for simple 2D shape morphing tasks
Code Comparison
three.js (3D cube):
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
flubber (shape morphing):
const interpolator = flubber.interpolate(
"M0,0 L100,0 L100,100 L0,100 Z",
"M0,0 C50,-50 50,150 100,100 L100,100 L0,100 Z"
);
const midway = interpolator(0.5);
Summary
three.js is a powerful 3D graphics library with a wide range of features and strong community support, making it ideal for complex 3D projects. However, it may be excessive for simpler tasks like 2D shape morphing. flubber, on the other hand, is specifically designed for shape interpolation and offers a more straightforward API for such tasks, but lacks the extensive 3D capabilities of three.js.
✌️ A spring physics based React animation library
Pros of react-spring
- Designed specifically for React applications, offering seamless integration
- Provides a more comprehensive animation system with physics-based springs
- Supports a wider range of animation types, including gestures and parallax effects
Cons of react-spring
- Steeper learning curve due to its more complex API and concepts
- Larger bundle size, which may impact performance in smaller projects
- Less suitable for simple shape morphing or SVG animations
Code Comparison
react-spring example:
import { useSpring, animated } from 'react-spring'
function AnimatedComponent() {
const props = useSpring({ opacity: 1, from: { opacity: 0 } })
return <animated.div style={props}>I will fade in</animated.div>
}
flubber example:
import { interpolate } from 'flubber'
const interpolator = interpolate(shape1, shape2)
const tweenedPath = interpolator(0.5)
While react-spring focuses on general-purpose animations in React, flubber specializes in shape morphing and SVG interpolation. react-spring offers more flexibility for complex animations, but flubber excels in simplicity for specific shape-related tasks.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
flubber
Some best-guess methods for smoothly interpolating between 2-D shapes.
Why?
Let's say you want to animate between two SVG paths or canvas shapes in a visualization. If you plug in their coordinates or their path strings to something like d3.transition()
, it might work if the shapes correspond to each other really well - for example, turning a triangle into a different triangle. But once your shapes don't really correspond, you'll get unpredictable results with weird inversions and sudden jumps.
The goal of this library is to provide a best-guess interpolation for any two arbitrary shapes (or collections of shapes) that results in a reasonably smooth animation, without overthinking it.
Installation
In a browser (exposes the flubber
global):
<script src="https://unpkg.com/flubber@0.3.0"></script>
With NPM:
npm install flubber
And then import/require it:
var flubber = require("flubber"); // Node classic
import { interpolate } from "flubber" // ES6
How to use
Flubber expects a shape input to be either an SVG path string or an array of [x, y]
points (a "ring"):
"M100,100 L200,100 L150,200Z" // A triangle as a path string
[[100, 100], [200, 100], [150, 200]] // A triangle as a ring
Flubber methods return interpolators, functions that you can call later with a value from 0 to 1 to get back the corresponding shape, where 0 is the beginning of the animation and 1 is the end.
Using D3, usage could look something like:
var triangle = [[1, 0], [2, 2], [0, 2]],
pentagon = [[0, 0], [2, 0], [2, 1], [1, 2], [0, 1]];
var interpolator = flubber.interpolate(triangle, pentagon);
d3.select("path")
.transition()
.attrTween("d", function(){ return interpolator; });
Without D3, usage might look something like this:
// Mixing and matching input types is OK
var triangle = "M1,0 L2,2 L0,2 Z",
pentagon = [[0, 0], [2, 0], [2, 1], [1, 2], [0, 1]];
var interpolator = flubber.interpolate(triangle, pentagon);
requestAnimationFrame(draw);
function draw(time) {
var t = howFarAlongTheAnimationIsOnAScaleOfZeroToOne(time);
myPathElement.setAttribute("d", interpolator(t));
if (t < 1) {
requestAnimationFrame(draw);
}
}
Note: it doesn't matter whether your ring has a closing point identical to the first point.
API
flubber.interpolate(fromShape, toShape [, options])
fromShape
and toShape
should each be a ring or an SVG path string. If your path string includes holes or multiple shapes in a single string, everything but the first outer shape will be ignored.
This returns a function that takes a value t
from 0 to 1 and returns the in-between shape:
var interpolator = flubber.interpolate(triangle, octagon);
interpolator(0); // returns an SVG triangle path string
interpolator(0.5); // returns something halfway between the triangle and the octagon
interpolator(1); // returns an SVG octagon path string
options
can include the following keys:
string
: whether to output results as an SVG path string or an array of points. (default: true
)
maxSegmentLength
: the lower this number is, the smoother the resulting animation will be, at the expense of performance. Represents a number in pixels (if no transforms are involved). Set it to false
or Infinity
for no smoothing. (default: 10
)
.interpolate() in action with SVG paths as input
.interpolate() in action with GeoJSON coordinates as input
flubber.toCircle(fromShape, x, y, r[, options])
Like interpolate()
, but for the specific case of transforming the shape to a circle centered at [x, y]
with radius r
.
var interpolator = flubber.toCircle(triangle, 100, 100, 10);
interpolator(0); // returns an SVG triangle path string
interpolator(0.5); // returns something halfway between the triangle and the circle
interpolator(1); // returns a circle path string centered at 100, 100 with a radius of 10
flubber.toRect(fromShape, x, y, width, height[, options])
Like interpolate()
, but for the specific case of transforming the shape to a rectangle with the upper-left corner [x, y]
and the dimensions width
x height
.
var interpolator = flubber.toRect(triangle, 10, 50, 100, 200);
interpolator(0); // returns an SVG triangle path string
interpolator(0.5); // returns something halfway between the triangle and the rectangle
interpolator(1); // returns a rectangle path string from [10, 50] in the upper left to [110, 250] in the lower right
flubber.fromCircle(x, y, r, toShape[, options])
Like toCircle()
but reversed.
flubber.fromRect(x, y, width, height, toShape[, options])
Like toRect()
but reversed.
flubber.separate(fromShape, toShapeList[, options])
If you're trying to interpolate between a single shape and multiple shapes (for example, a group of three circles turning into a single big circle), this method will break your shapes into pieces so you can animate between the two sets. This isn't terribly performant and has some quirks but it tends to get the job done.
fromShape
should be a ring or SVG path string, and toShapeList
should be an array of them.
The options are the same as for interpolate()
, with the additional option of single
, which defaults to false
.
If single
is false, this returns an array of n
interpolator functions, where n
is the length of toShapeList
. If single
is set to true this returns one interpolator that combines things into one giant path string or one big array of rings.
// returns an array of two interpolator functions
var interpolators = flubber.separate(triangle, [square, otherSquare]);
d3.selectAll("path")
.data(interpolators)
.transition()
.attrTween("d", function(interpolator) { return interpolator; });
// returns a single interpolator function
var combinedInterpolator = flubber.separate(triangle, [square, otherSquare], { single: true });
// This one path element will be two squares at the end
d3.select("path")
.transition()
.attrTween("d", function() { return combinedInterpolator; });
.separate({ single: true }) in action
flubber.combine(fromShapeList, toShape[, options])
Like separate()
but reversed.
flubber.interpolateAll(fromShapeList, toShapeList[, options])
Like separate()
or combine()
but instead expects two arrays of shapes the same length (e.g. an array of three triangles turning into an array of three squares). The shapes will be matched up in the order of the arrays (the first fromShapeList
item will turn into the first toShapeList
item, and so on).
.interpolateAll({ single: true }) in action
flubber.toPathString(ring)
A helper function for converting an array of points to an SVG path string.
flubber.toPathString([[1, 1], [2, 1], [1.5, 2]]);
// Returns "M1,1L2,1L1.5,2Z"
flubber.splitPathString(pathString)
A helper function for splitting an SVG path string that might contain multiple shapes into an array of one-shape path strings.
flubber.splitPathString("M1,1 L2,1 L1.5,2Z M3,3 L4,3 L3.5,4 Z");
// Returns ["M1,1 L2,1 L1.5,2Z", "M3,3 L4,3 L3.5,4 Z"]
Examples
Note: most of these demos use D3 to keep the code concise, but this can be used with any library, or with no library at all.
Morphing to and from rectangles
Morphing between one shape and multiple shapes (one element)
Morphing between one shape and multiple shapes (multiple elements)
Morphing between two sets of multiple shapes
To do
- Maintain original vertices when polygonizing a path string with curves
- Add
force: true
option to collapse small additional polygons onto the perimeter of the largest - Support unclosed lines
- Use curves between points for
fromCircle()
andtoCircle()
- Deal with holes?
- Accept SVG elements as arguments instead of just path strings?
- Add pre-simplification as an option
- Simulated annealing or random swapping for multishape matching?
Video
OpenVisConf 2017 talk about shape interpolation
Alternatives
react-svg-morph - utility for morphing between two SVGs in React
GreenSock MorphSVG plugin - GSAP shape morphing utility (costs money, not open source)
d3.geo2rect - a plugin for morphing between GeoJSON and a rectangular SVG grid
d3-interpolate-path - a D3 interpolator to interpolate between two unclosed lines, for things like line chart transitions with mismatched data
Wilderness - an SVG manipulation and animation library
Cirque - JS utility for morphing between circles and polygons
Credits
Many thanks to:
- Mike Bostock for D3 and TopoJSON
- Vladimir Agafonkin and Mapbox for earcut
- Roger Veciana Rovira for svg-path-properties
- Fontello for svgpath
- Rich Harris for Rollup and Bublé
License
MIT License
Copyright (c) 2017 Noah Veltman
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Top Related Projects
GSAP (GreenSock Animation Platform), a JavaScript animation library for the modern web
Beautiful React SVG maps with d3-geo and topojson using a declarative api.
Render After Effects animations natively on Web, Android and iOS, and React Native. http://airbnb.io/lottie/
JavaScript 3D Library.
✌️ A spring physics based React animation library
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot