FasterMotion
API ReferenceText

TextPath

Create dynamic SVG text flowing around elements with infinite scroll

TextPath creates scrolling text that flows around elements along SVG paths. It supports circles, ovals, rounded rectangles, and custom paths with seamless infinite scroll animation.

Basic Usage

import { TextPath } from 'faster-motion';

// Create scrolling text around a circle element
const textPath = TextPath.create('.badge', 'Premium Quality • ', {
  offset: 30,           // Distance from element edge
  borderRadius: 100,    // Creates circular path
  animate: {
    infiniteScroll: true,
    duration: 5000      // 5 second loop
  }
});

// Later: cleanup
textPath.destroy();

Try it: TextPath

const textPath = TextPath.create('.badge', 'Premium • ', {
  offset: 30,
  borderRadius: 100,
  animate: { infiniteScroll: true, duration: 5000 }
});

Circle TextPath

Try it: Circle

TextPath.create('#circle', 'Premium Quality • ', {
  offset: 30,
  borderRadius: 100,
  animate: { infiniteScroll: true, duration: 5000 }
});

Oval TextPath

Try it: Oval

TextPath.create('#oval', 'Elliptical Path • ', {
  offset: 30,
  borderRadius: 100,
  animate: { infiniteScroll: true, duration: 5000 }
});

Rectangle TextPath

Try it: Rectangle

TextPath.create('#rectangle', 'Rounded Rectangle • ', {
  offset: 30,
  borderRadius: 20,
  animate: { infiniteScroll: true, duration: 5000 }
});

TextPath.create()

Create an SVG text path around an element.

Syntax

TextPath.create(
  element: Element | string,
  text: string,
  options?: TextPathOptions
): TextPathData

Parameters

  • element - Target element or CSS selector
  • text - Text to display (automatically repeated for seamless scroll)
  • options - Configuration object (see options below)

Returns

TextPathData object:

interface TextPathData {
  container: HTMLElement;            // Wrapper container
  svg: SVGSVGElement;                // SVG element
  path: SVGPathElement;              // Path element
  textPath: SVGTextPathElement;      // TextPath element
  textElement: SVGTextElement;       // Text element
  update: (transformOverride?: string) => void;
  destroy: () => void;
}

Options

offset

Type: number | { x: number; y: number }

Default: 30

Distance of path from element edges in pixels.

  • Positive values: Path outside element
  • Zero: Path at element edges
  • Negative values: Path inside element
// Path 50px outside element
TextPath.create('.element', 'Text • ', { offset: 50 });

// Path at element edge
TextPath.create('.element', 'Text • ', { offset: 0 });

// Path 20px inside element
TextPath.create('.element', 'Text • ', { offset: -20 });

borderRadius

Type: number

Default: 30

Corner radius for the path. Higher values create more rounded paths.

// Circle (large radius)
TextPath.create('.circle', 'Text • ', { borderRadius: 100 });

// Rounded rectangle
TextPath.create('.box', 'Text • ', { borderRadius: 20 });

// Sharp corners
TextPath.create('.box', 'Text • ', { borderRadius: 0 });

Auto-detection:

  • Square element + large radius = Circle path
  • Non-square + large radius = Ellipse path
  • Any element + small radius = Rounded rectangle

customPath

Type: string

Custom SVG path data (d attribute). Overrides automatic path generation.

const pathData = 'M10,80 Q95,10 180,80 Q265,150 350,80';

TextPath.create('.element', 'Custom Curve • ', {
  customPath: pathData
});

autoRotate

Type: boolean

Default: false

Rotate text to follow path tangent. Useful for custom curved paths.

TextPath.create('.element', 'Text • ', {
  autoRotate: true,
  borderRadius: 100
});

watchResize

Type: boolean

Default: true

Monitor element resize and update path automatically.

// Disable resize observation
TextPath.create('.element', 'Text • ', {
  watchResize: false
});

animate

Type: object

Animation configuration for the text path.

Properties:

  • infiniteScroll (boolean) - Enable seamless infinite scrolling
  • duration (number) - Animation duration in milliseconds
  • ease (string) - Easing function (for manual mode)
  • repeat (number) - Repeat count (for manual mode)
  • startOffset ([number, number]) - [from, to] percentage (for manual mode)

Infinite Scroll (Recommended):

TextPath.create('.element', 'Scrolling Text • ', {
  animate: {
    infiniteScroll: true,
    duration: 5000  // 5 second loop
  }
});

Manual Mode:

TextPath.create('.element', 'Text • ', {
  animate: {
    startOffset: [0, 100],  // 0% to 100%
    duration: 3000,
    ease: 'linear',
    repeat: Infinity
  }
});

Instance Methods

.update(transformOverride?)

Update path position. Call after element transforms.

const textPath = TextPath.create('.element', 'Text • ');

// Element rotated
element.style.transform = 'rotate(45deg)';

// Update TextPath to match
textPath.update('rotate(45deg)');

.destroy()

Cleanup and remove the TextPath.

const textPath = TextPath.create('.element', 'Text • ');

// Later: cleanup
textPath.destroy();

Static Methods

TextPath.get(element)

Get existing TextPath instance for an element.

const textPath = TextPath.get('.element');

if (textPath) {
  console.log(textPath.svg);  // Access SVG element
}

TextPath.update(element)

Update TextPath for an element (useful after transforms).

