Overview of the Elastic Line component
The Elastic Line is a UI component that renders a wobbly SVG line featuring a spring-based cursor interaction. It allows users to 'grab' and manipulate the line's shape based on mouse or touch movement.
website·Indexed 34 days ago
https://www.fancycomponents.dev/docs/introductionA collection of open-source, high-performance UI components featuring 3D transforms, physics-based animations with Matter.js, SVG filters, and advanced text effects. Includes a CLI for installation and a wide variety of interactive blocks such as 3D CSS Boxes, Box Carousels, and cursor-proximity text animations.
The Letter Swap component creates a vertical sliding effect where letters are swapped on hover. It is available in two variants:
TextRotate component cycles through an array of strings provided in the texts prop. It supports automatic rotation (via the auto prop) or manual control using next() and previous() methods via a ref. It utilizes AnimatePresence from motion/react to handle the transition between the current and next text strings.AnimatedPathText component allows you to animate text along a specified SVG path. It supports two animation modes: auto (time-based) and scroll (driven by a scroll container). Performance may decrease as path length and complexity increase, especially when using multiple instances.VariableFontCursorProximity component animates the font variation settings of individual letters based on their distance from the cursor. It requires a variable font to function. The component calculates the distance between the cursor and each letter in real-time; as the cursor enters the specified radius, the letter's font variations interpolate from the fromFontVariationSettings state to the toFontVariationSettings state.TextCursorProximity component splits text into individual letters and animates their CSS properties based on the distance between the cursor and each letter. It uses the motion library to interpolate values between a 'from' state and a 'to' state as the cursor enters a defined radius. It supports any CSS value that the motion library can animate.TextRotate component is a React component that cycles through an array of text strings with customizable animations. It supports automatic rotation, manual control via a ref, and various text-splitting strategies (characters, words, or lines) to create staggered animation effects.useDimensions hook to track the size of the container and the element, and the Screensaver component itself which uses motion/react (Framer Motion) to handle the animation and boundary collisions.import { RefObject, useEffect, useState } from "react"
interface Dimensions {
width: number
height: number
}
export function useDimensions(
ref: RefObject<HTMLElement | SVGElement | null>
): Dimensions {
const [dimensions, setDimensions] = useState<Dimensions>({ width: 0, height: 0 })
useEffect(() => {
const updateDimensions = () => {
if (ref.current) {
const { width, height } = ref.current.getBoundingClientRect()
setDimensions({ width, height })
}
}
updateDimensions()
window.addEventListener("resize", updateDimensions)
return () => window.removeEventListener("resize", updateDimensions)
}, [ref])
return dimensions
}
// Screensaver Component
"use client"
import React, { useEffect, useRef } from "react"
import { motion, useAnimationFrame, useMotionValue } from "motion/react"
import { cn } from "@/lib/utils"
import { useDimensions } from "@/hooks/use-dimensions"
type ScreensaverProps = {
children: React.ReactNode
containerRef: React.RefObject<HTMLElement | null>
speed?: number
startPosition?: { x: number; y: number }
startAngle?: number
className?: string
}
const Screensaver: React.FC<ScreensaverProps> = ({
children,
speed = 3,
startPosition = { x: 0, y: 0 },
startAngle = 45,
containerRef,
className,
}) => {
const elementRef = useRef<HTMLDivElement>(null)
const x = useMotionValue(0)
const y = useMotionValue(0)
const angle = useRef((startAngle * Math.PI) / 180)
const containerDimensions = useDimensions(containerRef)
const elementDimensions = useDimensions(elementRef)
useEffect(() => {
if (containerDimensions.width && containerDimensions.height) {
const initialX = (startPosition.x / 100) * (containerDimensions.width - (elementDimensions.width || 0))
const initialY = (startPosition.y / 100) * (containerDimensions.height - (elementDimensions.height || 0))
x.set(initialX)
y.set(initialY)
}
}, [containerDimensions, elementDimensions, startPosition])
useAnimationFrame(() => {
const velocity = speed
const dx = Math.cos(angle.current) * velocity
const dy = Math.sin(angle.current) * velocity
let newX = x.get() + dx
let newY = y.get() + dy
if (newX <= 0 || newX + elementDimensions.width >= containerDimensions.width) {
angle.current = Math.PI - angle.current
newX = Math.max(0, Math.min(newX, containerDimensions.width - elementDimensions.width))
}
if (newY <= 0 || newY + elementDimensions.height >= containerDimensions.height) {
angle.current = -angle.current
newY = Math.max(0, Math.min(newY, containerDimensions.height - elementDimensions.height))
}
x.set(newX)
y.set(newY)
})
return (
<motion.div
ref={elementRef}
style={{ position: "absolute", top: 0, left: 0, x, y }}
className={cn("transform will-change-transform", className)}
>
{children}
</motion.div>
)
}
export default Screensaver// 1. Create useDimensions hook
import { RefObject, useEffect, useState } from "react"
interface Dimensions {
width: number
height: number
}
export function useDimensions(
ref: RefObject<HTMLElement | SVGElement | null>
): Dimensions {
const [dimensions, setDimensions] = useState<Dimensions>({ width: 0, height: 0 })
useEffect(() => {
const updateDimensions = () => {
if (ref.current) {
const { width, height } = ref.current.getBoundingClientRect()
setDimensions({ width, height })
}
}
updateDimensions()
window.addEventListener("resize", updateDimensions)
return () => window.removeEventListener("resize", updateDimensions)
}, [ref])
return dimensions
}/* 2. Add to globals.css */
@theme: {
--animate-background-gradient: background-gradient;
@keyframes: background-gradient {
0%, 100% {
transform: translate(0, 0);
animationDelay: var(--background-gradient-delay, 0s);
}
20% { transform: translate(calc(100% * var(--tx-1, 1)), calc(100% * var(--ty-1, 1))); }
40% { transform: translate(calc(100% * var(--tx-2, -1)), calc(100% * var(--ty-2, 1))); }
60% { transform: translate(calc(100% * var(--tx-3, 1)), calc(100% * var(--ty-3, -1))); }
80% { transform: translate(calc(100% * var(--tx-4, -1)), calc(100% * var(--ty-4, -1))); }
}
}Gravity component initializes a Matter.js physics world and manages the animation loop that syncs HTML element positions with physics bodies. To make an element react to physics, wrap it in a MatterBody component. You must provide initial x and y positions for each MatterBody, which can be specified as percentages (e.g., "50%") or absolute numbers. The component automatically calculates the width and height of the wrapped element.<Gravity>
<MatterBody x="50%" y="50%">
<div>Hello world!</div>
</MatterBody>
<MatterBody x="10%" y="10%">
<div>fancy!</div>
</MatterBody>
</Gravity>npx shadcn add @fancy/3d-css-box