sparkle-ripple 3.0.0
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.
- package/LICENSE +7 -0
- package/README.md +92 -0
- package/dist/components/base/ripple.d.ts +17 -0
- package/dist/components/base/sparkles_canvas.d.ts +10 -0
- package/dist/components/ripple_container.d.ts +22 -0
- package/dist/index.d.ts +2 -0
- package/dist/ripple/sparkles.d.ts +19 -0
- package/dist/ripple/utils.d.ts +4 -0
- package/dist/sparkle-ripple.css +1 -0
- package/dist/sparkle-ripple.js +393 -0
- package/dist/sparkle-ripple.umd.cjs +1 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2025 Litrain
|
|
2
|
+
|
|
3
|
+
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:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<h1 align="center">✨ sparkle-ripple</h1>
|
|
2
|
+
<p align="center">Bring Material 3(You) Ripple Effect to your <b>React</b> projects!</p>
|
|
3
|
+
<p align="center">Previously, it was named <b>m3-ripple</b>.</p>
|
|
4
|
+
<div align="center">
|
|
5
|
+
|
|
6
|
+
[](https://www.npmjs.com/package/sparkle-ripple)
|
|
7
|
+
[](#)
|
|
8
|
+
[](https://github.com/yuyake-litrain/sparkle-ripple/blob/main/LICENSE)
|
|
9
|
+
[](https://npmtrends.com/sparkle-ripple)
|
|
10
|
+
[](#)
|
|
11
|
+
[](https://github.com/yuyake-litrain/sparkle-ripple/actions/workflows/main.yml)
|
|
12
|
+
</div>
|
|
13
|
+
<div align="center"><a href="https://m3ripple.js.org/"><b>Live Demo</b></a></div><br />
|
|
14
|
+
|
|
15
|
+
<div align="center">
|
|
16
|
+
<video src="https://github.com/user-attachments/assets/5b8cd5e6-5c91-4ca1-bc4d-50d5781a8be9" />
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
- ✨ Ripple Effect with sparkle easily realized on the web
|
|
21
|
+
- 😍 Well-tuned behavior with no faltering
|
|
22
|
+
- 🎨 Highly customizable in terms of ripple color, number of sparkles, clarity, etc.
|
|
23
|
+
- ⚡ High speed drawing for Sparkles by canvas
|
|
24
|
+
- ✅ Ripple effect in Material 2 is also supported
|
|
25
|
+
|
|
26
|
+
## Getting Started
|
|
27
|
+
### Install
|
|
28
|
+
#### Bun
|
|
29
|
+
```bash
|
|
30
|
+
bun install sparkle-ripple
|
|
31
|
+
```
|
|
32
|
+
#### Others
|
|
33
|
+
<details>
|
|
34
|
+
<summary>npm</summary>
|
|
35
|
+
<pre>npm i sparkle-ripple</pre>
|
|
36
|
+
</details>
|
|
37
|
+
<details>
|
|
38
|
+
<summary>pnpm</summary>
|
|
39
|
+
<pre>pnpm add sparkle-ripple</pre>
|
|
40
|
+
</details>
|
|
41
|
+
<details>
|
|
42
|
+
<summary>Yarn</summary>
|
|
43
|
+
<pre>yarn add sparkle-ripple</pre>
|
|
44
|
+
</details>
|
|
45
|
+
|
|
46
|
+
### Use
|
|
47
|
+
Import `<RippleContainer>` component(by default, it's rendered as `<div />`) and set props.
|
|
48
|
+
|
|
49
|
+
#### Example
|
|
50
|
+
```tsx
|
|
51
|
+
import { RippleContainer } from 'sparkle-ripple'; //import it
|
|
52
|
+
import 'sparkle-ripple/css' // import css
|
|
53
|
+
|
|
54
|
+
import styles from './some_css_file.module.css';
|
|
55
|
+
|
|
56
|
+
const YourComponent = () => {
|
|
57
|
+
return (
|
|
58
|
+
<RippleContainer
|
|
59
|
+
as = 'div'
|
|
60
|
+
isMaterial3 = {true}
|
|
61
|
+
beforeRippleFn = {(event) =>{}}
|
|
62
|
+
className = {styles.rippleContainer}
|
|
63
|
+
rippleColor = "hsla(29,81%,84%,0.15)"
|
|
64
|
+
sparklesColorRGB = "255 255 255"
|
|
65
|
+
opacity_level1 = "0.4"
|
|
66
|
+
opacity_level2 = "0.1"
|
|
67
|
+
sparklesMaxCount = 2048
|
|
68
|
+
>
|
|
69
|
+
<div className={styles.children} />
|
|
70
|
+
</RippleContainer>
|
|
71
|
+
);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default YourComponent;
|
|
75
|
+
```
|
|
76
|
+
<div align="center">
|
|
77
|
+
|
|
78
|
+
|Property|optional|explanation|default|type|
|
|
79
|
+
|----|----|----|----|----|
|
|
80
|
+
|`as`|yes|What element is RippleContainer rendered as.|`"div"`|`ElementType`|
|
|
81
|
+
|`isMaterial3`|yes|Whether to use ripple of Material 3.|`true`|`boolean`|
|
|
82
|
+
|`beforeRippleFn`|yes|A function to be executed when a click occurs and just before the ripple is displayed (used for example to display a button shadow).|`()=>{}`|`(event: React.MouseEvent \| React.TouchEvent) => void`|
|
|
83
|
+
|`className`|yes|Since RippleContainer is rendered as the element which is selected by `as` property, this is the ClassName of that element.|`""`|`string`|
|
|
84
|
+
|`children`|yes|Child Elements of RippleContainer.|`undefined`|`ReactNode`|
|
|
85
|
+
|`rippleColor`|yes|Ripple Effect Colors. If transparency is not specified, the overlap will not be visible when multiple clicks are made.|`"#ffffff35"`|`string`|
|
|
86
|
+
|`sparklesColorRGB`|yes|Specify sparkle color as space-separated RGB. Transparency cannot be specified.|`"255 255 255"`|`string`|
|
|
87
|
+
|`opacity_level1`|yes|Transparency just before the sparkle disappears. \*The transparency when initially displayed is calculated by the current progress of the Ripple Effect.|`"0.2"`|`string`|
|
|
88
|
+
|`opacity_level2`|yes|Transparency just before Sparkles disappear.Set after `opacity_level1`.|`"0.1"`|`string`|
|
|
89
|
+
|`sparklesMaxCount`|yes|Total amount of dots representing sparkle.|`2048`|`number`|
|
|
90
|
+
|Other Properties|yes|You can set other properties to add to the rendered element. e.g., `href` attribute of the RippleContainer whose rendered as `<a></a>` element.|-|-|
|
|
91
|
+
|
|
92
|
+
</div>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
declare const Ripple: import('react').MemoExoticComponent<({ ripple_width, offsetX, offsetY, isMaterial3, rippleColor, rippleTime, time, tobeDeleted, waitingTime, deleteRipple, sparklesColorRGB, opacity_level1, opacity_level2, sparklesMaxCount, }: {
|
|
2
|
+
ripple_width: number;
|
|
3
|
+
offsetX: number;
|
|
4
|
+
offsetY: number;
|
|
5
|
+
isMaterial3?: boolean;
|
|
6
|
+
rippleColor?: string;
|
|
7
|
+
rippleTime?: number;
|
|
8
|
+
waitingTime?: number;
|
|
9
|
+
time: number;
|
|
10
|
+
tobeDeleted: boolean;
|
|
11
|
+
sparklesColorRGB?: string;
|
|
12
|
+
opacity_level1?: string;
|
|
13
|
+
opacity_level2?: string;
|
|
14
|
+
sparklesMaxCount?: number;
|
|
15
|
+
deleteRipple: (createdTime: number) => void;
|
|
16
|
+
}) => import("react/jsx-runtime").JSX.Element>;
|
|
17
|
+
export default Ripple;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
declare const SparklesCanvas: import('react').MemoExoticComponent<({ ripple_width, rippleRef, sparklesColorRGB, opacity_level1, opacity_level2, sparklesMaxCount, }: {
|
|
3
|
+
ripple_width: number;
|
|
4
|
+
rippleRef: RefObject<HTMLSpanElement | null>;
|
|
5
|
+
sparklesColorRGB?: string;
|
|
6
|
+
sparklesMaxCount?: number;
|
|
7
|
+
opacity_level1?: string;
|
|
8
|
+
opacity_level2?: string;
|
|
9
|
+
}) => import("react/jsx-runtime").JSX.Element>;
|
|
10
|
+
export default SparklesCanvas;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef, ElementType, ReactNode } from 'react';
|
|
2
|
+
export type RippleObj = {
|
|
3
|
+
ripple_width: number;
|
|
4
|
+
offsetY: number;
|
|
5
|
+
offsetX: number;
|
|
6
|
+
time: number;
|
|
7
|
+
isMaterial3: boolean;
|
|
8
|
+
tobeDeleted: boolean;
|
|
9
|
+
};
|
|
10
|
+
type Props<T extends ElementType = 'div'> = Omit<ComponentPropsWithoutRef<T>, 'as'> & {
|
|
11
|
+
as?: T;
|
|
12
|
+
isMaterial3?: boolean;
|
|
13
|
+
beforeRippleFn?: (event: React.MouseEvent | React.TouchEvent) => void;
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
rippleColor?: string;
|
|
16
|
+
sparklesColorRGB?: string;
|
|
17
|
+
opacity_level1?: string;
|
|
18
|
+
opacity_level2?: string;
|
|
19
|
+
sparklesMaxCount?: number;
|
|
20
|
+
};
|
|
21
|
+
declare const RippleContainer: <T extends ElementType = "div">({ as, isMaterial3, beforeRippleFn, rippleColor, sparklesColorRGB, opacity_level1, opacity_level2, sparklesMaxCount, className, children, onMouseDown, onTouchStart, onTouchMove, onMouseUp, onMouseLeave, onTouchEnd, onTouchCancel, ...rest }: Props<T>) => import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export default RippleContainer;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare class Sparkle {
|
|
2
|
+
xpos: number;
|
|
3
|
+
ypos: number;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
constructor(xpos: number, ypos: number, width: number, height: number);
|
|
7
|
+
}
|
|
8
|
+
export declare class Sparkles {
|
|
9
|
+
sparkles: Sparkle[];
|
|
10
|
+
color: string;
|
|
11
|
+
constructor(sparkles: Sparkle[], color: string);
|
|
12
|
+
}
|
|
13
|
+
type SparklesColor = {
|
|
14
|
+
rgb: string;
|
|
15
|
+
opacity_level1: string;
|
|
16
|
+
opacity_level2: string;
|
|
17
|
+
};
|
|
18
|
+
export declare function drawSparkles(sparkle_collection: Sparkles[], context: CanvasRenderingContext2D, ripple: HTMLSpanElement, ripple_width: number, sparklesColor?: SparklesColor, convLevel?: number, spaklesMaxCount?: number): void;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function calcRippleWidth(offsetX: number | null, offsetY: number | null, clientWidth: number | null, clientHeight: number | null): number | undefined;
|
|
2
|
+
export declare const offsetXFromCurrent: (rect: DOMRect, clientX: number) => number;
|
|
3
|
+
export declare const offsetYFromCurrent: (rect: DOMRect, clientY: number) => number;
|
|
4
|
+
export declare const rippleElemWidthOriginal: (rect: DOMRect, clientX: number, clientY: number, clientWidth: number, clientHeight: number) => number | undefined;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
._ripple_17x6w_1{position:absolute;border-radius:100%;z-index:-1;transform:translate(-50%) translateY(-50%);pointer-events:none;display:flex;align-items:center;justify-content:center;-webkit-user-select:none;user-select:none}._sparkles_canvas_qe9de_1{position:absolute;-webkit-user-select:none;user-select:none}._rippleContainer_qv2ad_1{overflow:hidden;position:relative;z-index:0}
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { jsx as k, jsxs as K } from "react/jsx-runtime";
|
|
2
|
+
import { memo as X, useRef as p, useEffect as B, useState as Y, useCallback as j } from "react";
|
|
3
|
+
function E(t) {
|
|
4
|
+
var e, o, n = "";
|
|
5
|
+
if (typeof t == "string" || typeof t == "number") n += t;
|
|
6
|
+
else if (typeof t == "object") if (Array.isArray(t)) {
|
|
7
|
+
var s = t.length;
|
|
8
|
+
for (e = 0; e < s; e++) t[e] && (o = E(t[e])) && (n && (n += " "), n += o);
|
|
9
|
+
} else for (o in t) t[o] && (n && (n += " "), n += o);
|
|
10
|
+
return n;
|
|
11
|
+
}
|
|
12
|
+
function q() {
|
|
13
|
+
for (var t, e, o = 0, n = "", s = arguments.length; o < s; o++) (t = arguments[o]) && (e = E(t)) && (n && (n += " "), n += e);
|
|
14
|
+
return n;
|
|
15
|
+
}
|
|
16
|
+
function Q(t, e, o, n) {
|
|
17
|
+
if (!t || !e || !o || !n) return;
|
|
18
|
+
const s = t, a = o - t, i = e, c = n - e;
|
|
19
|
+
let u, l;
|
|
20
|
+
return s >= a ? u = s : u = a, i >= c ? l = i : l = c, Math.sqrt(
|
|
21
|
+
u ** 2 + l ** 2
|
|
22
|
+
) * 2;
|
|
23
|
+
}
|
|
24
|
+
const D = (t, e) => e - t.left, O = (t, e) => e - t.top, N = (t, e, o, n, s) => Q(
|
|
25
|
+
D(t, e),
|
|
26
|
+
O(t, o),
|
|
27
|
+
n,
|
|
28
|
+
s
|
|
29
|
+
), Z = "_ripple_17x6w_1", G = {
|
|
30
|
+
ripple: Z
|
|
31
|
+
};
|
|
32
|
+
class L {
|
|
33
|
+
xpos;
|
|
34
|
+
ypos;
|
|
35
|
+
width;
|
|
36
|
+
height;
|
|
37
|
+
constructor(e, o, n, s) {
|
|
38
|
+
this.xpos = e, this.ypos = o, this.width = n, this.height = s;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
class _ {
|
|
42
|
+
sparkles;
|
|
43
|
+
color;
|
|
44
|
+
constructor(e, o) {
|
|
45
|
+
this.sparkles = e, this.color = o;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function v(t, e, o, n) {
|
|
49
|
+
const s = t * (Math.PI / 180), a = Math.cos(s) * n + (Math.floor(Math.random() * (e / o)) - Math.floor(Math.random() * (e / o))), i = Math.sin(s) * n + (Math.floor(Math.random() * (e / o)) - Math.floor(Math.random() * (e / o)));
|
|
50
|
+
return { xpos: a, ypos: i };
|
|
51
|
+
}
|
|
52
|
+
function tt(t, e, o) {
|
|
53
|
+
return `rgb(${o.rgb} / ${Math.abs(
|
|
54
|
+
(Math.random() - Math.random()) * 0.3 + (1 - t / e) - (1 - 0.6)
|
|
55
|
+
).toFixed(2)})`;
|
|
56
|
+
}
|
|
57
|
+
function I(t, e, o, n, s = {
|
|
58
|
+
rgb: "255 255 255",
|
|
59
|
+
opacity_level1: "0.2",
|
|
60
|
+
opacity_level2: "0.1"
|
|
61
|
+
}, a = 6, i = 2048) {
|
|
62
|
+
const c = o.clientWidth / 2, u = o.clientWidth / 2.6;
|
|
63
|
+
if (e.clearRect(0, 0, n, n), c === 0 || a === 0)
|
|
64
|
+
return;
|
|
65
|
+
const l = [];
|
|
66
|
+
let d = o.clientWidth;
|
|
67
|
+
d > i && (d = i);
|
|
68
|
+
const x = [];
|
|
69
|
+
for (let g = 0; g < d; g++)
|
|
70
|
+
x.push(Math.floor(Math.random() * 360));
|
|
71
|
+
for (const g of x) {
|
|
72
|
+
const f = v(
|
|
73
|
+
g,
|
|
74
|
+
c,
|
|
75
|
+
a,
|
|
76
|
+
u
|
|
77
|
+
);
|
|
78
|
+
l.push(new L(f.xpos, f.ypos, 1, 1));
|
|
79
|
+
}
|
|
80
|
+
const S = tt(
|
|
81
|
+
c,
|
|
82
|
+
n,
|
|
83
|
+
s
|
|
84
|
+
);
|
|
85
|
+
t.push(new _(l, S)), t.length > 5 && (t.splice(0, 1), t[0].color = `rgb(${s.rgb} / ${s.opacity_level1})`, t[1].color = `rgb(${s.rgb} / ${s.opacity_level2})`);
|
|
86
|
+
for (const g of t) {
|
|
87
|
+
e.fillStyle = g.color;
|
|
88
|
+
for (const f of g.sparkles)
|
|
89
|
+
e.fillRect(
|
|
90
|
+
n / 2 + f.xpos,
|
|
91
|
+
n / 2 + f.ypos,
|
|
92
|
+
f.width,
|
|
93
|
+
f.height
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
window.requestAnimationFrame(
|
|
97
|
+
() => I(t, e, o, n)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const rt = "_sparkles_canvas_qe9de_1", nt = {
|
|
101
|
+
sparkles_canvas: rt
|
|
102
|
+
}, et = X(
|
|
103
|
+
({
|
|
104
|
+
ripple_width: t,
|
|
105
|
+
rippleRef: e,
|
|
106
|
+
sparklesColorRGB: o = "255 255 255",
|
|
107
|
+
opacity_level1: n = "0.2",
|
|
108
|
+
opacity_level2: s = "0.1",
|
|
109
|
+
sparklesMaxCount: a = 2048
|
|
110
|
+
}) => {
|
|
111
|
+
const i = p(null), c = p(null);
|
|
112
|
+
return B(() => {
|
|
113
|
+
const u = window.devicePixelRatio;
|
|
114
|
+
c.current && (c.current.width = Math.floor(t * u), c.current.height = Math.floor(t * u));
|
|
115
|
+
const l = c.current?.getContext("2d");
|
|
116
|
+
if (!l)
|
|
117
|
+
return;
|
|
118
|
+
const d = [];
|
|
119
|
+
return l.scale(u, u), i.current = requestAnimationFrame(() => {
|
|
120
|
+
e.current && I(
|
|
121
|
+
d,
|
|
122
|
+
l,
|
|
123
|
+
e.current,
|
|
124
|
+
t,
|
|
125
|
+
{
|
|
126
|
+
rgb: o,
|
|
127
|
+
opacity_level1: n,
|
|
128
|
+
opacity_level2: s
|
|
129
|
+
},
|
|
130
|
+
6,
|
|
131
|
+
a
|
|
132
|
+
);
|
|
133
|
+
}), c.current?.animate(
|
|
134
|
+
{
|
|
135
|
+
opacity: [1, 0]
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
fill: "forwards",
|
|
139
|
+
duration: 600
|
|
140
|
+
}
|
|
141
|
+
), () => {
|
|
142
|
+
i.current && cancelAnimationFrame(i.current);
|
|
143
|
+
};
|
|
144
|
+
}, [
|
|
145
|
+
t,
|
|
146
|
+
e,
|
|
147
|
+
o,
|
|
148
|
+
a,
|
|
149
|
+
n,
|
|
150
|
+
s
|
|
151
|
+
]), /* @__PURE__ */ k(
|
|
152
|
+
"canvas",
|
|
153
|
+
{
|
|
154
|
+
ref: c,
|
|
155
|
+
style: {
|
|
156
|
+
width: `${t}px`,
|
|
157
|
+
height: `${t}px`
|
|
158
|
+
},
|
|
159
|
+
className: q("sparkles_canvas", nt.sparkles_canvas)
|
|
160
|
+
}
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
), ot = X(
|
|
164
|
+
({
|
|
165
|
+
ripple_width: t,
|
|
166
|
+
offsetX: e,
|
|
167
|
+
offsetY: o,
|
|
168
|
+
isMaterial3: n = !0,
|
|
169
|
+
rippleColor: s = "#ffffff35",
|
|
170
|
+
rippleTime: a = 500,
|
|
171
|
+
time: i,
|
|
172
|
+
tobeDeleted: c = !1,
|
|
173
|
+
// waiting time for ripple animation
|
|
174
|
+
waitingTime: u = 290,
|
|
175
|
+
deleteRipple: l,
|
|
176
|
+
sparklesColorRGB: d = "255 255 255",
|
|
177
|
+
opacity_level1: x = "0.2",
|
|
178
|
+
opacity_level2: S = "0.1",
|
|
179
|
+
sparklesMaxCount: g = 2048
|
|
180
|
+
}) => {
|
|
181
|
+
const f = p(null), y = p(null), C = p(!1), [F, P] = Y(!1), $ = j(async () => {
|
|
182
|
+
if (f.current && y.current) {
|
|
183
|
+
if (y.current.currentTime < u) {
|
|
184
|
+
const M = a - y.current.currentTime - u;
|
|
185
|
+
await new Promise((W) => {
|
|
186
|
+
setTimeout(W, M);
|
|
187
|
+
});
|
|
188
|
+
} else
|
|
189
|
+
await y.current.finished;
|
|
190
|
+
if (!f.current)
|
|
191
|
+
return;
|
|
192
|
+
await f.current.animate(
|
|
193
|
+
{
|
|
194
|
+
opacity: [1, 0]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
fill: "forwards",
|
|
198
|
+
duration: 200,
|
|
199
|
+
easing: "cubic-bezier(0.11, 0, 0.5, 0)"
|
|
200
|
+
}
|
|
201
|
+
).finished;
|
|
202
|
+
}
|
|
203
|
+
l(i);
|
|
204
|
+
}, [l, n, a, i, u]);
|
|
205
|
+
return B(() => {
|
|
206
|
+
c || (y.current = f.current?.animate(
|
|
207
|
+
{
|
|
208
|
+
width: n ? [`${t / 6}px`, `${t}px`] : [`${t / 3}px`, `${t}px`],
|
|
209
|
+
height: n ? [`${t / 6}px`, `${t}px`] : [`${t / 3}px`, `${t}px`],
|
|
210
|
+
background: n ? [
|
|
211
|
+
`radial-gradient(circle closest-side, ${s} 0%, transparent)`,
|
|
212
|
+
`radial-gradient(circle closest-side, ${s} 80%, transparent)`
|
|
213
|
+
] : [s, s]
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
fill: "forwards",
|
|
217
|
+
duration: n ? 400 : a,
|
|
218
|
+
easing: n ? "cubic-bezier(0,0.49,0,1)" : "cubic-bezier(0.1, 0.8, 1, 0.80)"
|
|
219
|
+
}
|
|
220
|
+
), (async () => (await y.current?.ready, P(!0)))());
|
|
221
|
+
}, [n, a, s, t, c]), B(() => {
|
|
222
|
+
c && (C.current || (C.current = !0, $()));
|
|
223
|
+
}, [c, $]), /* @__PURE__ */ k(
|
|
224
|
+
"span",
|
|
225
|
+
{
|
|
226
|
+
className: q("ripple", G.ripple),
|
|
227
|
+
style: {
|
|
228
|
+
width: t,
|
|
229
|
+
height: t,
|
|
230
|
+
left: e,
|
|
231
|
+
top: o
|
|
232
|
+
},
|
|
233
|
+
ref: f,
|
|
234
|
+
children: n && F && /* @__PURE__ */ k(
|
|
235
|
+
et,
|
|
236
|
+
{
|
|
237
|
+
ripple_width: t,
|
|
238
|
+
rippleRef: f,
|
|
239
|
+
sparklesColorRGB: d,
|
|
240
|
+
opacity_level1: x,
|
|
241
|
+
opacity_level2: S,
|
|
242
|
+
sparklesMaxCount: g
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
), st = "_rippleContainer_qv2ad_1", ct = {
|
|
249
|
+
rippleContainer: st
|
|
250
|
+
}, ft = ({
|
|
251
|
+
as: t,
|
|
252
|
+
isMaterial3: e = !0,
|
|
253
|
+
beforeRippleFn: o,
|
|
254
|
+
rippleColor: n = "#ffffff35",
|
|
255
|
+
sparklesColorRGB: s = "255 255 255",
|
|
256
|
+
opacity_level1: a = "0.2",
|
|
257
|
+
opacity_level2: i = "0.1",
|
|
258
|
+
sparklesMaxCount: c = 2048,
|
|
259
|
+
className: u,
|
|
260
|
+
children: l,
|
|
261
|
+
onMouseDown: d,
|
|
262
|
+
onTouchStart: x,
|
|
263
|
+
onTouchMove: S,
|
|
264
|
+
onMouseUp: g,
|
|
265
|
+
onMouseLeave: f,
|
|
266
|
+
onTouchEnd: y,
|
|
267
|
+
onTouchCancel: C,
|
|
268
|
+
...F
|
|
269
|
+
}) => {
|
|
270
|
+
const [P, $] = Y([]), b = p(!1), M = p(!1);
|
|
271
|
+
function W(r) {
|
|
272
|
+
if (M.current)
|
|
273
|
+
return;
|
|
274
|
+
const h = N(
|
|
275
|
+
r.currentTarget.getBoundingClientRect(),
|
|
276
|
+
r.clientX,
|
|
277
|
+
r.clientY,
|
|
278
|
+
r.currentTarget.clientWidth,
|
|
279
|
+
r.currentTarget.clientHeight
|
|
280
|
+
), m = D(
|
|
281
|
+
r.currentTarget.getBoundingClientRect(),
|
|
282
|
+
r.clientX
|
|
283
|
+
), T = O(
|
|
284
|
+
r.currentTarget.getBoundingClientRect(),
|
|
285
|
+
r.clientY
|
|
286
|
+
);
|
|
287
|
+
o && o(r), z(h, m, T);
|
|
288
|
+
}
|
|
289
|
+
function z(r, h, m, T = !1) {
|
|
290
|
+
if (M.current && !T || !r)
|
|
291
|
+
return;
|
|
292
|
+
const R = r / 0.8, A = (/* @__PURE__ */ new Date()).getTime();
|
|
293
|
+
$((J) => [
|
|
294
|
+
...J,
|
|
295
|
+
{
|
|
296
|
+
ripple_width: R,
|
|
297
|
+
offsetX: h,
|
|
298
|
+
offsetY: m,
|
|
299
|
+
time: A,
|
|
300
|
+
isMaterial3: e,
|
|
301
|
+
tobeDeleted: !1
|
|
302
|
+
}
|
|
303
|
+
]);
|
|
304
|
+
}
|
|
305
|
+
async function w(r) {
|
|
306
|
+
(r.type === "touchend" || r.type === "touchcancel") && await new Promise((h) => setTimeout(h, 100)), $((h) => h.length === 0 ? [] : h.map((m) => ({ ...m, tobeDeleted: !0 }))), b.current = !1;
|
|
307
|
+
}
|
|
308
|
+
const H = j((r) => {
|
|
309
|
+
$((h) => h.filter((m) => m.time !== r));
|
|
310
|
+
}, []), U = async (r) => {
|
|
311
|
+
const h = N(
|
|
312
|
+
r.currentTarget.getBoundingClientRect(),
|
|
313
|
+
r.changedTouches[0].clientX,
|
|
314
|
+
r.changedTouches[0].clientY,
|
|
315
|
+
r.currentTarget.clientWidth,
|
|
316
|
+
r.currentTarget.clientHeight
|
|
317
|
+
), m = D(
|
|
318
|
+
r.currentTarget.getBoundingClientRect(),
|
|
319
|
+
r.changedTouches[0].clientX
|
|
320
|
+
), T = O(
|
|
321
|
+
r.currentTarget.getBoundingClientRect(),
|
|
322
|
+
r.changedTouches[0].clientY
|
|
323
|
+
);
|
|
324
|
+
M.current = !0, await new Promise((R) => {
|
|
325
|
+
setTimeout(R, 100);
|
|
326
|
+
}), setTimeout(() => {
|
|
327
|
+
M.current = !1;
|
|
328
|
+
}, 1e3), b.current || (o && o(r), z(h, m, T, !0)), b.current = !1;
|
|
329
|
+
}, V = () => {
|
|
330
|
+
b.current = !0;
|
|
331
|
+
};
|
|
332
|
+
return /* @__PURE__ */ K(
|
|
333
|
+
t ?? "div",
|
|
334
|
+
{
|
|
335
|
+
...F,
|
|
336
|
+
className: q(ct.rippleContainer, u ?? ""),
|
|
337
|
+
onMouseDown: (r) => {
|
|
338
|
+
W(r), d?.(r);
|
|
339
|
+
},
|
|
340
|
+
onTouchStart: (r) => {
|
|
341
|
+
U(r), x?.(r);
|
|
342
|
+
},
|
|
343
|
+
onTouchMove: (r) => {
|
|
344
|
+
V(), S?.(r);
|
|
345
|
+
},
|
|
346
|
+
onTouchEnd: (r) => {
|
|
347
|
+
w(r), y?.(r);
|
|
348
|
+
},
|
|
349
|
+
onMouseUp: (r) => {
|
|
350
|
+
w(r), g?.(r);
|
|
351
|
+
},
|
|
352
|
+
onMouseLeave: (r) => {
|
|
353
|
+
w(r), f?.(r);
|
|
354
|
+
},
|
|
355
|
+
onTouchCancel: (r) => {
|
|
356
|
+
w(r), C?.(r);
|
|
357
|
+
},
|
|
358
|
+
children: [
|
|
359
|
+
P.map(
|
|
360
|
+
({
|
|
361
|
+
ripple_width: r,
|
|
362
|
+
offsetX: h,
|
|
363
|
+
offsetY: m,
|
|
364
|
+
isMaterial3: T,
|
|
365
|
+
time: R,
|
|
366
|
+
tobeDeleted: A
|
|
367
|
+
}) => /* @__PURE__ */ k(
|
|
368
|
+
ot,
|
|
369
|
+
{
|
|
370
|
+
time: R,
|
|
371
|
+
ripple_width: r,
|
|
372
|
+
offsetX: h,
|
|
373
|
+
offsetY: m,
|
|
374
|
+
isMaterial3: T,
|
|
375
|
+
tobeDeleted: A,
|
|
376
|
+
deleteRipple: H,
|
|
377
|
+
rippleColor: n,
|
|
378
|
+
sparklesColorRGB: s,
|
|
379
|
+
opacity_level1: a,
|
|
380
|
+
opacity_level2: i,
|
|
381
|
+
sparklesMaxCount: c
|
|
382
|
+
},
|
|
383
|
+
R
|
|
384
|
+
)
|
|
385
|
+
),
|
|
386
|
+
l
|
|
387
|
+
]
|
|
388
|
+
}
|
|
389
|
+
);
|
|
390
|
+
};
|
|
391
|
+
export {
|
|
392
|
+
ft as RippleContainer
|
|
393
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(T,y){typeof exports=="object"&&typeof module<"u"?y(exports,require("react/jsx-runtime"),require("react")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react"],y):(T=typeof globalThis<"u"?globalThis:T||self,y(T["sparkle-ripple"]={},T.ReactJSXRuntime,T.React))})(this,(function(T,y,i){"use strict";function D(t){var r,s,e="";if(typeof t=="string"||typeof t=="number")e+=t;else if(typeof t=="object")if(Array.isArray(t)){var o=t.length;for(r=0;r<o;r++)t[r]&&(s=D(t[r]))&&(e&&(e+=" "),e+=s)}else for(s in t)t[s]&&(e&&(e+=" "),e+=s);return e}function F(){for(var t,r,s=0,e="",o=arguments.length;s<o;s++)(t=arguments[s])&&(r=D(t))&&(e&&(e+=" "),e+=r);return e}function N(t,r,s,e){if(!t||!r||!s||!e)return;const o=t,a=s-t,u=r,c=e-r;let f,h;return o>=a?f=o:f=a,u>=c?h=u:h=c,Math.sqrt(f**2+h**2)*2}const W=(t,r)=>r-t.left,A=(t,r)=>r-t.top,X=(t,r,s,e,o)=>N(W(t,r),A(t,s),e,o),Y={ripple:"_ripple_17x6w_1"};class I{xpos;ypos;width;height;constructor(r,s,e,o){this.xpos=r,this.ypos=s,this.width=e,this.height=o}}class H{sparkles;color;constructor(r,s){this.sparkles=r,this.color=s}}function J(t,r,s,e){const o=t*(Math.PI/180),a=Math.cos(o)*e+(Math.floor(Math.random()*(r/s))-Math.floor(Math.random()*(r/s))),u=Math.sin(o)*e+(Math.floor(Math.random()*(r/s))-Math.floor(Math.random()*(r/s)));return{xpos:a,ypos:u}}function U(t,r,s){return`rgb(${s.rgb} / ${Math.abs((Math.random()-Math.random())*.3+(1-t/r)-(1-.6)).toFixed(2)})`}function z(t,r,s,e,o={rgb:"255 255 255",opacity_level1:"0.2",opacity_level2:"0.1"},a=6,u=2048){const c=s.clientWidth/2,f=s.clientWidth/2.6;if(r.clearRect(0,0,e,e),c===0||a===0)return;const h=[];let p=s.clientWidth;p>u&&(p=u);const b=[];for(let g=0;g<p;g++)b.push(Math.floor(Math.random()*360));for(const g of b){const l=J(g,c,a,f);h.push(new I(l.xpos,l.ypos,1,1))}const k=U(c,e,o);t.push(new H(h,k)),t.length>5&&(t.splice(0,1),t[0].color=`rgb(${o.rgb} / ${o.opacity_level1})`,t[1].color=`rgb(${o.rgb} / ${o.opacity_level2})`);for(const g of t){r.fillStyle=g.color;for(const l of g.sparkles)r.fillRect(e/2+l.xpos,e/2+l.ypos,l.width,l.height)}window.requestAnimationFrame(()=>z(t,r,s,e))}const V={sparkles_canvas:"_sparkles_canvas_qe9de_1"},K=i.memo(({ripple_width:t,rippleRef:r,sparklesColorRGB:s="255 255 255",opacity_level1:e="0.2",opacity_level2:o="0.1",sparklesMaxCount:a=2048})=>{const u=i.useRef(null),c=i.useRef(null);return i.useEffect(()=>{const f=window.devicePixelRatio;c.current&&(c.current.width=Math.floor(t*f),c.current.height=Math.floor(t*f));const h=c.current?.getContext("2d");if(!h)return;const p=[];return h.scale(f,f),u.current=requestAnimationFrame(()=>{r.current&&z(p,h,r.current,t,{rgb:s,opacity_level1:e,opacity_level2:o},6,a)}),c.current?.animate({opacity:[1,0]},{fill:"forwards",duration:600}),()=>{u.current&&cancelAnimationFrame(u.current)}},[t,r,s,a,e,o]),y.jsx("canvas",{ref:c,style:{width:`${t}px`,height:`${t}px`},className:F("sparkles_canvas",V.sparkles_canvas)})}),Q=i.memo(({ripple_width:t,offsetX:r,offsetY:s,isMaterial3:e=!0,rippleColor:o="#ffffff35",rippleTime:a=500,time:u,tobeDeleted:c=!1,waitingTime:f=290,deleteRipple:h,sparklesColorRGB:p="255 255 255",opacity_level1:b="0.2",opacity_level2:k="0.1",sparklesMaxCount:g=2048})=>{const l=i.useRef(null),x=i.useRef(null),w=i.useRef(!1),[q,O]=i.useState(!1),$=i.useCallback(async()=>{if(l.current&&x.current){if(x.current.currentTime<f){const M=a-x.current.currentTime-f;await new Promise(j=>{setTimeout(j,M)})}else await x.current.finished;if(!l.current)return;await l.current.animate({opacity:[1,0]},{fill:"forwards",duration:200,easing:"cubic-bezier(0.11, 0, 0.5, 0)"}).finished}h(u)},[h,e,a,u,f]);return i.useEffect(()=>{c||(x.current=l.current?.animate({width:e?[`${t/6}px`,`${t}px`]:[`${t/3}px`,`${t}px`],height:e?[`${t/6}px`,`${t}px`]:[`${t/3}px`,`${t}px`],background:e?[`radial-gradient(circle closest-side, ${o} 0%, transparent)`,`radial-gradient(circle closest-side, ${o} 80%, transparent)`]:[o,o]},{fill:"forwards",duration:e?400:a,easing:e?"cubic-bezier(0,0.49,0,1)":"cubic-bezier(0.1, 0.8, 1, 0.80)"}),(async()=>(await x.current?.ready,O(!0)))())},[e,a,o,t,c]),i.useEffect(()=>{c&&(w.current||(w.current=!0,$()))},[c,$]),y.jsx("span",{className:F("ripple",Y.ripple),style:{width:t,height:t,left:r,top:s},ref:l,children:e&&q&&y.jsx(K,{ripple_width:t,rippleRef:l,sparklesColorRGB:p,opacity_level1:b,opacity_level2:k,sparklesMaxCount:g})})}),Z={rippleContainer:"_rippleContainer_qv2ad_1"},G=({as:t,isMaterial3:r=!0,beforeRippleFn:s,rippleColor:e="#ffffff35",sparklesColorRGB:o="255 255 255",opacity_level1:a="0.2",opacity_level2:u="0.1",sparklesMaxCount:c=2048,className:f,children:h,onMouseDown:p,onTouchStart:b,onTouchMove:k,onMouseUp:g,onMouseLeave:l,onTouchEnd:x,onTouchCancel:w,...q})=>{const[O,$]=i.useState([]),S=i.useRef(!1),M=i.useRef(!1);function j(n){if(M.current)return;const d=X(n.currentTarget.getBoundingClientRect(),n.clientX,n.clientY,n.currentTarget.clientWidth,n.currentTarget.clientHeight),m=W(n.currentTarget.getBoundingClientRect(),n.clientX),R=A(n.currentTarget.getBoundingClientRect(),n.clientY);s&&s(n),E(d,m,R)}function E(n,d,m,R=!1){if(M.current&&!R||!n)return;const C=n/.8,B=new Date().getTime();$(nt=>[...nt,{ripple_width:C,offsetX:d,offsetY:m,time:B,isMaterial3:r,tobeDeleted:!1}])}async function P(n){(n.type==="touchend"||n.type==="touchcancel")&&await new Promise(d=>setTimeout(d,100)),$(d=>d.length===0?[]:d.map(m=>({...m,tobeDeleted:!0}))),S.current=!1}const L=i.useCallback(n=>{$(d=>d.filter(m=>m.time!==n))},[]),_=async n=>{const d=X(n.currentTarget.getBoundingClientRect(),n.changedTouches[0].clientX,n.changedTouches[0].clientY,n.currentTarget.clientWidth,n.currentTarget.clientHeight),m=W(n.currentTarget.getBoundingClientRect(),n.changedTouches[0].clientX),R=A(n.currentTarget.getBoundingClientRect(),n.changedTouches[0].clientY);M.current=!0,await new Promise(C=>{setTimeout(C,100)}),setTimeout(()=>{M.current=!1},1e3),S.current||(s&&s(n),E(d,m,R,!0)),S.current=!1},v=()=>{S.current=!0},tt=t??"div";return y.jsxs(tt,{...q,className:F(Z.rippleContainer,f??""),onMouseDown:n=>{j(n),p?.(n)},onTouchStart:n=>{_(n),b?.(n)},onTouchMove:n=>{v(),k?.(n)},onTouchEnd:n=>{P(n),x?.(n)},onMouseUp:n=>{P(n),g?.(n)},onMouseLeave:n=>{P(n),l?.(n)},onTouchCancel:n=>{P(n),w?.(n)},children:[O.map(({ripple_width:n,offsetX:d,offsetY:m,isMaterial3:R,time:C,tobeDeleted:B})=>y.jsx(Q,{time:C,ripple_width:n,offsetX:d,offsetY:m,isMaterial3:R,tobeDeleted:B,deleteRipple:L,rippleColor:e,sparklesColorRGB:o,opacity_level1:a,opacity_level2:u,sparklesMaxCount:c},C)),h]})};T.RippleContainer=G,Object.defineProperty(T,Symbol.toStringTag,{value:"Module"})}));
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sparkle-ripple",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"author": "yuyake-litrain",
|
|
5
|
+
"homepage": "https://m3ripple.js.org",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/yuyake-litrain/sparkle-ripple.git"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/sparkle-ripple.umd.cjs",
|
|
11
|
+
"module": "./dist/sparkle-ripple.js",
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@biomejs/biome": "^2.3.10",
|
|
14
|
+
"@types/node": "^22.19.3",
|
|
15
|
+
"@types/react": "^19.2.7",
|
|
16
|
+
"@types/react-dom": "^19.2.3",
|
|
17
|
+
"@vitejs/plugin-react-swc": "^4.2.2",
|
|
18
|
+
"@vitest/browser": "^3.2.4",
|
|
19
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
20
|
+
"clsx": "^2.1.1",
|
|
21
|
+
"concurrently": "^9.2.1",
|
|
22
|
+
"globals": "^16.5.0",
|
|
23
|
+
"playwright": "^1.57.0",
|
|
24
|
+
"typed-css-modules": "^0.9.1",
|
|
25
|
+
"typescript": "5.9.3",
|
|
26
|
+
"vite": "^7.3.0",
|
|
27
|
+
"vite-plugin-dts": "^4.5.4",
|
|
28
|
+
"vitest": "^3.2.4"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"@types/react": "^18.0.0 || ^19.0.0",
|
|
32
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
33
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
34
|
+
},
|
|
35
|
+
"exports": {
|
|
36
|
+
".": {
|
|
37
|
+
"types": "./dist/sparkle-ripple.d.ts",
|
|
38
|
+
"import": "./dist/sparkle-ripple.js",
|
|
39
|
+
"require": "./dist/sparkle-ripple.umd.cjs"
|
|
40
|
+
},
|
|
41
|
+
"./css": "./dist/sparkle-ripple.css"
|
|
42
|
+
},
|
|
43
|
+
"description": "Bring Material 3(You) Ripple Effect to your projects!",
|
|
44
|
+
"files": [
|
|
45
|
+
"dist"
|
|
46
|
+
],
|
|
47
|
+
"keywords": [
|
|
48
|
+
"Ripple",
|
|
49
|
+
"Ripple Effect",
|
|
50
|
+
"Material Design",
|
|
51
|
+
"Material You",
|
|
52
|
+
"react",
|
|
53
|
+
"web",
|
|
54
|
+
"front-end"
|
|
55
|
+
],
|
|
56
|
+
"license": "MIT",
|
|
57
|
+
"private": false,
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsc && vite build",
|
|
60
|
+
"lint": "biome lint --write",
|
|
61
|
+
"format": "biome format --write",
|
|
62
|
+
"check": "biome check --write",
|
|
63
|
+
"preview": "vite preview",
|
|
64
|
+
"preinstall": "npx only-allow bun"
|
|
65
|
+
},
|
|
66
|
+
"type": "module",
|
|
67
|
+
"types": "./dist/index.d.ts"
|
|
68
|
+
}
|