element.style.transform = 'rotate(45deg)';
TextPath.update(element);

TextPath.destroy(element)

Destroy TextPath instance for a specific element.

TextPath.destroy('.element');

TextPath.destroyAll()

Destroy all TextPath instances. Useful for SPA route changes.

// Cleanup all on route change
TextPath.destroyAll();

Helper Functions

createCircularTextPath()

Convenience function for circular text paths.

import { createCircularTextPath } from 'faster-motion';

const textPath = createCircularTextPath('.circle', 'Text • ', 100, {
  offset: 30,
  animate: {
    infiniteScroll: true,
    duration: 5000
  }
});

Examples

Basic Circle

<div class="circle" style="width: 200px; height: 200px; border-radius: 100px;"></div>
TextPath.create('.circle', 'Spinning Text • ', {
  offset: 30,
  borderRadius: 100,
  animate: {
    infiniteScroll: true,
    duration: 5000
  }
});

Oval/Ellipse

<div class="oval" style="width: 300px; height: 150px; border-radius: 150px;"></div>
TextPath.create('.oval', 'Elliptical Path • ', {
  offset: 40,
  borderRadius: 75,
  animate: {
    infiniteScroll: true,
    duration: 6000
  }
});

Rounded Rectangle

<div class="box" style="width: 300px; height: 200px; border-radius: 20px;"></div>
TextPath.create('.box', 'Rounded Box • ', {
  offset: 20,
  borderRadius: 20,
  animate: {
    infiniteScroll: true,
    duration: 8000
  }
});

Inside Element

// Text inside element borders
TextPath.create('.element', 'Inside Text • ', {
  offset: -20,  // Negative = inside
  borderRadius: 50,
  animate: {
    infiniteScroll: true,
    duration: 4000
  }
});

Styling Text

const textPath = TextPath.create('.element', 'Styled Text • ', {
  offset: 30,
  borderRadius: 100
});

// Style the text
textPath.textElement.style.fill = '#ff0000';
textPath.textElement.style.fontSize = '24px';
textPath.textElement.style.fontWeight = 'bold';
textPath.textElement.style.fontFamily = 'Arial, sans-serif';

With Scroll-Synced Rotation

import { TextPath, ScrollObserver } from 'faster-motion';

const textPath = TextPath.create('.hero-badge', 'Scroll Down • ', {
  offset: 30,
  borderRadius: 80,
  animate: {
    infiniteScroll: true,
    duration: 6000
  }
});

ScrollObserver.observe('.hero-badge', ({ progress }) => {
  const rotation = progress * 720;  // 2 full rotations
  const element = document.querySelector('.hero-badge');
  const transform = `rotate(${rotation}deg)`;

  element.style.transform = transform;
  textPath.update(transform);
});

Multiple Elements

const elements = document.querySelectorAll('.badge');

elements.forEach(element => {
  TextPath.create(element, 'Badge Text • ', {
    offset: 10,
    borderRadius: 50,
    animate: {
      infiniteScroll: true,
      duration: 3000
    }
  });
});

Cleanup on Route Change

// Create TextPaths
TextPath.create('.badge-1', 'Text • ', { animate: { infiniteScroll: true, duration: 5000 } });
TextPath.create('.badge-2', 'Text • ', { animate: { infiniteScroll: true, duration: 5000 } });

// SPA route change
router.on('beforeNavigate', () => {
  TextPath.destroyAll();  // Cleanup all
});

Best Practices

Do

  • Use a separator character (like ) in your text for visual spacing
  • Call destroy() when unmounting components
  • Use TextPath.destroyAll() on SPA route changes
  • Update after transforms with textPath.update(transformString)
  • Use infiniteScroll: true for continuous animations

Don't

  • Forget the separator: "Text" will run together, use "Text • "
  • Create without cleanup: causes memory leaks in SPAs
  • Use with tiny elements where text is larger than the element
  • Forget to update after transforms (TextPath will be out of sync)
  • Use negative duration values

TypeScript

import {
  TextPath,
  TextPathOptions,
  TextPathData,
  createCircularTextPath
} from 'faster-motion';

// Type-safe options
const options: TextPathOptions = {
  offset: 30,
  borderRadius: 100,
  watchResize: true,
  animate: {
    infiniteScroll: true,
    duration: 5000
  }
};

// Type-safe create
const element: Element = document.querySelector('.element')!;
const textPath: TextPathData = TextPath.create(element, 'Text • ', options);

// Type-safe instance properties
const container: HTMLElement = textPath.container;
const svg: SVGSVGElement = textPath.svg;

// Type-safe methods
textPath.update('rotate(45deg)');
textPath.destroy();

Performance Notes

Transform Composition

TextPath uses a wrapper architecture for perfect sync with element transforms:

Container (receives element transforms)
├── Element (original element)
└── SVG (only handles infinite scroll rotation)

Benefits:

  • No JavaScript transform coordination needed
  • CSS handles composition via DOM hierarchy
  • No getBoundingClientRect() reads
  • Perfect sync at all times

Cached Layout

Layout properties are cached for fast transform updates. Only resize triggers recalculation.

Seamless Infinite Scroll

Uses modulo math for seamless wrapping - continuous scrolling with no visible reset.

Browser Support

  • Chrome/Edge: Full support
  • Firefox: Full support
  • Safari: Full support (iOS 12+)
  • Requires: SVG support (all modern browsers)

See Also

On this page