@stianlarsen/react-fade-effects
Version:
A collection of React components for smooth fade-in animations, starting with FadeWords. Easily add fade effects to your UI elements with customizable options.
166 lines (162 loc) • 6.15 kB
JavaScript
"use client";
// src/components/fadedWords.tsx
import {
motion,
stagger,
useAnimate,
useInView
} from "framer-motion";
import { useEffect, useMemo, useRef } from "react";
// #style-inject:#style-inject
function styleInject(css, { insertAt } = {}) {
if (!css || typeof document === "undefined") return;
const head = document.head || document.getElementsByTagName("head")[0];
const style = document.createElement("style");
style.type = "text/css";
if (insertAt === "top") {
if (head.firstChild) {
head.insertBefore(style, head.firstChild);
} else {
head.appendChild(style);
}
} else {
head.appendChild(style);
}
if (style.styleSheet) {
style.styleSheet.cssText = css;
} else {
style.appendChild(document.createTextNode(css));
}
}
// src/css/fadeEffects.css
styleInject(".sl-fade-textEffectWrapper {\n display: inline-block;\n vertical-align: inherit;\n text-align: inherit;\n text-decoration: inherit;\n text-wrap: inherit;\n font-size: inherit;\n font-weight: inherit;\n font-style: inherit;\n font-family: inherit;\n line-height: inherit;\n letter-spacing: inherit;\n color: inherit;\n background-color: inherit;\n border: inherit;\n border-radius: inherit;\n}\n.sl-fade-textEffectWrapper .sl-fade-word {\n display: inline-block;\n opacity: 0;\n color: inherit;\n vertical-align: inherit;\n text-align: inherit;\n text-decoration: inherit;\n text-wrap: inherit;\n font-size: inherit;\n font-weight: inherit;\n font-style: inherit;\n font-family: inherit;\n line-height: inherit;\n letter-spacing: inherit;\n background-color: inherit;\n border: inherit;\n border-radius: inherit;\n will-change:\n transform,\n opacity,\n filter;\n backface-visibility: hidden;\n -webkit-backface-visibility: hidden;\n transform: translateZ(0);\n contain: layout style;\n}\n.sl-fade-renderWordsWrapper {\n display: block;\n text-align: inherit;\n text-decoration: inherit;\n text-wrap: inherit;\n font-size: inherit;\n font-weight: inherit;\n font-style: inherit;\n font-family: inherit;\n line-height: inherit;\n letter-spacing: inherit;\n color: inherit;\n background-color: inherit;\n border: inherit;\n border-radius: inherit;\n}\n");
// src/components/fadedWords.tsx
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var GPU_BLUR_PX = 8;
var FadedWords = ({
words,
className,
filter = true,
duration = 1,
staggerTime = 0.1,
delay = 0.2,
variant = "default",
scaleSize = void 0,
once = true,
translateAmount = void 0,
splitChar = " ",
includeSpaces = true
}) => {
const [scope, animate] = useAnimate();
const isInViewContainer = useRef(null);
const isInView = useInView(isInViewContainer, {
once,
amount: 0.5
});
const tokensArray = useMemo(() => {
if (Array.isArray(words)) {
return words.map((word) => ({ value: word, isDelimiter: false }));
}
let delimiterRegex;
if (splitChar instanceof RegExp) {
delimiterRegex = splitChar;
} else {
delimiterRegex = new RegExp(escapeRegex(splitChar), "g");
}
return tokenize(words, delimiterRegex);
}, [words, splitChar]);
const initialStyle = useMemo(
() => getInitialStyleObject(variant, filter, scaleSize, translateAmount),
[variant, filter, scaleSize, translateAmount]
);
useEffect(() => {
if (!isInView || !isInViewContainer.current || !scope.current) return;
const timeoutId = setTimeout(() => {
animate(
".singleWord",
{
opacity: 1,
filter: filter ? "blur(0px)" : "none",
transform: "translateY(0px) translateX(0px) scale(1)"
},
{
duration,
delay: stagger(staggerTime),
ease: [0.25, 0.1, 0.25, 1]
}
);
}, delay * 1e3);
return () => clearTimeout(timeoutId);
}, [isInView, animate, delay, duration, filter, staggerTime]);
return /* @__PURE__ */ jsx("div", { ref: isInViewContainer, className: "sl-fade-renderWordsWrapper", children: /* @__PURE__ */ jsx(
motion.span,
{
ref: scope,
className: `${className || ""} sl-fade-textEffectWrapper`,
children: tokensArray.map((token, idx) => /* @__PURE__ */ jsxs(
motion.span,
{
className: "sl-fade-word singleWord",
style: initialStyle,
children: [
token.value,
includeSpaces && !token.isDelimiter && idx < tokensArray.length - 1 ? /* @__PURE__ */ jsx(Fragment, { children: "\xA0" }) : null
]
},
token.value + idx
))
}
) });
};
var getInitialStyleObject = (variant, filter, scaleSize, translateAmount) => {
let translateY = "0px";
let translateX = "0px";
switch (variant) {
case "up":
translateY = `${translateAmount !== void 0 ? translateAmount : 15}px`;
break;
case "down":
translateY = `${translateAmount !== void 0 ? -translateAmount : -15}px`;
break;
case "left":
translateX = `${translateAmount !== void 0 ? translateAmount : 15}px`;
break;
case "right":
translateX = `${translateAmount !== void 0 ? -translateAmount : -15}px`;
break;
}
const scale = scaleSize !== void 0 ? scaleSize : 1;
return {
opacity: 0,
filter: filter ? `blur(${GPU_BLUR_PX}px)` : "none",
display: "inline-block",
transform: `translateY(${translateY}) translateX(${translateX}) scale(${scale})`
};
};
var tokenize = (str, delimiterRegex) => {
const tokens = [];
let lastIndex = 0;
const regex = new RegExp(delimiterRegex, "g");
let match;
while ((match = regex.exec(str)) !== null) {
if (match.index > lastIndex) {
tokens.push({
value: str.slice(lastIndex, match.index),
isDelimiter: false
});
}
tokens.push({ value: match[0], isDelimiter: true });
lastIndex = regex.lastIndex;
}
if (lastIndex < str.length) {
tokens.push({ value: str.slice(lastIndex), isDelimiter: false });
}
return tokens;
};
var escapeRegex = (str) => {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
};
export {
FadedWords
};
//# sourceMappingURL=index.js.map