flet-box 0.0.0-stage → 1.0.2
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 +21 -0
- package/README.md +190 -2
- package/bin/cli.js +394 -0
- package/bin/commands/buildAndroid.js +200 -0
- package/bin/commands/buildIOS.js +237 -0
- package/bin/commands/create.js +406 -0
- package/bin/commands/createBundle.js +375 -0
- package/bin/commands/createComponent.js +115 -0
- package/bin/commands/createScreen.js +161 -0
- package/bin/commands/killServer.js +107 -0
- package/bin/commands/package.js +131 -0
- package/bin/commands/release.js +189 -0
- package/bin/commands/runBundle.js +62 -0
- package/bin/commands/runCloudflare.js +89 -0
- package/bin/commands/runNative.js +109 -0
- package/bin/commands/runServer.js +390 -0
- package/bin/ota/loader.js +361 -0
- package/bin/utils/bundleConfig.js +178 -0
- package/bin/utils/capacitor.js +137 -0
- package/bin/utils/colors.js +215 -0
- package/bin/utils/helpers.js +80 -0
- package/bin/utils/spinner.js +96 -0
- package/bin/utils/templates.js +1824 -0
- package/bin/utils/version.js +108 -0
- package/package.json +58 -4
- package/src/animations/AnimatedBox.js +607 -0
- package/src/animations/AnimatedText.js +105 -0
- package/src/animations/MatrixRain.js +220 -0
- package/src/animations/ParallaxBox.js +369 -0
- package/src/animations/animate.js +332 -0
- package/src/animations/index.js +25 -0
- package/src/app.js +41 -0
- package/src/assets/icon-1024-maskable.png +0 -0
- package/src/assets/icon-192-maskable.png +0 -0
- package/src/assets/icon-192.png +0 -0
- package/src/assets/icon-512-maskable.png +0 -0
- package/src/assets/icon-512.png +0 -0
- package/src/assets/logo.png +0 -0
- package/src/core/App.js +44 -0
- package/src/core/hmr-client.js +142 -0
- package/src/core/index.js +34 -0
- package/src/core/pwa.js +100 -0
- package/src/core/runApp.js +430 -0
- package/src/create-index.sh +237 -0
- package/src/fonts/MaterialIcons-Regular.woff2 +0 -0
- package/src/fonts/icons.css +23 -0
- package/src/index.d.ts +1930 -0
- package/src/index.js +288 -0
- package/src/navigations/AdaptiveScaffold.js +149 -0
- package/src/navigations/AppBar.js +331 -0
- package/src/navigations/BottomNavigation.js +298 -0
- package/src/navigations/CollapsibleSideBar.js +139 -0
- package/src/navigations/Drawer.js +333 -0
- package/src/navigations/DrawerItem.js +260 -0
- package/src/navigations/Router.js +475 -0
- package/src/navigations/Scaffold.js +537 -0
- package/src/navigations/Tabs.js +384 -0
- package/src/navigations/index.js +57 -0
- package/src/services/RamStore.js +213 -0
- package/src/services/Session.js +310 -0
- package/src/services/Storage.js +309 -0
- package/src/services/http.js +350 -0
- package/src/services/index.js +57 -0
- package/src/tools/animation.js +42 -0
- package/src/tools/array.js +95 -0
- package/src/tools/border.js +28 -0
- package/src/tools/clipboard.js +104 -0
- package/src/tools/color.js +329 -0
- package/src/tools/createList.js +215 -0
- package/src/tools/delay.js +55 -0
- package/src/tools/device.js +138 -0
- package/src/tools/dict.js +297 -0
- package/src/tools/dimensions.js +118 -0
- package/src/tools/filter.js +41 -0
- package/src/tools/flex.js +26 -0
- package/src/tools/gradient.js +34 -0
- package/src/tools/grid.js +57 -0
- package/src/tools/index.js +38 -0
- package/src/tools/mapList.js +107 -0
- package/src/tools/margin.js +93 -0
- package/src/tools/memo.js +60 -0
- package/src/tools/os.js +104 -0
- package/src/tools/padding.js +93 -0
- package/src/tools/print.js +69 -0
- package/src/tools/random.js +299 -0
- package/src/tools/ref.js +70 -0
- package/src/tools/rgba.js +4 -0
- package/src/tools/shadow.js +23 -0
- package/src/tools/string.js +85 -0
- package/src/tools/time.js +674 -0
- package/src/tools/transform.js +68 -0
- package/src/tools/transition.js +48 -0
- package/src/tools/useState.js +268 -0
- package/src/tools/uuid.js +42 -0
- package/src/utils/TextInputValidator.js +256 -0
- package/src/utils/composeUpdate.js +65 -0
- package/src/utils/getWidgetProps.js +35 -0
- package/src/utils/index.js +76 -0
- package/src/utils/markdownParser.js +368 -0
- package/src/utils/mediaTime.js +79 -0
- package/src/utils/navigation.js +263 -0
- package/src/utils/scriptInjector.js +73 -0
- package/src/utils/stackPosition.js +31 -0
- package/src/utils/stopWebRefresh.js +77 -0
- package/src/utils/styleInjector.js +38 -0
- package/src/utils/syntaxHighlight.js +339 -0
- package/src/utils/themes.js +347 -0
- package/src/utils/units.js +87 -0
- package/src/utils/visualEffects.js +169 -0
- package/src/widget-factory/addChildren.js +114 -0
- package/src/widget-factory/assignProps.js +256 -0
- package/src/widget-factory/createWidget.js +28 -0
- package/src/widget-factory/effects.js +132 -0
- package/src/widget-factory/index.js +7 -0
- package/src/widget-factory/lifecycle.js +210 -0
- package/src/widget-factory/makeParentable.js +89 -0
- package/src/widget-factory/pixTool.js +40 -0
- package/src/widget-factory/processProps.js +122 -0
- package/src/widget-factory/reactivity.js +98 -0
- package/src/widget-factory/remTool.js +34 -0
- package/src/widget-factory/stackPosition.js +87 -0
- package/src/widget-factory/stringTool.js +26 -0
- package/src/widget-factory/tools.js +249 -0
- package/src/widget-factory/translateProps.js +1049 -0
- package/src/widget-factory/warnings.js +284 -0
- package/src/widget-factory/widgetFactory.js +200 -0
- package/src/widgets/Accordion.js +562 -0
- package/src/widgets/AdMob.js +315 -0
- package/src/widgets/AdSense.js +268 -0
- package/src/widgets/AlertDialog.js +162 -0
- package/src/widgets/Audio.js +311 -0
- package/src/widgets/Avatar.js +210 -0
- package/src/widgets/Badge.js +189 -0
- package/src/widgets/BottomSheet.js +287 -0
- package/src/widgets/Button.js +247 -0
- package/src/widgets/Card.js +33 -0
- package/src/widgets/Carousel.js +301 -0
- package/src/widgets/Chart.js +632 -0
- package/src/widgets/Checkbox.js +106 -0
- package/src/widgets/Chip.js +153 -0
- package/src/widgets/CircularBar.js +352 -0
- package/src/widgets/CircularChart.js +549 -0
- package/src/widgets/CodeViewer.js +236 -0
- package/src/widgets/Column.js +29 -0
- package/src/widgets/Container.js +31 -0
- package/src/widgets/DataTable.js +257 -0
- package/src/widgets/Divider.js +93 -0
- package/src/widgets/DraggBox.js +115 -0
- package/src/widgets/Dropdown.js +505 -0
- package/src/widgets/DroppBox.js +180 -0
- package/src/widgets/FloatingActionButton.js +91 -0
- package/src/widgets/GridView.js +20 -0
- package/src/widgets/Icon.js +33 -0
- package/src/widgets/Image.js +15 -0
- package/src/widgets/Input.js +565 -0
- package/src/widgets/Inspector.js +254 -0
- package/src/widgets/InstallButton.js +110 -0
- package/src/widgets/ListTile.js +224 -0
- package/src/widgets/ListView.js +503 -0
- package/src/widgets/Markdown.js +1236 -0
- package/src/widgets/Modal.js +496 -0
- package/src/widgets/Pagination.js +195 -0
- package/src/widgets/ProgressBar.js +178 -0
- package/src/widgets/QRCode.js +467 -0
- package/src/widgets/Radio.js +132 -0
- package/src/widgets/Rating.js +261 -0
- package/src/widgets/Row.js +29 -0
- package/src/widgets/Skeleton.js +243 -0
- package/src/widgets/Slider.js +527 -0
- package/src/widgets/SnackBar.js +227 -0
- package/src/widgets/Stack.js +29 -0
- package/src/widgets/Stepper.js +296 -0
- package/src/widgets/Switch.js +102 -0
- package/src/widgets/Text.js +98 -0
- package/src/widgets/Tooltip.js +267 -0
- package/src/widgets/TreeView.js +370 -0
- package/src/widgets/Video.js +92 -0
- package/src/widgets/index.js +52 -0
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AnimatedText - Animates individual characters of a Text widget.
|
|
3
|
+
*
|
|
4
|
+
* Splits the original text into individual letter widgets, wraps each one in
|
|
5
|
+
* an {@link AnimatedBox}, and lays them out in a flex container. Supports both
|
|
6
|
+
* simultaneous and staggered (per-character delay) animation modes.
|
|
7
|
+
*
|
|
8
|
+
* @module animations/AnimatedText
|
|
9
|
+
*/
|
|
10
|
+
import { Container } from "../widgets/Container.js";
|
|
11
|
+
import { AnimatedBox } from "./AnimatedBox.js";
|
|
12
|
+
import { Text } from "../widgets/Text.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Renders each character of a Text widget as an independently animated element.
|
|
16
|
+
*
|
|
17
|
+
* @param {object} props
|
|
18
|
+
* @param {HTMLElement} props.child - A Text widget whose characters will be animated.
|
|
19
|
+
* @param {Array<object>} [props.animations] - Animation descriptors passed to each character's
|
|
20
|
+
* AnimatedBox. When absent or empty the original `child` is returned unchanged.
|
|
21
|
+
* @param {boolean} [props.sameTime=false] - When `true`, all characters animate simultaneously
|
|
22
|
+
* (no per-character delay). When `false`, each character is delayed by `delayBetween` seconds
|
|
23
|
+
* multiplied by its index.
|
|
24
|
+
* @param {number} [props.delayBetween=0.1] - Seconds between consecutive character animations
|
|
25
|
+
* (only used when `sameTime` is `false`).
|
|
26
|
+
* @param {"row"|"column"} [props.orientation="row"] - Flex direction for the character container.
|
|
27
|
+
* `"row"` lays characters horizontally (normal reading order);
|
|
28
|
+
* `"column"` stacks them vertically.
|
|
29
|
+
* @returns {HTMLElement|null} A flex Container holding each animated letter, or `null` if no
|
|
30
|
+
* `child` was provided.
|
|
31
|
+
*/
|
|
32
|
+
export const AnimatedText = ({
|
|
33
|
+
child,
|
|
34
|
+
animations,
|
|
35
|
+
sameTime = false,
|
|
36
|
+
delayBetween = 0.1,
|
|
37
|
+
orientation = "row",
|
|
38
|
+
}) => {
|
|
39
|
+
if (!child) return null;
|
|
40
|
+
if (!animations || animations.length === 0) return child;
|
|
41
|
+
|
|
42
|
+
// Extract text content and props from the original Text widget
|
|
43
|
+
const originalText = child._props?.text || child.textContent || "";
|
|
44
|
+
const originalProps = { ...(child._props || {}) };
|
|
45
|
+
// Ensure no background animation styles bleed into individual letter widgets
|
|
46
|
+
delete originalProps.animations;
|
|
47
|
+
|
|
48
|
+
const container = Container({
|
|
49
|
+
display: "flex",
|
|
50
|
+
flexDirection: orientation === "row" ? "row" : "column",
|
|
51
|
+
flexWrap: orientation === "row" ? "wrap" : "nowrap",
|
|
52
|
+
alignItems: orientation === "row" ? "center" : "flex-start",
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const letters = originalText.split("");
|
|
56
|
+
|
|
57
|
+
letters.forEach((letter, index) => {
|
|
58
|
+
// Render spaces as fixed-width spacers rather than animated characters
|
|
59
|
+
if (letter === " ") {
|
|
60
|
+
const space = Container({
|
|
61
|
+
textContent: " ",
|
|
62
|
+
display: "inline-block",
|
|
63
|
+
width: orientation === "row" ? "0.3em" : "100%",
|
|
64
|
+
height: orientation === "column" ? "0.3em" : "auto",
|
|
65
|
+
});
|
|
66
|
+
container.appendChild(space);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Compute per-character delay based on position (0 when sameTime is true)
|
|
71
|
+
const baseDelay = sameTime ? 0 : index * delayBetween;
|
|
72
|
+
|
|
73
|
+
// Copy all animation descriptors, overriding delay with the per-character value
|
|
74
|
+
const letterAnimations = animations.map((anim) => ({
|
|
75
|
+
...anim,
|
|
76
|
+
delay: `${baseDelay}s`,
|
|
77
|
+
}));
|
|
78
|
+
|
|
79
|
+
// Createste a new Text widget for this single character, inheriting all original
|
|
80
|
+
// text props (font, color, size, etc.) but rendering only the one letter.
|
|
81
|
+
const letterWidget = Text({
|
|
82
|
+
...originalProps,
|
|
83
|
+
text: letter,
|
|
84
|
+
children: undefined,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Match the flex layout of the container
|
|
88
|
+
letterWidget.style.display =
|
|
89
|
+
orientation === "row" ? "inline-block" : "block";
|
|
90
|
+
if (orientation === "column") {
|
|
91
|
+
letterWidget.style.textAlign = "center";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const animatedLetter = AnimatedBox({
|
|
95
|
+
animations: letterAnimations,
|
|
96
|
+
child: letterWidget,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
container.appendChild(animatedLetter);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
return container;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
export default AnimatedText;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MatrixRain - Canvas-based Matrix-style falling character animation.
|
|
3
|
+
*
|
|
4
|
+
* Renders a stream of randomly chosen characters cascading downward in a
|
|
5
|
+
* Matrix-green color scheme. Works in both `"fixed"` (full-viewport) and
|
|
6
|
+
* `"absolute"` (parent-relative) positioning modes.
|
|
7
|
+
*
|
|
8
|
+
* The returned `<canvas>` element has a `_cleanup()` method attached to it.
|
|
9
|
+
* Call it when removing the canvas from the DOM to cancel the animation loop,
|
|
10
|
+
* detach event listeners, and disconnect any ResizeObserver.
|
|
11
|
+
*
|
|
12
|
+
* @module animations/MatrixRain
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates and starts a Matrix-rain animation on a `<canvas>` element.
|
|
17
|
+
*
|
|
18
|
+
* @param {object} [props={}]
|
|
19
|
+
* @param {string} [props.chars="01アイウエオ…"] - Character pool to draw from.
|
|
20
|
+
* Defaults to a mix of digits and katakana.
|
|
21
|
+
* @param {number} [props.fontSize=16] - Column width and character height in pixels.
|
|
22
|
+
* @param {number} [props.speed=0.5] - Base drop speed (rows per frame).
|
|
23
|
+
* Each drop also adds a random jitter up to `speed` per frame.
|
|
24
|
+
* @param {number} [props.fadeAmount=0.05] - Opacity of the black overlay drawn each frame,
|
|
25
|
+
* controlling how quickly old characters fade. Higher = faster fade.
|
|
26
|
+
* @param {number} [props.resetProbability=0.975] - Probability threshold above which a drop that
|
|
27
|
+
* has passed the bottom resets to the top. Values closer to 1 make resets rarer.
|
|
28
|
+
* @param {boolean} [props.useDynamicColor=true] - When `true`, character brightness increases
|
|
29
|
+
* toward the bottom of the canvas. When `false`, a flat `#0f0` green is used.
|
|
30
|
+
* @param {"fixed"|"absolute"} [props.position="fixed"] - CSS `position` of the canvas.
|
|
31
|
+
* Use `"absolute"` to confine the rain inside a positioned parent element.
|
|
32
|
+
* @param {number} [props.zIndex=1] - CSS `z-index` of the canvas. Use a low value
|
|
33
|
+
* (e.g. `1` or even a negative number) to render behind other content.
|
|
34
|
+
* @returns {HTMLCanvasElement} The canvas element, already appended with an animation loop
|
|
35
|
+
* running. Attach `canvas._cleanup()` to your unmount lifecycle to clean up.
|
|
36
|
+
*/
|
|
37
|
+
export const MatrixRain = (props = {}) => {
|
|
38
|
+
const {
|
|
39
|
+
chars = "01アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン",
|
|
40
|
+
fontSize = 16,
|
|
41
|
+
speed = 0.5,
|
|
42
|
+
fadeAmount = 0.05,
|
|
43
|
+
resetProbability = 0.975,
|
|
44
|
+
useDynamicColor = true,
|
|
45
|
+
position = "fixed",
|
|
46
|
+
zIndex = 1, // Low default so the canvas renders behind other UI elements
|
|
47
|
+
} = props;
|
|
48
|
+
|
|
49
|
+
let canvas = null;
|
|
50
|
+
let ctx = null;
|
|
51
|
+
let animationId = null;
|
|
52
|
+
let drops = [];
|
|
53
|
+
let columns = 0;
|
|
54
|
+
let resizeObserver = null;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Determines the canvas dimensions based on positioning mode.
|
|
58
|
+
* In `"absolute"` mode, dimensions come from the parent element's bounding rect.
|
|
59
|
+
* In `"fixed"` mode (default), the full viewport size is used.
|
|
60
|
+
*
|
|
61
|
+
* @returns {{ width: number, height: number }}
|
|
62
|
+
*/
|
|
63
|
+
const getCanvasSize = () => {
|
|
64
|
+
const parent = canvas?.parentElement;
|
|
65
|
+
if (position === "absolute" && parent) {
|
|
66
|
+
const rect = parent.getBoundingClientRect();
|
|
67
|
+
return {
|
|
68
|
+
width: Math.max(1, Math.round(rect.width)),
|
|
69
|
+
height: Math.max(1, Math.round(rect.height)),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
width: window.innerWidth,
|
|
74
|
+
height: window.innerHeight,
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Resizes the canvas to match its container, recalculates column count,
|
|
80
|
+
* and resets all drop positions. Also attaches a ResizeObserver on first
|
|
81
|
+
* call when in `"absolute"` mode.
|
|
82
|
+
*
|
|
83
|
+
* @returns {void}
|
|
84
|
+
*/
|
|
85
|
+
const resizeCanvas = () => {
|
|
86
|
+
if (!canvas) return;
|
|
87
|
+
if (
|
|
88
|
+
position === "absolute" &&
|
|
89
|
+
!resizeObserver &&
|
|
90
|
+
typeof ResizeObserver !== "undefined" &&
|
|
91
|
+
canvas.parentElement
|
|
92
|
+
) {
|
|
93
|
+
resizeObserver = new ResizeObserver(resizeCanvas);
|
|
94
|
+
resizeObserver.observe(canvas.parentElement);
|
|
95
|
+
}
|
|
96
|
+
const { width, height } = getCanvasSize();
|
|
97
|
+
canvas.width = width;
|
|
98
|
+
canvas.height = height;
|
|
99
|
+
columns = Math.floor(canvas.width / fontSize);
|
|
100
|
+
drops = Array.from({ length: columns }, () => Math.random() * -100);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Returns a random character from the configured character pool.
|
|
105
|
+
*
|
|
106
|
+
* @returns {string} A single character.
|
|
107
|
+
*/
|
|
108
|
+
const getRandomChar = () => chars[Math.floor(Math.random() * chars.length)];
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Computes the fill color for a character at a given vertical position.
|
|
112
|
+
* When `useDynamicColor` is `true`, brightness increases toward the bottom,
|
|
113
|
+
* creating a depth illusion. Otherwise returns a flat `#0f0`.
|
|
114
|
+
*
|
|
115
|
+
* @param {number} y - Current y-coordinate of the character (pixels).
|
|
116
|
+
* @param {number} canvasHeight - Total canvas height in pixels.
|
|
117
|
+
* @returns {string} A CSS color string.
|
|
118
|
+
*/
|
|
119
|
+
const getColor = (y, canvasHeight) => {
|
|
120
|
+
if (!useDynamicColor) return "#0f0";
|
|
121
|
+
const intensity = 100 + (y / canvasHeight) * 155;
|
|
122
|
+
return `rgb(0, ${Math.min(255, intensity)}, 0)`;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Initializes the canvas element, sets its styles, and starts the draw loop.
|
|
127
|
+
*
|
|
128
|
+
* @returns {void}
|
|
129
|
+
*/
|
|
130
|
+
const init = () => {
|
|
131
|
+
canvas = document.createElement("canvas");
|
|
132
|
+
resizeCanvas();
|
|
133
|
+
canvas.style.display = "block";
|
|
134
|
+
canvas.style.position = position;
|
|
135
|
+
canvas.style.top = "0";
|
|
136
|
+
canvas.style.left = "0";
|
|
137
|
+
canvas.style.zIndex = zIndex;
|
|
138
|
+
canvas.style.pointerEvents = "none";
|
|
139
|
+
|
|
140
|
+
ctx = canvas.getContext("2d");
|
|
141
|
+
|
|
142
|
+
draw();
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Renders a single animation frame: applies a semi-transparent black overlay
|
|
147
|
+
* to fade previous characters, then draws each column's next character at its
|
|
148
|
+
* current drop position and advances the drop.
|
|
149
|
+
*
|
|
150
|
+
* Schedules itself via `requestAnimationFrame` until cleanup is called.
|
|
151
|
+
*
|
|
152
|
+
* @returns {void}
|
|
153
|
+
*/
|
|
154
|
+
const draw = () => {
|
|
155
|
+
if (!ctx || !canvas) return;
|
|
156
|
+
|
|
157
|
+
// Semi-transparent black overlay produces the trailing-fade effect
|
|
158
|
+
ctx.fillStyle = `rgba(0, 0, 0, ${fadeAmount})`;
|
|
159
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
160
|
+
|
|
161
|
+
ctx.font = `${fontSize}px monospace`;
|
|
162
|
+
|
|
163
|
+
for (let i = 0; i < drops.length; i++) {
|
|
164
|
+
const char = getRandomChar();
|
|
165
|
+
const x = i * fontSize;
|
|
166
|
+
const y = drops[i] * fontSize;
|
|
167
|
+
|
|
168
|
+
ctx.fillStyle = getColor(y, canvas.height);
|
|
169
|
+
ctx.fillText(char, x, y);
|
|
170
|
+
|
|
171
|
+
// Reset drop to the top once it has passed the bottom edge
|
|
172
|
+
if (y > canvas.height && Math.random() > resetProbability) {
|
|
173
|
+
drops[i] = 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
drops[i] += speed + Math.random() * speed;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
animationId = requestAnimationFrame(draw);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Initialize canvas and start animation
|
|
183
|
+
init();
|
|
184
|
+
|
|
185
|
+
/** Window resize handler — recalculates dimensions on viewport changes. */
|
|
186
|
+
const handleResize = () => {
|
|
187
|
+
resizeCanvas();
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
window.addEventListener("resize", handleResize);
|
|
191
|
+
if (position === "absolute" && typeof ResizeObserver !== "undefined") {
|
|
192
|
+
resizeObserver = new ResizeObserver(resizeCanvas);
|
|
193
|
+
if (canvas.parentElement) resizeObserver.observe(canvas.parentElement);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Stops the animation loop, removes event listeners, disconnects the
|
|
198
|
+
* ResizeObserver, and removes the canvas from the DOM.
|
|
199
|
+
*
|
|
200
|
+
* @returns {void}
|
|
201
|
+
*/
|
|
202
|
+
const cleanup = () => {
|
|
203
|
+
if (animationId) cancelAnimationFrame(animationId);
|
|
204
|
+
window.removeEventListener("resize", handleResize);
|
|
205
|
+
if (resizeObserver) resizeObserver.disconnect();
|
|
206
|
+
if (canvas && canvas.parentNode) canvas.parentNode.removeChild(canvas);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// Chain cleanup so any previously assigned _cleanup is also called
|
|
210
|
+
const originalCleanup = canvas._cleanup;
|
|
211
|
+
canvas._cleanup = () => {
|
|
212
|
+
cleanup();
|
|
213
|
+
if (originalCleanup) originalCleanup();
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// Return the canvas directly — not a wrapper container
|
|
217
|
+
return canvas;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export default MatrixRain;
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ParallaxBox - A container that applies a parallax offset to its child content.
|
|
3
|
+
*
|
|
4
|
+
* Supports three interaction modes:
|
|
5
|
+
* - `"scroll"`: offset follows the window scroll position.
|
|
6
|
+
* - `"mouse"`: offset tracks the pointer relative to the widget center.
|
|
7
|
+
* - `"hover"`: offset snaps to a position based on where the pointer enters,
|
|
8
|
+
* then resets on mouse leave.
|
|
9
|
+
*
|
|
10
|
+
* The returned element exposes public methods (`updateSpeed`, `updateDirection`,
|
|
11
|
+
* `setPosition`, `reset`) and a `_cleanup()` function that removes all event
|
|
12
|
+
* listeners when the element is destroyed.
|
|
13
|
+
*
|
|
14
|
+
* @module animations/ParallaxBox
|
|
15
|
+
*/
|
|
16
|
+
import { WidgetFactory } from "../widget-factory/index.js";
|
|
17
|
+
import { AnimatedBox } from "../animations/AnimatedBox.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a parallax-effect container.
|
|
21
|
+
*
|
|
22
|
+
* @param {object} [props={}]
|
|
23
|
+
* @param {"scroll"|"mouse"|"hover"} [props.type="scroll"]
|
|
24
|
+
* Interaction mode that drives the parallax offset.
|
|
25
|
+
* @param {number} [props.speed=0.5]
|
|
26
|
+
* Multiplier applied to scroll distance or pointer deviation. Higher values
|
|
27
|
+
* produce more pronounced movement.
|
|
28
|
+
* @param {"vertical"|"horizontal"|"both"} [props.direction="vertical"]
|
|
29
|
+
* Which axes the parallax offset is applied to.
|
|
30
|
+
* @param {number} [props.maxOffset=100]
|
|
31
|
+
* Maximum pixel offset allowed in each direction.
|
|
32
|
+
* @param {boolean} [props.reverse=false]
|
|
33
|
+
* When `true`, the movement direction is inverted.
|
|
34
|
+
* @param {HTMLElement|null} [props.child]
|
|
35
|
+
* The content element to wrap. Returns `null` if not provided.
|
|
36
|
+
* @param {boolean} [props.disabled=false]
|
|
37
|
+
* When `true`, all parallax transforms are suppressed.
|
|
38
|
+
* @param {Function} [props.onParallaxMove]
|
|
39
|
+
* Callback invoked on every transform update with `{ x, y }` offsets.
|
|
40
|
+
* @param {number} [props.duration=300]
|
|
41
|
+
* CSS transition duration (ms) for smooth motion.
|
|
42
|
+
* @param {string} [props.easing="ease-out"]
|
|
43
|
+
* CSS transition timing function.
|
|
44
|
+
* @param {...*} [props.rest]
|
|
45
|
+
* Additional props forwarded to the outer container's WidgetFactory call.
|
|
46
|
+
* @returns {HTMLElement|null} The container element with the parallax wrapper
|
|
47
|
+
* inside, or `null` if `child` was not provided.
|
|
48
|
+
*/
|
|
49
|
+
export const ParallaxBox = (props = {}) => {
|
|
50
|
+
let {
|
|
51
|
+
type = "scroll",
|
|
52
|
+
speed = 0.5,
|
|
53
|
+
direction = "vertical",
|
|
54
|
+
maxOffset = 100,
|
|
55
|
+
reverse = false,
|
|
56
|
+
child,
|
|
57
|
+
disabled = false,
|
|
58
|
+
onParallaxMove,
|
|
59
|
+
duration = 300,
|
|
60
|
+
easing = "ease-out",
|
|
61
|
+
...rest
|
|
62
|
+
} = props;
|
|
63
|
+
|
|
64
|
+
if (!child) return null;
|
|
65
|
+
|
|
66
|
+
let containerRef = null;
|
|
67
|
+
let animationFrame = null;
|
|
68
|
+
let currentX = 0;
|
|
69
|
+
let currentY = 0;
|
|
70
|
+
let targetX = 0;
|
|
71
|
+
let targetY = 0;
|
|
72
|
+
let animatedContent = null;
|
|
73
|
+
let initialTimer = null;
|
|
74
|
+
const transitionTimers = new Set();
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Schedules removal of the CSS transition after it has finished playing,
|
|
78
|
+
* so subsequent programmatic transforms are instant rather than animated.
|
|
79
|
+
*
|
|
80
|
+
* @returns {void}
|
|
81
|
+
*/
|
|
82
|
+
const clearTransitionLater = () => {
|
|
83
|
+
const timer = setTimeout(() => {
|
|
84
|
+
transitionTimers.delete(timer);
|
|
85
|
+
if (animatedContent) animatedContent.style.transition = "";
|
|
86
|
+
}, duration);
|
|
87
|
+
transitionTimers.add(timer);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Applies a CSS `transform` to the inner animated wrapper, clamped to
|
|
92
|
+
* `maxOffset`, respecting the active `direction` and `reverse` settings.
|
|
93
|
+
*
|
|
94
|
+
* @param {number} x - Desired horizontal offset in pixels.
|
|
95
|
+
* @param {number} y - Desired vertical offset in pixels.
|
|
96
|
+
* @param {boolean} [animate=true] - When `true`, a CSS transition is set;
|
|
97
|
+
* when `false`, the transition is removed for an instant snap.
|
|
98
|
+
* @returns {void}
|
|
99
|
+
*/
|
|
100
|
+
const applyTransform = (x, y, animate = true) => {
|
|
101
|
+
if (disabled) return;
|
|
102
|
+
|
|
103
|
+
let moveX = x;
|
|
104
|
+
let moveY = y;
|
|
105
|
+
|
|
106
|
+
if (direction === "vertical") moveX = 0;
|
|
107
|
+
if (direction === "horizontal") moveY = 0;
|
|
108
|
+
|
|
109
|
+
if (reverse) {
|
|
110
|
+
moveX = -moveX;
|
|
111
|
+
moveY = -moveY;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
moveX = Math.max(-maxOffset, Math.min(maxOffset, moveX));
|
|
115
|
+
moveY = Math.max(-maxOffset, Math.min(maxOffset, moveY));
|
|
116
|
+
|
|
117
|
+
targetX = moveX;
|
|
118
|
+
targetY = moveY;
|
|
119
|
+
|
|
120
|
+
if (onParallaxMove) onParallaxMove({ x: moveX, y: moveY });
|
|
121
|
+
|
|
122
|
+
if (animatedContent) {
|
|
123
|
+
const transformParts = [];
|
|
124
|
+
if (direction === "horizontal" || direction === "both") {
|
|
125
|
+
transformParts.push(`translateX(${moveX}px)`);
|
|
126
|
+
}
|
|
127
|
+
if (direction === "vertical" || direction === "both") {
|
|
128
|
+
transformParts.push(`translateY(${moveY}px)`);
|
|
129
|
+
}
|
|
130
|
+
animatedContent.style.transform = transformParts.join(" ");
|
|
131
|
+
if (animate) {
|
|
132
|
+
animatedContent.style.transition = `transform ${duration}ms ${easing}`;
|
|
133
|
+
} else {
|
|
134
|
+
animatedContent.style.transition = "none";
|
|
135
|
+
}
|
|
136
|
+
clearTransitionLater();
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Resets the inner wrapper's transform back to the origin (0, 0).
|
|
142
|
+
*
|
|
143
|
+
* @param {boolean} [animate=true] - Whether to animate the reset.
|
|
144
|
+
* @returns {void}
|
|
145
|
+
*/
|
|
146
|
+
const resetTransform = (animate = true) => {
|
|
147
|
+
targetX = 0;
|
|
148
|
+
targetY = 0;
|
|
149
|
+
if (animatedContent) {
|
|
150
|
+
animatedContent.style.transform = "translateX(0) translateY(0)";
|
|
151
|
+
if (animate) {
|
|
152
|
+
animatedContent.style.transition = `transform ${duration}ms ${easing}`;
|
|
153
|
+
} else {
|
|
154
|
+
animatedContent.style.transition = "none";
|
|
155
|
+
}
|
|
156
|
+
clearTransitionLater();
|
|
157
|
+
}
|
|
158
|
+
if (onParallaxMove) onParallaxMove({ x: 0, y: 0 });
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Window scroll event handler for `type="scroll"` mode.
|
|
163
|
+
* Converts the current scroll position into a parallax offset and calls
|
|
164
|
+
* `applyTransform` inside a `requestAnimationFrame` callback.
|
|
165
|
+
*
|
|
166
|
+
* @returns {void}
|
|
167
|
+
*/
|
|
168
|
+
const handleScroll = () => {
|
|
169
|
+
if (disabled || type !== "scroll") return;
|
|
170
|
+
if (animationFrame) cancelAnimationFrame(animationFrame);
|
|
171
|
+
|
|
172
|
+
animationFrame = requestAnimationFrame(() => {
|
|
173
|
+
const scrollY = window.scrollY;
|
|
174
|
+
const scrollX = window.scrollX;
|
|
175
|
+
let moveY = scrollY * speed;
|
|
176
|
+
let moveX = scrollX * speed;
|
|
177
|
+
|
|
178
|
+
applyTransform(moveX, moveY, true);
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Pointer move handler for `type="mouse"` and `type="hover"` modes.
|
|
184
|
+
* Calculates the pointer's normalized offset from the container center and
|
|
185
|
+
* converts it to a pixel offset scaled by `maxOffset`.
|
|
186
|
+
*
|
|
187
|
+
* @param {MouseEvent} e
|
|
188
|
+
* @returns {void}
|
|
189
|
+
*/
|
|
190
|
+
const handleMouseMove = (e) => {
|
|
191
|
+
if (disabled || (type !== "mouse" && type !== "hover")) return;
|
|
192
|
+
if (animationFrame) cancelAnimationFrame(animationFrame);
|
|
193
|
+
|
|
194
|
+
animationFrame = requestAnimationFrame(() => {
|
|
195
|
+
const rect = containerRef.getBoundingClientRect();
|
|
196
|
+
const centerX = rect.left + rect.width / 2;
|
|
197
|
+
const centerY = rect.top + rect.height / 2;
|
|
198
|
+
|
|
199
|
+
let moveX = (e.clientX - centerX) / (rect.width / 2);
|
|
200
|
+
let moveY = (e.clientY - centerY) / (rect.height / 2);
|
|
201
|
+
|
|
202
|
+
moveX = moveX * maxOffset;
|
|
203
|
+
moveY = moveY * maxOffset;
|
|
204
|
+
|
|
205
|
+
applyTransform(moveX, moveY, true);
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* `mouseenter` handler for `type="hover"` mode.
|
|
211
|
+
* Snaps the offset to match the pointer's entry position within the element.
|
|
212
|
+
*
|
|
213
|
+
* @param {MouseEvent} e
|
|
214
|
+
* @returns {void}
|
|
215
|
+
*/
|
|
216
|
+
const handleMouseEnter = (e) => {
|
|
217
|
+
if (disabled || type !== "hover") return;
|
|
218
|
+
const rect = containerRef.getBoundingClientRect();
|
|
219
|
+
const percentX = (e.clientX - rect.left) / rect.width;
|
|
220
|
+
const percentY = (e.clientY - rect.top) / rect.height;
|
|
221
|
+
|
|
222
|
+
let moveX = (percentX - 0.5) * maxOffset * 2;
|
|
223
|
+
let moveY = (percentY - 0.5) * maxOffset * 2;
|
|
224
|
+
|
|
225
|
+
applyTransform(moveX, moveY, true);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* `mouseleave` handler — resets the parallax offset back to zero.
|
|
230
|
+
*
|
|
231
|
+
* @returns {void}
|
|
232
|
+
*/
|
|
233
|
+
const handleMouseLeave = () => {
|
|
234
|
+
if (disabled || (type !== "mouse" && type !== "hover")) return;
|
|
235
|
+
resetTransform(true);
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Outer container — position: relative + overflow: hidden to clip the child
|
|
239
|
+
const container = WidgetFactory({
|
|
240
|
+
tag: "div",
|
|
241
|
+
position: "relative",
|
|
242
|
+
overflow: "hidden",
|
|
243
|
+
...rest,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
containerRef = container;
|
|
247
|
+
|
|
248
|
+
// Inner wrapper that receives CSS transforms; will-change enables GPU compositing
|
|
249
|
+
const animatedWrapper = WidgetFactory({
|
|
250
|
+
tag: "div",
|
|
251
|
+
willChange: "transform",
|
|
252
|
+
transition: `transform ${duration}ms ${easing}`,
|
|
253
|
+
transform: "translateX(0) translateY(0)",
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Append child content to the animated wrapper
|
|
257
|
+
if (child) {
|
|
258
|
+
if (child instanceof HTMLElement) {
|
|
259
|
+
animatedWrapper.appendChild(child);
|
|
260
|
+
} else if (typeof child === "string") {
|
|
261
|
+
const textNode = document.createTextNode(child);
|
|
262
|
+
animatedWrapper.appendChild(textNode);
|
|
263
|
+
} else if (Array.isArray(child)) {
|
|
264
|
+
child.forEach((item) => {
|
|
265
|
+
if (item instanceof HTMLElement) animatedWrapper.appendChild(item);
|
|
266
|
+
else if (typeof item === "string") {
|
|
267
|
+
animatedWrapper.appendChild(document.createTextNode(item));
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
animatedContent = animatedWrapper;
|
|
274
|
+
container.appendChild(animatedWrapper);
|
|
275
|
+
|
|
276
|
+
// Attach the appropriate event listeners for the selected interaction mode
|
|
277
|
+
if (type === "scroll") {
|
|
278
|
+
window.addEventListener("scroll", handleScroll);
|
|
279
|
+
// Run once after a short delay to sync with the current scroll position
|
|
280
|
+
initialTimer = setTimeout(() => {
|
|
281
|
+
initialTimer = null;
|
|
282
|
+
handleScroll();
|
|
283
|
+
}, 100);
|
|
284
|
+
} else if (type === "mouse" || type === "hover") {
|
|
285
|
+
container.addEventListener("mousemove", handleMouseMove);
|
|
286
|
+
container.addEventListener("mouseleave", handleMouseLeave);
|
|
287
|
+
if (type === "hover") {
|
|
288
|
+
container.addEventListener("mouseenter", handleMouseEnter);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ========== PUBLIC METHODS ==========
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Updates the parallax speed multiplier at runtime and re-applies the
|
|
296
|
+
* current transform (scroll mode only).
|
|
297
|
+
*
|
|
298
|
+
* @param {number} newSpeed - New speed multiplier.
|
|
299
|
+
* @returns {void}
|
|
300
|
+
*/
|
|
301
|
+
container.updateSpeed = (newSpeed) => {
|
|
302
|
+
speed = newSpeed;
|
|
303
|
+
if (type === "scroll") handleScroll();
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Updates the active parallax direction and re-applies or resets the transform.
|
|
308
|
+
*
|
|
309
|
+
* @param {"vertical"|"horizontal"|"both"} newDirection
|
|
310
|
+
* @returns {void}
|
|
311
|
+
*/
|
|
312
|
+
container.updateDirection = (newDirection) => {
|
|
313
|
+
direction = newDirection;
|
|
314
|
+
if (type === "scroll") handleScroll();
|
|
315
|
+
else resetTransform(true);
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Programmatically sets the parallax offset to a specific position.
|
|
320
|
+
*
|
|
321
|
+
* @param {number} x - Horizontal offset in pixels.
|
|
322
|
+
* @param {number} y - Vertical offset in pixels.
|
|
323
|
+
* @param {boolean} [animate=true] - Whether to animate the change.
|
|
324
|
+
* @returns {void}
|
|
325
|
+
*/
|
|
326
|
+
container.setPosition = (x, y, animate = true) => {
|
|
327
|
+
applyTransform(x, y, animate);
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Resets the parallax offset back to the origin (0, 0).
|
|
332
|
+
*
|
|
333
|
+
* @param {boolean} [animate=true] - Whether to animate the reset.
|
|
334
|
+
* @returns {void}
|
|
335
|
+
*/
|
|
336
|
+
container.reset = (animate = true) => {
|
|
337
|
+
resetTransform(animate);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// ========== CLEANUP ==========
|
|
341
|
+
|
|
342
|
+
const originalCleanup = container._cleanup;
|
|
343
|
+
/**
|
|
344
|
+
* Removes all event listeners, cancels pending animation frames, clears
|
|
345
|
+
* timers, and calls any previously assigned `_cleanup` function.
|
|
346
|
+
*
|
|
347
|
+
* @returns {void}
|
|
348
|
+
*/
|
|
349
|
+
container._cleanup = () => {
|
|
350
|
+
if (type === "scroll") {
|
|
351
|
+
window.removeEventListener("scroll", handleScroll);
|
|
352
|
+
} else {
|
|
353
|
+
container.removeEventListener("mousemove", handleMouseMove);
|
|
354
|
+
container.removeEventListener("mouseleave", handleMouseLeave);
|
|
355
|
+
if (type === "hover") {
|
|
356
|
+
container.removeEventListener("mouseenter", handleMouseEnter);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (animationFrame) cancelAnimationFrame(animationFrame);
|
|
360
|
+
if (initialTimer) clearTimeout(initialTimer);
|
|
361
|
+
transitionTimers.forEach((timer) => clearTimeout(timer));
|
|
362
|
+
transitionTimers.clear();
|
|
363
|
+
if (originalCleanup) originalCleanup();
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
return container;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
export default ParallaxBox;
|