@flux-ui/visuals 3.6.1 → 3.7.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/dist/index.js +148 -146
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/component/FluxVisualAnimatedColors.vue +41 -42
- package/src/component/FluxVisualFlickeringGrid.vue +54 -54
- package/src/component/FluxVisualHighlighter.vue +35 -35
- package/src/component/FluxVisualNumberFlow.vue +56 -56
- package/src/component/FluxVisualSlotText.vue +25 -23
- package/src/component/FluxVisualTextScramble.vue +8 -8
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flux-ui/visuals",
|
|
3
3
|
"description": "Visual effect components for the Flux UI library.",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.7.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://github.com/sponsors/basmilius",
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
"module": "./dist/index.js",
|
|
49
49
|
"types": "./dist/index.d.ts",
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@basmilius/common": "^3.
|
|
52
|
-
"@basmilius/utils": "^3.
|
|
53
|
-
"@flux-ui/internals": "3.
|
|
54
|
-
"@flux-ui/types": "3.
|
|
51
|
+
"@basmilius/common": "^3.53.0",
|
|
52
|
+
"@basmilius/utils": "^3.53.0",
|
|
53
|
+
"@flux-ui/internals": "3.7.0",
|
|
54
|
+
"@flux-ui/types": "3.7.0",
|
|
55
55
|
"clsx": "^2.1.1",
|
|
56
56
|
"rough-notation": "^0.5.1"
|
|
57
57
|
},
|
|
@@ -59,9 +59,9 @@
|
|
|
59
59
|
"vue": "^3.6.0-beta.17"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@basmilius/vite-preset": "^3.
|
|
62
|
+
"@basmilius/vite-preset": "^3.53.0",
|
|
63
63
|
"@types/node": "^26.1.1",
|
|
64
|
-
"@vitejs/plugin-vue": "^6.0.
|
|
64
|
+
"@vitejs/plugin-vue": "^6.0.8",
|
|
65
65
|
"@vue/tsconfig": "^0.9.1",
|
|
66
66
|
"sass-embedded": "^1.100.0",
|
|
67
67
|
"typescript": "6.0.3",
|
|
@@ -32,14 +32,13 @@
|
|
|
32
32
|
}>();
|
|
33
33
|
|
|
34
34
|
const canvasRef = useTemplateRef('canvas');
|
|
35
|
-
const componentId = useComponentId();
|
|
36
|
-
const inView = useInView(canvasRef, {initial: true});
|
|
37
|
-
|
|
38
35
|
const contextRef = ref<CanvasRenderingContext2D>();
|
|
39
36
|
const animationFrame = ref(0);
|
|
40
37
|
const tick = ref(0);
|
|
41
38
|
const size = ref<{ width: number; height: number; } | null>(null);
|
|
42
39
|
|
|
40
|
+
const componentId = useComponentId();
|
|
41
|
+
const inView = useInView(canvasRef, {initial: true});
|
|
43
42
|
const reducedMotion = prefersReducedMotion();
|
|
44
43
|
|
|
45
44
|
const polygons = computed(() => {
|
|
@@ -72,6 +71,45 @@
|
|
|
72
71
|
return polygons;
|
|
73
72
|
});
|
|
74
73
|
|
|
74
|
+
watch(canvasRef, (canvas, _, onCleanup) => {
|
|
75
|
+
if (!canvas) {
|
|
76
|
+
contextRef.value = undefined;
|
|
77
|
+
size.value = null;
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
contextRef.value = canvas.getContext('2d', {
|
|
82
|
+
alpha: true,
|
|
83
|
+
colorSpace: 'display-p3'
|
|
84
|
+
})!;
|
|
85
|
+
|
|
86
|
+
if (typeof ResizeObserver === 'undefined') {
|
|
87
|
+
size.value = {width: canvas.offsetWidth, height: canvas.offsetHeight};
|
|
88
|
+
canvas.width = canvas.offsetWidth;
|
|
89
|
+
canvas.height = canvas.offsetHeight;
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const observer = new ResizeObserver(() => {
|
|
94
|
+
const width = canvas.offsetWidth;
|
|
95
|
+
const height = canvas.offsetHeight;
|
|
96
|
+
|
|
97
|
+
if (!width || !height || (size.value?.width === width && size.value?.height === height)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
canvas.width = width;
|
|
102
|
+
canvas.height = height;
|
|
103
|
+
size.value = {width, height};
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
observer.observe(canvas);
|
|
107
|
+
|
|
108
|
+
onCleanup(() => observer.disconnect());
|
|
109
|
+
}, {immediate: true});
|
|
110
|
+
|
|
111
|
+
watch([polygons, () => opacity, size, inView], () => restart());
|
|
112
|
+
|
|
75
113
|
onBeforeUnmount(() => cancel());
|
|
76
114
|
|
|
77
115
|
function cancel(): void {
|
|
@@ -145,43 +183,4 @@
|
|
|
145
183
|
|
|
146
184
|
schedule();
|
|
147
185
|
}
|
|
148
|
-
|
|
149
|
-
watch(canvasRef, (canvas, _, onCleanup) => {
|
|
150
|
-
if (!canvas) {
|
|
151
|
-
contextRef.value = undefined;
|
|
152
|
-
size.value = null;
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
contextRef.value = canvas.getContext('2d', {
|
|
157
|
-
alpha: true,
|
|
158
|
-
colorSpace: 'display-p3'
|
|
159
|
-
})!;
|
|
160
|
-
|
|
161
|
-
if (typeof ResizeObserver === 'undefined') {
|
|
162
|
-
size.value = {width: canvas.offsetWidth, height: canvas.offsetHeight};
|
|
163
|
-
canvas.width = canvas.offsetWidth;
|
|
164
|
-
canvas.height = canvas.offsetHeight;
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const observer = new ResizeObserver(() => {
|
|
169
|
-
const width = canvas.offsetWidth;
|
|
170
|
-
const height = canvas.offsetHeight;
|
|
171
|
-
|
|
172
|
-
if (!width || !height || (size.value?.width === width && size.value?.height === height)) {
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
canvas.width = width;
|
|
177
|
-
canvas.height = height;
|
|
178
|
-
size.value = {width, height};
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
observer.observe(canvas);
|
|
182
|
-
|
|
183
|
-
onCleanup(() => observer.disconnect());
|
|
184
|
-
}, {immediate: true});
|
|
185
|
-
|
|
186
|
-
watch([polygons, () => opacity, size, inView], () => restart());
|
|
187
186
|
</script>
|
|
@@ -49,60 +49,6 @@
|
|
|
49
49
|
return context.getImageData(0, 0, 1, 1).data;
|
|
50
50
|
});
|
|
51
51
|
|
|
52
|
-
function draw(context: CanvasRenderingContext2D, width: number, height: number, columns: number, rows: number, squares: Float32Array, dpr: number): void {
|
|
53
|
-
context.clearRect(0, 0, width * dpr, height * dpr);
|
|
54
|
-
|
|
55
|
-
const [r, g, b] = unref(rgb);
|
|
56
|
-
|
|
57
|
-
for (let i = 0; i < columns; ++i) {
|
|
58
|
-
for (let j = 0; j < rows; ++j) {
|
|
59
|
-
const opacity = squares[i * rows + j];
|
|
60
|
-
context.fillStyle = `rgb(${r} ${g} ${b} / ${opacity})`;
|
|
61
|
-
context.fillRect(
|
|
62
|
-
(i * (size + gap) + width / 2 - (columns / 2 * (size + gap) - gap / 2)) * dpr,
|
|
63
|
-
(j * (size + gap) + height / 2 - (rows / 2 * (size + gap) - gap / 2)) * dpr,
|
|
64
|
-
size * dpr,
|
|
65
|
-
size * dpr
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function setup(canvas: HTMLCanvasElement) {
|
|
72
|
-
const width = canvas.clientWidth;
|
|
73
|
-
const height = canvas.clientHeight;
|
|
74
|
-
const dpr = window.devicePixelRatio || 1;
|
|
75
|
-
canvas.width = width * dpr;
|
|
76
|
-
canvas.height = height * dpr;
|
|
77
|
-
canvas.style.width = `${width}px`;
|
|
78
|
-
canvas.style.height = `${height}px`;
|
|
79
|
-
|
|
80
|
-
const columns = Math.ceil(width / (size + gap));
|
|
81
|
-
const rows = Math.ceil(height / (size + gap));
|
|
82
|
-
const squares = new Float32Array(columns * rows);
|
|
83
|
-
|
|
84
|
-
for (let i = 0; i < squares.length; ++i) {
|
|
85
|
-
squares[i] = mulberry.next() * maxOpacity;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
width,
|
|
90
|
-
height,
|
|
91
|
-
columns,
|
|
92
|
-
rows,
|
|
93
|
-
squares,
|
|
94
|
-
dpr
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function tick(squares: Float32Array, delta: number): void {
|
|
99
|
-
for (let i = 0; i < squares.length; ++i) {
|
|
100
|
-
if (mulberry.next() < flickerChance * delta) {
|
|
101
|
-
squares[i] = mulberry.next() * maxOpacity;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
52
|
watch([canvasRef, inView], ([canvas, inView], _, onCleanup) => {
|
|
107
53
|
if (!canvas || !inView) {
|
|
108
54
|
return;
|
|
@@ -156,4 +102,58 @@
|
|
|
156
102
|
cancelAnimationFrame(frame);
|
|
157
103
|
});
|
|
158
104
|
}, {immediate: true});
|
|
105
|
+
|
|
106
|
+
function draw(context: CanvasRenderingContext2D, width: number, height: number, columns: number, rows: number, squares: Float32Array, dpr: number): void {
|
|
107
|
+
context.clearRect(0, 0, width * dpr, height * dpr);
|
|
108
|
+
|
|
109
|
+
const [r, g, b] = unref(rgb);
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < columns; ++i) {
|
|
112
|
+
for (let j = 0; j < rows; ++j) {
|
|
113
|
+
const opacity = squares[i * rows + j];
|
|
114
|
+
context.fillStyle = `rgb(${r} ${g} ${b} / ${opacity})`;
|
|
115
|
+
context.fillRect(
|
|
116
|
+
(i * (size + gap) + width / 2 - (columns / 2 * (size + gap) - gap / 2)) * dpr,
|
|
117
|
+
(j * (size + gap) + height / 2 - (rows / 2 * (size + gap) - gap / 2)) * dpr,
|
|
118
|
+
size * dpr,
|
|
119
|
+
size * dpr
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function setup(canvas: HTMLCanvasElement) {
|
|
126
|
+
const width = canvas.clientWidth;
|
|
127
|
+
const height = canvas.clientHeight;
|
|
128
|
+
const dpr = window.devicePixelRatio || 1;
|
|
129
|
+
canvas.width = width * dpr;
|
|
130
|
+
canvas.height = height * dpr;
|
|
131
|
+
canvas.style.width = `${width}px`;
|
|
132
|
+
canvas.style.height = `${height}px`;
|
|
133
|
+
|
|
134
|
+
const columns = Math.ceil(width / (size + gap));
|
|
135
|
+
const rows = Math.ceil(height / (size + gap));
|
|
136
|
+
const squares = new Float32Array(columns * rows);
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < squares.length; ++i) {
|
|
139
|
+
squares[i] = mulberry.next() * maxOpacity;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
width,
|
|
144
|
+
height,
|
|
145
|
+
columns,
|
|
146
|
+
rows,
|
|
147
|
+
squares,
|
|
148
|
+
dpr
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function tick(squares: Float32Array, delta: number): void {
|
|
153
|
+
for (let i = 0; i < squares.length; ++i) {
|
|
154
|
+
if (mulberry.next() < flickerChance * delta) {
|
|
155
|
+
squares[i] = mulberry.next() * maxOpacity;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
159
|
</script>
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
default(): any;
|
|
49
49
|
}>();
|
|
50
50
|
|
|
51
|
-
const group = useHighlighterGroupInjection();
|
|
52
|
-
|
|
53
51
|
const targetRef = useTemplateRef('target');
|
|
54
52
|
|
|
53
|
+
const group = useHighlighterGroupInjection();
|
|
54
|
+
|
|
55
55
|
// Standalone in-view tracking. In a group the parent owns the reveal timing,
|
|
56
56
|
// so no observer is set up at all.
|
|
57
57
|
const inView = group ? shallowRef(true) : useInView(targetRef, {initial: !whenInView});
|
|
@@ -71,6 +71,39 @@
|
|
|
71
71
|
let shownTimer: number | undefined;
|
|
72
72
|
let revealed = false;
|
|
73
73
|
|
|
74
|
+
// The annotation type is immutable, so any prop change rebuilds it from scratch.
|
|
75
|
+
watch([effectiveVariant, effectiveColor, effectiveStrokeWidth, effectiveAnimationDuration, effectiveIterations, effectivePadding, effectiveMultiline], () => build());
|
|
76
|
+
|
|
77
|
+
// Standalone reveal once the element scrolls into view (whenInView).
|
|
78
|
+
watch(inView, () => {
|
|
79
|
+
if (!group) {
|
|
80
|
+
reveal();
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
onMounted(() => {
|
|
85
|
+
const element = targetRef.value;
|
|
86
|
+
|
|
87
|
+
if (group && element) {
|
|
88
|
+
entry = {element, getAnnotation: () => annotation};
|
|
89
|
+
group.add(entry);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
build();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
onBeforeUnmount(() => {
|
|
96
|
+
if (group && entry) {
|
|
97
|
+
group.remove(entry);
|
|
98
|
+
entry = null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
window.clearTimeout(shownTimer);
|
|
102
|
+
stopSettleWatch();
|
|
103
|
+
annotation?.remove();
|
|
104
|
+
annotation = null;
|
|
105
|
+
});
|
|
106
|
+
|
|
74
107
|
// rough-notation has no completion callback, so shown is emitted after the
|
|
75
108
|
// draw animation's duration, or immediately when it draws without animation.
|
|
76
109
|
function emitShown(): void {
|
|
@@ -177,39 +210,6 @@
|
|
|
177
210
|
observer.observe(document.body);
|
|
178
211
|
}
|
|
179
212
|
|
|
180
|
-
onMounted(() => {
|
|
181
|
-
const element = targetRef.value;
|
|
182
|
-
|
|
183
|
-
if (group && element) {
|
|
184
|
-
entry = {element, getAnnotation: () => annotation};
|
|
185
|
-
group.add(entry);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
build();
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// The annotation type is immutable, so any prop change rebuilds it from scratch.
|
|
192
|
-
watch([effectiveVariant, effectiveColor, effectiveStrokeWidth, effectiveAnimationDuration, effectiveIterations, effectivePadding, effectiveMultiline], () => build());
|
|
193
|
-
|
|
194
|
-
// Standalone reveal once the element scrolls into view (whenInView).
|
|
195
|
-
watch(inView, () => {
|
|
196
|
-
if (!group) {
|
|
197
|
-
reveal();
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
onBeforeUnmount(() => {
|
|
202
|
-
if (group && entry) {
|
|
203
|
-
group.remove(entry);
|
|
204
|
-
entry = null;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
window.clearTimeout(shownTimer);
|
|
208
|
-
stopSettleWatch();
|
|
209
|
-
annotation?.remove();
|
|
210
|
-
annotation = null;
|
|
211
|
-
});
|
|
212
|
-
|
|
213
213
|
defineExpose({
|
|
214
214
|
hide,
|
|
215
215
|
replay,
|
|
@@ -48,6 +48,62 @@
|
|
|
48
48
|
|
|
49
49
|
const CUBIC_BEZIER_PATTERN = /^cubic-bezier\(\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*,\s*([-\d.]+)\s*\)$/;
|
|
50
50
|
|
|
51
|
+
// Fallback for an unrecognised easing value, matching the --swift-out default.
|
|
52
|
+
const swiftOutEasing = cubicBezier(0.55, 0, 0.1, 1);
|
|
53
|
+
|
|
54
|
+
const labelRef = useTemplateRef('label');
|
|
55
|
+
|
|
56
|
+
let frame = 0;
|
|
57
|
+
let current = animateOnMount ? 0 : value;
|
|
58
|
+
|
|
59
|
+
// Resolve the easing prop to a plain (t) => number function the tween can
|
|
60
|
+
// sample: functions pass through, keywords map to the table above and a
|
|
61
|
+
// cubic-bezier(...) string is parsed into a solver, falling back to swift-out.
|
|
62
|
+
const easingFunction = computed<NumberFlowEasingFunction>(() => {
|
|
63
|
+
if (typeof easing === 'function') {
|
|
64
|
+
return easing;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const keyword = EASINGS[easing as NumberFlowEasingKeyword];
|
|
68
|
+
|
|
69
|
+
if (keyword) {
|
|
70
|
+
return keyword;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return parseCubicBezier(easing) ?? swiftOutEasing;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Default to whole numbers so a mid-tween value never sprouts stray decimals.
|
|
77
|
+
// Currency, percentage and fractional displays opt in through `format`.
|
|
78
|
+
const formatter = computed(() => new Intl.NumberFormat(locale, format ?? {maximumFractionDigits: 0}));
|
|
79
|
+
|
|
80
|
+
// Rendered once for SSR / first paint only. The engine owns the span's text
|
|
81
|
+
// after mount, so this must NOT be reactive - a reactive {{ value }} would make
|
|
82
|
+
// Vue re-patch the text node every frame and wipe the tween. The accessible
|
|
83
|
+
// name stays current through the reactive :aria-label binding.
|
|
84
|
+
const initialText = formatter.value.format(animateOnMount ? 0 : value);
|
|
85
|
+
|
|
86
|
+
// Screen readers always read the final, settled value rather than the
|
|
87
|
+
// intermediate frames streaming past on screen.
|
|
88
|
+
const accessibleValue = computed(() => formatter.value.format(value));
|
|
89
|
+
|
|
90
|
+
// Tween from wherever the display currently sits, so a value that changes
|
|
91
|
+
// mid-tween keeps rolling smoothly instead of jumping.
|
|
92
|
+
watch(() => value, next => tween(current, next));
|
|
93
|
+
|
|
94
|
+
// Re-render in place when the locale or format changes.
|
|
95
|
+
watch(formatter, () => render(current));
|
|
96
|
+
|
|
97
|
+
onMounted(() => {
|
|
98
|
+
if (animateOnMount) {
|
|
99
|
+
tween(0, value);
|
|
100
|
+
} else {
|
|
101
|
+
render(value);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
onBeforeUnmount(cancel);
|
|
106
|
+
|
|
51
107
|
// Evaluate a cubic-bezier(x1, y1, x2, y2) timing function in JS. The control
|
|
52
108
|
// points describe x(t) and y(t); for a given progress we need y at the t where
|
|
53
109
|
// x(t) === progress. x is solved with Newton-Raphson and a bisection fallback,
|
|
@@ -132,45 +188,6 @@
|
|
|
132
188
|
return cubicBezier(Number(match[1]), Number(match[2]), Number(match[3]), Number(match[4]));
|
|
133
189
|
}
|
|
134
190
|
|
|
135
|
-
// Fallback for an unrecognised easing value, matching the --swift-out default.
|
|
136
|
-
const swiftOutEasing = cubicBezier(0.55, 0, 0.1, 1);
|
|
137
|
-
|
|
138
|
-
// Resolve the easing prop to a plain (t) => number function the tween can
|
|
139
|
-
// sample: functions pass through, keywords map to the table above and a
|
|
140
|
-
// cubic-bezier(...) string is parsed into a solver, falling back to swift-out.
|
|
141
|
-
const easingFunction = computed<NumberFlowEasingFunction>(() => {
|
|
142
|
-
if (typeof easing === 'function') {
|
|
143
|
-
return easing;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const keyword = EASINGS[easing as NumberFlowEasingKeyword];
|
|
147
|
-
|
|
148
|
-
if (keyword) {
|
|
149
|
-
return keyword;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return parseCubicBezier(easing) ?? swiftOutEasing;
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// Default to whole numbers so a mid-tween value never sprouts stray decimals.
|
|
156
|
-
// Currency, percentage and fractional displays opt in through `format`.
|
|
157
|
-
const formatter = computed(() => new Intl.NumberFormat(locale, format ?? {maximumFractionDigits: 0}));
|
|
158
|
-
|
|
159
|
-
// Rendered once for SSR / first paint only. The engine owns the span's text
|
|
160
|
-
// after mount, so this must NOT be reactive - a reactive {{ value }} would make
|
|
161
|
-
// Vue re-patch the text node every frame and wipe the tween. The accessible
|
|
162
|
-
// name stays current through the reactive :aria-label binding.
|
|
163
|
-
const initialText = formatter.value.format(animateOnMount ? 0 : value);
|
|
164
|
-
|
|
165
|
-
// Screen readers always read the final, settled value rather than the
|
|
166
|
-
// intermediate frames streaming past on screen.
|
|
167
|
-
const accessibleValue = computed(() => formatter.value.format(value));
|
|
168
|
-
|
|
169
|
-
const labelRef = useTemplateRef('label');
|
|
170
|
-
|
|
171
|
-
let frame = 0;
|
|
172
|
-
let current = animateOnMount ? 0 : value;
|
|
173
|
-
|
|
174
191
|
function render(next: number): void {
|
|
175
192
|
current = next;
|
|
176
193
|
|
|
@@ -215,21 +232,4 @@
|
|
|
215
232
|
|
|
216
233
|
frame = requestAnimationFrame(step);
|
|
217
234
|
}
|
|
218
|
-
|
|
219
|
-
onMounted(() => {
|
|
220
|
-
if (animateOnMount) {
|
|
221
|
-
tween(0, value);
|
|
222
|
-
} else {
|
|
223
|
-
render(value);
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
// Tween from wherever the display currently sits, so a value that changes
|
|
228
|
-
// mid-tween keeps rolling smoothly instead of jumping.
|
|
229
|
-
watch(() => value, next => tween(current, next));
|
|
230
|
-
|
|
231
|
-
// Re-render in place when the locale or format changes.
|
|
232
|
-
watch(formatter, () => render(current));
|
|
233
|
-
|
|
234
|
-
onBeforeUnmount(cancel);
|
|
235
235
|
</script>
|
|
@@ -82,13 +82,35 @@
|
|
|
82
82
|
let revertTimeout: number | undefined;
|
|
83
83
|
let restingText: string | undefined;
|
|
84
84
|
|
|
85
|
-
|
|
85
|
+
watch(() => text, value => set(value));
|
|
86
|
+
|
|
87
|
+
onMounted(() => {
|
|
88
|
+
const element = labelRef.value;
|
|
89
|
+
|
|
90
|
+
if (element) {
|
|
91
|
+
buildSlotText(element, text);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
onBeforeUnmount(() => {
|
|
96
|
+
window.clearTimeout(revertTimeout);
|
|
97
|
+
|
|
98
|
+
const element = labelRef.value;
|
|
99
|
+
|
|
100
|
+
if (element) {
|
|
101
|
+
clearSlotText(element, text);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
function glyph(char: string): string {
|
|
106
|
+
return char === ' ' ? NBSP : char;
|
|
107
|
+
}
|
|
86
108
|
|
|
87
109
|
// Sweep the hue across the line so the roll lands as a chromatic spectrum.
|
|
88
|
-
|
|
110
|
+
function chromaticColor(index: number, total: number): string {
|
|
89
111
|
const t = total <= 1 ? 0 : index / (total - 1);
|
|
90
112
|
return `hsl(${(t * 320) % 360} 92% 60%)`;
|
|
91
|
-
}
|
|
113
|
+
}
|
|
92
114
|
|
|
93
115
|
function baseOptions(): AnimateOptions {
|
|
94
116
|
return {
|
|
@@ -430,26 +452,6 @@
|
|
|
430
452
|
}, revertAfter);
|
|
431
453
|
}
|
|
432
454
|
|
|
433
|
-
onMounted(() => {
|
|
434
|
-
const element = labelRef.value;
|
|
435
|
-
|
|
436
|
-
if (element) {
|
|
437
|
-
buildSlotText(element, text);
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
watch(() => text, value => set(value));
|
|
442
|
-
|
|
443
|
-
onBeforeUnmount(() => {
|
|
444
|
-
window.clearTimeout(revertTimeout);
|
|
445
|
-
|
|
446
|
-
const element = labelRef.value;
|
|
447
|
-
|
|
448
|
-
if (element) {
|
|
449
|
-
clearSlotText(element, text);
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
|
|
453
455
|
defineExpose({
|
|
454
456
|
flash,
|
|
455
457
|
set
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
fixed: boolean;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
const emit = defineEmits<{
|
|
26
|
+
finished: [];
|
|
27
|
+
}>();
|
|
28
|
+
|
|
25
29
|
const {
|
|
26
30
|
text,
|
|
27
31
|
characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
|
|
@@ -38,10 +42,6 @@
|
|
|
38
42
|
readonly stagger?: number;
|
|
39
43
|
}>();
|
|
40
44
|
|
|
41
|
-
const emit = defineEmits<{
|
|
42
|
-
finished: [];
|
|
43
|
-
}>();
|
|
44
|
-
|
|
45
45
|
// Rendered once for SSR / first paint only. The engine owns the span's text
|
|
46
46
|
// after mount, so this must NOT be reactive - a reactive {{ text }} would make
|
|
47
47
|
// Vue re-patch the text node every frame and wipe the scramble. The accessible
|
|
@@ -53,6 +53,10 @@
|
|
|
53
53
|
let frame = 0;
|
|
54
54
|
let currentText = text;
|
|
55
55
|
|
|
56
|
+
watch(() => text, value => set(value));
|
|
57
|
+
|
|
58
|
+
onBeforeUnmount(cancel);
|
|
59
|
+
|
|
56
60
|
function cancel(): void {
|
|
57
61
|
if (frame) {
|
|
58
62
|
cancelAnimationFrame(frame);
|
|
@@ -157,10 +161,6 @@
|
|
|
157
161
|
scramble(element, currentText, currentText, true);
|
|
158
162
|
}
|
|
159
163
|
|
|
160
|
-
watch(() => text, value => set(value));
|
|
161
|
-
|
|
162
|
-
onBeforeUnmount(cancel);
|
|
163
|
-
|
|
164
164
|
defineExpose({
|
|
165
165
|
replay,
|
|
166
166
|
set
|