@elyri/motion 0.0.0-stage → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -2
  3. package/dist/chunk-A7JCTGZO.js +230 -0
  4. package/dist/chunk-A7JCTGZO.js.map +1 -0
  5. package/dist/core.cjs +238 -0
  6. package/dist/core.cjs.map +1 -0
  7. package/dist/core.d.cts +77 -0
  8. package/dist/core.d.ts +77 -0
  9. package/dist/core.js +3 -0
  10. package/dist/core.js.map +1 -0
  11. package/dist/index.cjs +4288 -0
  12. package/dist/index.cjs.map +1 -0
  13. package/dist/index.css +1118 -0
  14. package/dist/index.css.map +1 -0
  15. package/dist/index.d.cts +532 -0
  16. package/dist/index.d.ts +532 -0
  17. package/dist/index.js +4029 -0
  18. package/dist/index.js.map +1 -0
  19. package/package.json +60 -3
  20. package/src/components/animations/CountUp/CountUp.css +4 -0
  21. package/src/components/animations/CountUp/CountUp.tsx +100 -0
  22. package/src/components/animations/CountUp/index.ts +2 -0
  23. package/src/components/animations/FadeIn/FadeIn.css +43 -0
  24. package/src/components/animations/FadeIn/FadeIn.tsx +72 -0
  25. package/src/components/animations/FadeIn/index.ts +2 -0
  26. package/src/components/animations/Magnetic/Magnetic.css +11 -0
  27. package/src/components/animations/Magnetic/Magnetic.tsx +107 -0
  28. package/src/components/animations/Magnetic/index.ts +2 -0
  29. package/src/components/animations/ScaleIn/ScaleIn.css +24 -0
  30. package/src/components/animations/ScaleIn/ScaleIn.tsx +65 -0
  31. package/src/components/animations/ScaleIn/index.ts +2 -0
  32. package/src/components/animations/Spotlight/Spotlight.css +93 -0
  33. package/src/components/animations/Spotlight/Spotlight.tsx +100 -0
  34. package/src/components/animations/Spotlight/index.ts +2 -0
  35. package/src/components/animations/Tilt/Tilt.css +35 -0
  36. package/src/components/animations/Tilt/Tilt.tsx +123 -0
  37. package/src/components/animations/Tilt/index.ts +2 -0
  38. package/src/components/animations/index.ts +12 -0
  39. package/src/components/backgrounds/Aurora/Aurora.css +33 -0
  40. package/src/components/backgrounds/Aurora/Aurora.tsx +147 -0
  41. package/src/components/backgrounds/Aurora/index.ts +2 -0
  42. package/src/components/backgrounds/Caustics/Caustics.css +34 -0
  43. package/src/components/backgrounds/Caustics/Caustics.tsx +142 -0
  44. package/src/components/backgrounds/Caustics/index.ts +2 -0
  45. package/src/components/backgrounds/Contours/Contours.css +42 -0
  46. package/src/components/backgrounds/Contours/Contours.tsx +147 -0
  47. package/src/components/backgrounds/Contours/index.ts +2 -0
  48. package/src/components/backgrounds/DotField/DotField.css +44 -0
  49. package/src/components/backgrounds/DotField/DotField.tsx +135 -0
  50. package/src/components/backgrounds/DotField/index.ts +2 -0
  51. package/src/components/backgrounds/DragonScales/DragonScales.css +44 -0
  52. package/src/components/backgrounds/DragonScales/DragonScales.tsx +250 -0
  53. package/src/components/backgrounds/DragonScales/index.ts +2 -0
  54. package/src/components/backgrounds/Filaments/Filaments.css +38 -0
  55. package/src/components/backgrounds/Filaments/Filaments.tsx +138 -0
  56. package/src/components/backgrounds/Filaments/index.ts +2 -0
  57. package/src/components/backgrounds/FlipTiles/FlipTiles.css +44 -0
  58. package/src/components/backgrounds/FlipTiles/FlipTiles.tsx +368 -0
  59. package/src/components/backgrounds/FlipTiles/index.ts +2 -0
  60. package/src/components/backgrounds/Halo/Halo.css +43 -0
  61. package/src/components/backgrounds/Halo/Halo.tsx +122 -0
  62. package/src/components/backgrounds/Halo/index.ts +2 -0
  63. package/src/components/backgrounds/MeshGradient/MeshGradient.css +43 -0
  64. package/src/components/backgrounds/MeshGradient/MeshGradient.tsx +195 -0
  65. package/src/components/backgrounds/MeshGradient/index.ts +2 -0
  66. package/src/components/backgrounds/PixelVortex/PixelVortex.css +44 -0
  67. package/src/components/backgrounds/PixelVortex/PixelVortex.tsx +278 -0
  68. package/src/components/backgrounds/PixelVortex/index.ts +2 -0
  69. package/src/components/backgrounds/Planet/Planet.css +39 -0
  70. package/src/components/backgrounds/Planet/Planet.tsx +736 -0
  71. package/src/components/backgrounds/Planet/index.ts +2 -0
  72. package/src/components/backgrounds/SilkWaves/SilkWaves.css +44 -0
  73. package/src/components/backgrounds/SilkWaves/SilkWaves.tsx +144 -0
  74. package/src/components/backgrounds/SilkWaves/index.ts +2 -0
  75. package/src/components/backgrounds/Velvet/Velvet.css +35 -0
  76. package/src/components/backgrounds/Velvet/Velvet.tsx +246 -0
  77. package/src/components/backgrounds/Velvet/index.ts +2 -0
  78. package/src/components/backgrounds/index.ts +26 -0
  79. package/src/components/index.ts +4 -0
  80. package/src/components/media/AsciiImage/AsciiImage.css +16 -0
  81. package/src/components/media/AsciiImage/AsciiImage.tsx +366 -0
  82. package/src/components/media/AsciiImage/index.ts +2 -0
  83. package/src/components/media/index.ts +2 -0
  84. package/src/components/text/GradientText/GradientText.css +32 -0
  85. package/src/components/text/GradientText/GradientText.tsx +27 -0
  86. package/src/components/text/GradientText/index.ts +2 -0
  87. package/src/components/text/Marquee/Marquee.css +91 -0
  88. package/src/components/text/Marquee/Marquee.tsx +130 -0
  89. package/src/components/text/Marquee/index.ts +2 -0
  90. package/src/components/text/ParticleText/ParticleText.css +16 -0
  91. package/src/components/text/ParticleText/ParticleText.tsx +279 -0
  92. package/src/components/text/ParticleText/index.ts +2 -0
  93. package/src/components/text/RotatingText/RotatingText.css +133 -0
  94. package/src/components/text/RotatingText/RotatingText.tsx +112 -0
  95. package/src/components/text/RotatingText/index.ts +2 -0
  96. package/src/components/text/ScrambleText/ScrambleText.css +48 -0
  97. package/src/components/text/ScrambleText/ScrambleText.tsx +141 -0
  98. package/src/components/text/ScrambleText/index.ts +2 -0
  99. package/src/components/text/ScrollMarquee/ScrollMarquee.css +33 -0
  100. package/src/components/text/ScrollMarquee/ScrollMarquee.tsx +110 -0
  101. package/src/components/text/ScrollMarquee/index.ts +2 -0
  102. package/src/components/text/ScrollReveal/ScrollReveal.css +51 -0
  103. package/src/components/text/ScrollReveal/ScrollReveal.tsx +186 -0
  104. package/src/components/text/ScrollReveal/index.ts +2 -0
  105. package/src/components/text/SplitReveal/SplitReveal.css +52 -0
  106. package/src/components/text/SplitReveal/SplitReveal.tsx +127 -0
  107. package/src/components/text/SplitReveal/index.ts +2 -0
  108. package/src/components/text/Typewriter/Typewriter.css +37 -0
  109. package/src/components/text/Typewriter/Typewriter.tsx +80 -0
  110. package/src/components/text/Typewriter/index.ts +2 -0
  111. package/src/components/text/WaveText/WaveText.css +28 -0
  112. package/src/components/text/WaveText/WaveText.tsx +52 -0
  113. package/src/components/text/WaveText/index.ts +2 -0
  114. package/src/components/text/index.ts +20 -0
@@ -0,0 +1,127 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import type { CSSProperties } from 'react';
3
+
4
+ import { cn, usePrefersReducedMotion } from '../../../core';
5
+
6
+ import './SplitReveal.css';
7
+
8
+ export type SplitRevealBy = 'char' | 'word';
9
+
10
+ export interface SplitRevealProps {
11
+ /** 需要浮现的文本,仅支持纯字符串 */
12
+ children: string;
13
+ className?: string;
14
+ /** 拆分单位:按字符或按单词 */
15
+ by?: SplitRevealBy;
16
+ /** 起始延迟(毫秒) */
17
+ delay?: number;
18
+ /** 相邻单元之间的播放间隔(毫秒) */
19
+ stagger?: number;
20
+ /** 单个单元的动画时长(毫秒) */
21
+ duration?: number;
22
+ /** 位移距离(像素) */
23
+ distance?: number;
24
+ /** 是否只在首次进入视口时触发 */
25
+ once?: boolean;
26
+ }
27
+
28
+ interface Unit {
29
+ char: string;
30
+ index: number;
31
+ }
32
+
33
+ type Segment = { kind: 'space'; key: string; value: string } | { kind: 'word'; key: string; units: Unit[] };
34
+
35
+ /** 按空白切分文本,保留空白片段,并为每个动画单元分配全局序号 */
36
+ const splitText = (text: string, by: SplitRevealBy): Segment[] => {
37
+ let index = 0;
38
+
39
+ return text
40
+ .split(/(\s+)/)
41
+ .filter(Boolean)
42
+ .map((part, partIndex) => {
43
+ if (/^\s+$/.test(part)) return { kind: 'space' as const, key: `space-${partIndex}`, value: part };
44
+
45
+ const units = by === 'char' ? Array.from(part) : [part];
46
+
47
+ return {
48
+ kind: 'word' as const,
49
+ key: `word-${partIndex}`,
50
+ units: units.map((char) => ({ char, index: index++ })),
51
+ };
52
+ });
53
+ };
54
+
55
+ export function SplitReveal({
56
+ children,
57
+ className,
58
+ by = 'char',
59
+ delay = 0,
60
+ stagger = 40,
61
+ duration = 600,
62
+ distance = 24,
63
+ once = true,
64
+ }: SplitRevealProps) {
65
+ const elementRef = useRef<HTMLSpanElement>(null);
66
+ const prefersReducedMotion = usePrefersReducedMotion();
67
+ const [isIntersecting, setIsIntersecting] = useState(false);
68
+
69
+ useEffect(() => {
70
+ const element = elementRef.current;
71
+ if (!element || prefersReducedMotion) return;
72
+
73
+ const observer = new IntersectionObserver(
74
+ ([entry]) => {
75
+ if (entry.isIntersecting) {
76
+ setIsIntersecting(true);
77
+ if (once) observer.disconnect();
78
+ return;
79
+ }
80
+
81
+ if (!once) setIsIntersecting(false);
82
+ },
83
+ { threshold: 0.1 },
84
+ );
85
+
86
+ observer.observe(element);
87
+
88
+ return () => observer.disconnect();
89
+ }, [once, prefersReducedMotion]);
90
+
91
+ const segments = useMemo(() => splitText(children, by), [children, by]);
92
+
93
+ const style = {
94
+ '--elyri-split-reveal-delay': `${delay}ms`,
95
+ '--elyri-split-reveal-stagger': `${stagger}ms`,
96
+ '--elyri-split-reveal-duration': `${duration}ms`,
97
+ '--elyri-split-reveal-distance': `${distance}px`,
98
+ } as CSSProperties;
99
+
100
+ const isVisible = prefersReducedMotion || isIntersecting;
101
+
102
+ return (
103
+ <span
104
+ ref={elementRef}
105
+ className={cn('elyri-split-reveal', isVisible && 'elyri-split-reveal--visible', className)}
106
+ style={style}
107
+ >
108
+ {segments.map((segment) =>
109
+ segment.kind === 'space' ? (
110
+ segment.value
111
+ ) : (
112
+ <span key={segment.key} className="elyri-split-reveal__word">
113
+ {segment.units.map((unit) => (
114
+ <span
115
+ key={unit.index}
116
+ className="elyri-split-reveal__unit"
117
+ style={{ '--elyri-split-reveal-index': unit.index } as CSSProperties}
118
+ >
119
+ {unit.char}
120
+ </span>
121
+ ))}
122
+ </span>
123
+ ),
124
+ )}
125
+ </span>
126
+ );
127
+ }
@@ -0,0 +1,2 @@
1
+ export { SplitReveal } from './SplitReveal';
2
+ export type { SplitRevealBy, SplitRevealProps } from './SplitReveal';
@@ -0,0 +1,37 @@
1
+ .elyri-typewriter {
2
+ display: inline;
3
+ white-space: pre-wrap;
4
+ }
5
+
6
+ .elyri-typewriter__text {
7
+ white-space: pre-wrap;
8
+ }
9
+
10
+ /* 块状光标,随 currentColor 取色 */
11
+ .elyri-typewriter__cursor {
12
+ display: inline-block;
13
+ width: 2px;
14
+ height: 1em;
15
+ margin-left: 0.1em;
16
+ border-radius: 1px;
17
+ background: currentColor;
18
+ vertical-align: -0.15em;
19
+ animation: elyri-typewriter-blink 1s step-end infinite;
20
+ }
21
+
22
+ @keyframes elyri-typewriter-blink {
23
+ 0%,
24
+ 100% {
25
+ opacity: 1;
26
+ }
27
+
28
+ 50% {
29
+ opacity: 0;
30
+ }
31
+ }
32
+
33
+ @media (prefers-reduced-motion: reduce) {
34
+ .elyri-typewriter__cursor {
35
+ animation: none;
36
+ }
37
+ }
@@ -0,0 +1,80 @@
1
+ import { useEffect, useState } from 'react';
2
+
3
+ import { cn, usePrefersReducedMotion } from '../../../core';
4
+
5
+ import './Typewriter.css';
6
+
7
+ export interface TypewriterProps {
8
+ /** 需要逐字输出的文本,仅支持纯字符串 */
9
+ children: string;
10
+ className?: string;
11
+ /** 每个字符的输入间隔(毫秒) */
12
+ speed?: number;
13
+ /** 开始输入前的延迟(毫秒) */
14
+ startDelay?: number;
15
+ /** 输入完成后是否循环重播 */
16
+ loop?: boolean;
17
+ /** 循环重播前的停顿(毫秒) */
18
+ loopDelay?: number;
19
+ /** 是否显示闪烁光标 */
20
+ cursor?: boolean;
21
+ }
22
+
23
+ export function Typewriter({
24
+ children,
25
+ className,
26
+ speed = 60,
27
+ startDelay = 200,
28
+ loop = false,
29
+ loopDelay = 1200,
30
+ cursor = true,
31
+ }: TypewriterProps) {
32
+ const text = children;
33
+ const prefersReducedMotion = usePrefersReducedMotion();
34
+ const [count, setCount] = useState(0);
35
+
36
+ useEffect(() => {
37
+ if (prefersReducedMotion || !text) return;
38
+
39
+ let timer: ReturnType<typeof setTimeout>;
40
+ let typed = 0;
41
+
42
+ const typeNext = (wait: number) => {
43
+ timer = setTimeout(() => {
44
+ typed += 1;
45
+ setCount(typed);
46
+
47
+ if (typed < text.length) {
48
+ typeNext(speed);
49
+ return;
50
+ }
51
+
52
+ if (loop) {
53
+ timer = setTimeout(() => {
54
+ typed = 0;
55
+ setCount(0);
56
+ typeNext(speed);
57
+ }, loopDelay);
58
+ }
59
+ }, wait);
60
+ };
61
+
62
+ // 起始延迟结束后归零并开始逐字输入
63
+ timer = setTimeout(() => {
64
+ typed = 0;
65
+ setCount(0);
66
+ typeNext(0);
67
+ }, startDelay);
68
+
69
+ return () => clearTimeout(timer);
70
+ }, [text, speed, startDelay, loop, loopDelay, prefersReducedMotion]);
71
+
72
+ const visible = prefersReducedMotion ? text : text.slice(0, count);
73
+
74
+ return (
75
+ <span className={cn('elyri-typewriter', className)}>
76
+ <span className="elyri-typewriter__text">{visible}</span>
77
+ {cursor && <span className="elyri-typewriter__cursor" aria-hidden="true" />}
78
+ </span>
79
+ );
80
+ }
@@ -0,0 +1,2 @@
1
+ export { Typewriter } from './Typewriter';
2
+ export type { TypewriterProps } from './Typewriter';
@@ -0,0 +1,28 @@
1
+ .elyri-wave-text__word {
2
+ display: inline-block;
3
+ white-space: nowrap;
4
+ }
5
+
6
+ .elyri-wave-text__unit {
7
+ display: inline-block;
8
+ animation: elyri-wave-text-swing var(--elyri-wave-text-speed, 1200ms) var(--elyri-ease-in-out) infinite;
9
+ animation-delay: calc(var(--elyri-wave-text-index) * var(--elyri-wave-text-stagger, 60ms) * -1);
10
+ will-change: transform;
11
+ }
12
+
13
+ @keyframes elyri-wave-text-swing {
14
+ 0%,
15
+ 100% {
16
+ transform: translateY(0);
17
+ }
18
+
19
+ 50% {
20
+ transform: translateY(calc(var(--elyri-wave-text-amplitude, 6px) * -1));
21
+ }
22
+ }
23
+
24
+ @media (prefers-reduced-motion: reduce) {
25
+ .elyri-wave-text__unit {
26
+ animation: none;
27
+ }
28
+ }
@@ -0,0 +1,52 @@
1
+ import { useMemo } from 'react';
2
+ import type { CSSProperties } from 'react';
3
+
4
+ import { cn, splitTextUnits } from '../../../core';
5
+
6
+ import './WaveText.css';
7
+
8
+ export interface WaveTextProps {
9
+ /** 需要起伏的文本,仅支持纯字符串 */
10
+ children: string;
11
+ className?: string;
12
+ /** 振幅(像素) */
13
+ amplitude?: number;
14
+ /** 单个字符一次起伏的时长(毫秒) */
15
+ speed?: number;
16
+ /** 相邻字符的相位差(毫秒),越大波浪越稀疏 */
17
+ stagger?: number;
18
+ }
19
+
20
+ /** 波浪文字:字符按正弦节奏依次上下起伏 */
21
+ export function WaveText({ children, className, amplitude = 6, speed = 1200, stagger = 60 }: WaveTextProps) {
22
+ const segments = useMemo(() => splitTextUnits(children, 'char'), [children]);
23
+
24
+ const style = {
25
+ '--elyri-wave-text-amplitude': `${amplitude}px`,
26
+ '--elyri-wave-text-speed': `${speed}ms`,
27
+ '--elyri-wave-text-stagger': `${stagger}ms`,
28
+ } as CSSProperties;
29
+
30
+ return (
31
+ <span className={cn('elyri-wave-text', className)} style={style} aria-label={children}>
32
+ {segments.map((segment) =>
33
+ segment.kind === 'space' ? (
34
+ segment.value
35
+ ) : (
36
+ <span key={segment.key} className="elyri-wave-text__word">
37
+ {segment.units.map((unit) => (
38
+ <span
39
+ key={unit.index}
40
+ className="elyri-wave-text__unit"
41
+ style={{ '--elyri-wave-text-index': unit.index } as CSSProperties}
42
+ aria-hidden="true"
43
+ >
44
+ {unit.char}
45
+ </span>
46
+ ))}
47
+ </span>
48
+ ),
49
+ )}
50
+ </span>
51
+ );
52
+ }
@@ -0,0 +1,2 @@
1
+ export { WaveText } from './WaveText';
2
+ export type { WaveTextProps } from './WaveText';
@@ -0,0 +1,20 @@
1
+ export { GradientText } from './GradientText';
2
+ export type { GradientTextProps } from './GradientText';
3
+ export { Marquee } from './Marquee';
4
+ export type { MarqueeDirection, MarqueeProps } from './Marquee';
5
+ export { ParticleText } from './ParticleText';
6
+ export type { ParticleTextProps } from './ParticleText';
7
+ export { RotatingText } from './RotatingText';
8
+ export type { RotatingTextProps } from './RotatingText';
9
+ export { ScrambleText } from './ScrambleText';
10
+ export type { ScrambleTextProps, ScrambleTextTrigger } from './ScrambleText';
11
+ export { ScrollMarquee } from './ScrollMarquee';
12
+ export type { ScrollMarqueeDirection, ScrollMarqueeProps } from './ScrollMarquee';
13
+ export { ScrollReveal } from './ScrollReveal';
14
+ export type { ScrollRevealBy, ScrollRevealProps } from './ScrollReveal';
15
+ export { SplitReveal } from './SplitReveal';
16
+ export type { SplitRevealBy, SplitRevealProps } from './SplitReveal';
17
+ export { Typewriter } from './Typewriter';
18
+ export type { TypewriterProps } from './Typewriter';
19
+ export { WaveText } from './WaveText';
20
+ export type { WaveTextProps } from './WaveText';