@tombcato/smart-ticker 1.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.
@@ -0,0 +1,542 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Vue Ticker Demo</title>
8
+ <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Inter:wght@400;500;600;700&display=swap"
11
+ rel="stylesheet">
12
+ <style>
13
+ :root {
14
+ --bg: #0f172a;
15
+ --bg-card: #1e293b;
16
+ --text: #f8fafc;
17
+ --text-muted: #94a3b8;
18
+ --accent: #6366f1;
19
+ --accent-glow: rgba(99, 102, 241, 0.4);
20
+ --border: #334155;
21
+ }
22
+
23
+ body {
24
+ background-color: var(--bg);
25
+ color: var(--text);
26
+ font-family: 'Inter', sans-serif;
27
+ display: flex;
28
+ flex-direction: column;
29
+ align-items: center;
30
+ justify-content: center;
31
+ min-height: 100vh;
32
+ margin: 0;
33
+ overflow-x: hidden;
34
+ }
35
+
36
+ /* Price Hero Styles */
37
+ .price-hero {
38
+ width: 100%;
39
+ max-width: 1000px;
40
+ padding: 2rem;
41
+ text-align: center;
42
+ }
43
+
44
+ .price-label {
45
+ font-size: 1rem;
46
+ color: var(--text-muted);
47
+ margin-bottom: 0.5rem;
48
+ text-transform: uppercase;
49
+ letter-spacing: 2px;
50
+ }
51
+
52
+ .price-value {
53
+ font-family: 'JetBrains Mono', monospace;
54
+ font-size: 5rem;
55
+ font-weight: 700;
56
+ color: var(--text);
57
+ display: flex;
58
+ align-items: center;
59
+ justify-content: center;
60
+ margin-bottom: 3rem;
61
+ }
62
+
63
+ .price-currency {
64
+ font-size: 3rem;
65
+ margin-right: 0.25rem;
66
+ color: var(--text-muted);
67
+ }
68
+
69
+ @media (max-width: 768px) {
70
+ .price-value {
71
+ font-size: 3rem;
72
+ }
73
+
74
+ .price-currency {
75
+ font-size: 2rem;
76
+ }
77
+ }
78
+
79
+ /* Controls */
80
+ .price-controls {
81
+ display: flex;
82
+ gap: 2rem;
83
+ justify-content: center;
84
+ flex-wrap: wrap;
85
+ }
86
+
87
+ .control-group {
88
+ display: flex;
89
+ flex-direction: column;
90
+ align-items: center;
91
+ gap: 0.5rem;
92
+ background: rgba(30, 41, 59, 0.5);
93
+ padding: 1rem;
94
+ border-radius: 12px;
95
+ border: 1px solid var(--border);
96
+ }
97
+
98
+ .control-label {
99
+ font-size: 0.8rem;
100
+ color: var(--text-muted);
101
+ text-transform: uppercase;
102
+ letter-spacing: 1px;
103
+ }
104
+
105
+ .control-buttons {
106
+ display: flex;
107
+ gap: 0.4rem;
108
+ }
109
+
110
+ .control-buttons button {
111
+ padding: 0.4rem 0.8rem;
112
+ background: rgba(255, 255, 255, 0.05);
113
+ border: 1px solid var(--border);
114
+ border-radius: 6px;
115
+ color: var(--text);
116
+ cursor: pointer;
117
+ font-size: 0.8rem;
118
+ font-family: 'Inter', sans-serif;
119
+ transition: all 0.2s;
120
+ }
121
+
122
+ .control-buttons button:hover {
123
+ background: rgba(255, 255, 255, 0.1);
124
+ }
125
+
126
+ .control-buttons button.active {
127
+ background: var(--accent);
128
+ border-color: var(--accent);
129
+ box-shadow: 0 0 10px var(--accent-glow);
130
+ }
131
+
132
+ /* Ticker Component Styles */
133
+ .ticker {
134
+ display: inline-flex;
135
+ overflow: hidden;
136
+ font-family: 'JetBrains Mono', monospace;
137
+ font-weight: 600;
138
+ line-height: 1.2;
139
+ }
140
+
141
+ .ticker-column {
142
+ display: inline-block;
143
+ height: 1.2em;
144
+ overflow: hidden;
145
+ position: relative;
146
+ }
147
+
148
+ .ticker-char {
149
+ position: absolute;
150
+ top: 0;
151
+ left: 0;
152
+ right: 0;
153
+ height: 1.2em;
154
+ display: flex;
155
+ align-items: center;
156
+ justify-content: center;
157
+ white-space: pre;
158
+ }
159
+ </style>
160
+ </head>
161
+
162
+ <body>
163
+ <div id="app">
164
+ <div class="price-hero">
165
+ <div class="price-label">价格</div>
166
+ <div class="price-value">
167
+ <span class="price-currency">$</span>
168
+ <ticker-component :value="heroPrice" :duration="animDuration" :easing="easing" :char-width="charWidth"
169
+ :character-lists="['0123456789.,']"></ticker-component>
170
+ </div>
171
+
172
+ <div class="price-controls">
173
+ <div class="control-group">
174
+ <span class="control-label">字符宽度</span>
175
+ <div class="control-buttons">
176
+ <button v-for="w in widthOptions" :key="w" :class="{ active: charWidth === w }"
177
+ @click="charWidth = w">
178
+ {{ w }}x
179
+ </button>
180
+ </div>
181
+ </div>
182
+ <div class="control-group">
183
+ <span class="control-label">动画时长</span>
184
+ <div class="control-buttons">
185
+ <button v-for="d in durationOptions" :key="d" :class="{ active: animDuration === d }"
186
+ @click="animDuration = d">
187
+ {{ d }}ms
188
+ </button>
189
+ </div>
190
+ </div>
191
+ <div class="control-group">
192
+ <span class="control-label">缓动曲线</span>
193
+ <div class="control-buttons">
194
+ <button v-for="e in easingOptions" :key="e.name" :class="{ active: easing === e.name }"
195
+ @click="easing = e.name">
196
+ {{ e.label }}
197
+ </button>
198
+ </div>
199
+ </div>
200
+ </div>
201
+ </div>
202
+ </div>
203
+
204
+ <script>
205
+ const { createApp, ref, computed, watch, onUnmounted, onMounted } = Vue;
206
+
207
+ // ============================================================================
208
+ // Constants & Logic
209
+ // ============================================================================
210
+ const EMPTY_CHAR = '\0';
211
+
212
+ const easingFunctions = {
213
+ linear: (t) => t,
214
+ easeIn: (t) => t * t,
215
+ easeOut: (t) => 1 - (1 - t) * (1 - t),
216
+ easeInOut: (t) => t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2,
217
+ bounce: (t) => {
218
+ const n1 = 7.5625, d1 = 2.75;
219
+ if (t < 1 / d1) return n1 * t * t;
220
+ else if (t < 2 / d1) return n1 * (t -= 1.5 / d1) * t + 0.75;
221
+ else if (t < 2.5 / d1) return n1 * (t -= 2.25 / d1) * t + 0.9375;
222
+ else return n1 * (t -= 2.625 / d1) * t + 0.984375;
223
+ },
224
+ };
225
+
226
+ class TickerCharacterList {
227
+ constructor(characterList) {
228
+ const charsArray = characterList.split('');
229
+ const length = charsArray.length;
230
+ this.numOriginalCharacters = length;
231
+ this.characterIndicesMap = new Map();
232
+ for (let i = 0; i < length; i++) this.characterIndicesMap.set(charsArray[i], i);
233
+ this.characterList = new Array(length * 2 + 1);
234
+ this.characterList[0] = EMPTY_CHAR;
235
+ for (let i = 0; i < length; i++) {
236
+ this.characterList[1 + i] = charsArray[i];
237
+ this.characterList[1 + length + i] = charsArray[i];
238
+ }
239
+ }
240
+ getCharacterIndices(start, end, direction) {
241
+ let startIndex = this.getIndexOfChar(start);
242
+ let endIndex = this.getIndexOfChar(end);
243
+ if (startIndex < 0 || endIndex < 0) return null;
244
+ if (direction === 'DOWN') {
245
+ if (end === EMPTY_CHAR) endIndex = this.characterList.length;
246
+ else if (endIndex < startIndex) endIndex += this.numOriginalCharacters;
247
+ } else if (direction === 'UP') {
248
+ if (startIndex < endIndex) startIndex += this.numOriginalCharacters;
249
+ } else if (direction === 'ANY') {
250
+ if (start !== EMPTY_CHAR && end !== EMPTY_CHAR) {
251
+ if (endIndex < startIndex) {
252
+ const nonWrap = startIndex - endIndex;
253
+ const wrap = this.numOriginalCharacters - startIndex + endIndex;
254
+ if (wrap < nonWrap) endIndex += this.numOriginalCharacters;
255
+ } else if (startIndex < endIndex) {
256
+ const nonWrap = endIndex - startIndex;
257
+ const wrap = this.numOriginalCharacters - endIndex + startIndex;
258
+ if (wrap < nonWrap) startIndex += this.numOriginalCharacters;
259
+ }
260
+ }
261
+ }
262
+ return { startIndex, endIndex };
263
+ }
264
+ getSupportedCharacters() { return new Set(this.characterIndicesMap.keys()); }
265
+ getCharacterList() { return this.characterList; }
266
+ getIndexOfChar(c) {
267
+ if (c === EMPTY_CHAR) return 0;
268
+ if (this.characterIndicesMap.has(c)) return this.characterIndicesMap.get(c) + 1;
269
+ return -1;
270
+ }
271
+ }
272
+
273
+ const ACTION_SAME = 0, ACTION_INSERT = 1, ACTION_DELETE = 2;
274
+ function computeColumnActions(source, target, supported) {
275
+ let si = 0, ti = 0;
276
+ const actions = [];
277
+ while (true) {
278
+ const endS = si === source.length;
279
+ const endT = ti === target.length;
280
+ if (endS && endT) break;
281
+ if (endS) { for (; ti < target.length; ti++) actions.push(ACTION_INSERT); break; }
282
+ if (endT) { for (; si < source.length; si++) actions.push(ACTION_DELETE); break; }
283
+ const sSupp = supported.has(source[si]);
284
+ const tSupp = supported.has(target[ti]);
285
+ if (sSupp && tSupp) {
286
+ let se = si + 1, te = ti + 1;
287
+ while (se < source.length && supported.has(source[se])) se++;
288
+ while (te < target.length && supported.has(target[te])) te++;
289
+ const sLen = se - si, tLen = te - ti;
290
+ if (sLen === tLen) for (let i = 0; i < sLen; i++) actions.push(ACTION_SAME);
291
+ else {
292
+ const matrix = Array(sLen + 1).fill(null).map(() => Array(tLen + 1).fill(0));
293
+ for (let i = 0; i <= sLen; i++) matrix[i][0] = i;
294
+ for (let j = 0; j <= tLen; j++) matrix[0][j] = j;
295
+ for (let r = 1; r <= sLen; r++) {
296
+ for (let c = 1; c <= tLen; c++) {
297
+ const cost = source[si + r - 1] === target[ti + c - 1] ? 0 : 1;
298
+ matrix[r][c] = Math.min(matrix[r - 1][c] + 1, matrix[r][c - 1] + 1, matrix[r - 1][c - 1] + cost);
299
+ }
300
+ }
301
+ const result = [];
302
+ let r = sLen, c = tLen;
303
+ while (r > 0 || c > 0) {
304
+ if (r === 0) { result.push(ACTION_INSERT); c--; }
305
+ else if (c === 0) { result.push(ACTION_DELETE); r--; }
306
+ else {
307
+ const ins = matrix[r][c - 1], del = matrix[r - 1][c], rep = matrix[r - 1][c - 1];
308
+ if (ins < del && ins < rep) { result.push(ACTION_INSERT); c--; }
309
+ else if (del < rep) { result.push(ACTION_DELETE); r--; }
310
+ else { result.push(ACTION_SAME); r--; c--; }
311
+ }
312
+ }
313
+ for (let i = result.length - 1; i >= 0; i--) actions.push(result[i]);
314
+ }
315
+ si = se; ti = te;
316
+ } else if (sSupp) { actions.push(ACTION_INSERT); ti++; }
317
+ else if (tSupp) { actions.push(ACTION_DELETE); si++; }
318
+ else { actions.push(ACTION_SAME); si++; ti++; }
319
+ }
320
+ return actions;
321
+ }
322
+
323
+ function createColumn() {
324
+ return {
325
+ currentChar: EMPTY_CHAR, targetChar: EMPTY_CHAR, charList: null,
326
+ startIndex: 0, endIndex: 0, sourceWidth: 0, currentWidth: 0, targetWidth: 0,
327
+ directionAdj: 1, prevDelta: 0, currDelta: 0,
328
+ };
329
+ }
330
+
331
+ function setTarget(col, target, lists, dir) {
332
+ const c = { ...col };
333
+ c.targetChar = target;
334
+ c.sourceWidth = c.currentWidth;
335
+ c.targetWidth = target === EMPTY_CHAR ? 0 : 1;
336
+ let found = false;
337
+ for (const list of lists) {
338
+ const indices = list.getCharacterIndices(c.currentChar, target, dir);
339
+ if (indices) {
340
+ c.charList = list.getCharacterList();
341
+ c.startIndex = indices.startIndex;
342
+ c.endIndex = indices.endIndex;
343
+ found = true;
344
+ break;
345
+ }
346
+ }
347
+ if (!found) {
348
+ c.charList = c.currentChar === target ? [c.currentChar] : [c.currentChar, target];
349
+ c.startIndex = 0;
350
+ c.endIndex = c.currentChar === target ? 0 : 1;
351
+ }
352
+ c.directionAdj = c.endIndex >= c.startIndex ? 1 : -1;
353
+ c.prevDelta = c.currDelta;
354
+ c.currDelta = 0;
355
+ return c;
356
+ }
357
+
358
+ function applyProgress(col, progress, forceUpdate = false) {
359
+ const c = { ...col };
360
+ const total = Math.abs(c.endIndex - c.startIndex);
361
+ const pos = progress * total;
362
+ const offset = pos - Math.floor(pos);
363
+ const additional = c.prevDelta * (1 - progress);
364
+ const delta = offset * c.directionAdj + additional;
365
+ const charIdx = c.startIndex + Math.floor(pos) * c.directionAdj;
366
+ if (progress >= 1) {
367
+ c.currentChar = c.targetChar;
368
+ c.currDelta = 0;
369
+ c.prevDelta = 0;
370
+ } else if (forceUpdate && c.charList && charIdx >= 0 && charIdx < c.charList.length) {
371
+ c.currentChar = c.charList[charIdx];
372
+ c.currDelta = delta;
373
+ }
374
+ c.currentWidth = c.sourceWidth + (c.targetWidth - c.sourceWidth) * progress;
375
+ return { col: c, charIdx, delta };
376
+ }
377
+
378
+ const TickerComponent = {
379
+ template: `
380
+ <div class="ticker" :class="className">
381
+ <div v-for="(col, i) in renderedColumns" :key="i" class="ticker-column" :style="{ width: col.width + 'em' }">
382
+ <div v-for="charObj in col.chars" :key="charObj.key" class="ticker-char" :style="{ transform: 'translateY(' + charObj.offset + 'em)' }">
383
+ {{ charObj.char === '${EMPTY_CHAR}' ? '\u00A0' : charObj.char }}
384
+ </div>
385
+ </div>
386
+ </div>
387
+ `,
388
+ props: {
389
+ value: { type: String, required: true },
390
+ characterLists: { type: Array, default: () => ['0123456789'] },
391
+ duration: { type: Number, default: 500 },
392
+ direction: { type: String, default: 'ANY' },
393
+ easing: { type: String, default: 'easeInOut' },
394
+ className: { type: String, default: '' },
395
+ charWidth: { type: Number, default: 1 },
396
+ },
397
+ setup(props) {
398
+ const columns = ref([]);
399
+ const progress = ref(1);
400
+ let animId;
401
+
402
+ const lists = computed(() => props.characterLists.map(s => new TickerCharacterList(s)));
403
+ const supported = computed(() => {
404
+ const set = new Set();
405
+ lists.value.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));
406
+ return set;
407
+ });
408
+
409
+ watch(() => props.value, (newValue, oldValue) => {
410
+ if (newValue === oldValue) return;
411
+ if (animId) { cancelAnimationFrame(animId); animId = undefined; }
412
+
413
+ let currentCols = columns.value;
414
+ if (progress.value < 1 && progress.value > 0) {
415
+ currentCols = currentCols.map(c => applyProgress(c, progress.value, true).col);
416
+ }
417
+
418
+ const targetChars = newValue.split('');
419
+ const sourceChars = currentCols.map(c => c.currentChar);
420
+ const actions = computeColumnActions(sourceChars, targetChars, supported.value);
421
+ let ci = 0, ti = 0;
422
+ const result = [];
423
+ const validCols = currentCols.filter(c => c.currentWidth > 0);
424
+
425
+ for (const action of actions) {
426
+ if (action === ACTION_INSERT) result.push(setTarget(createColumn(), targetChars[ti++], lists.value, props.direction));
427
+ else if (action === ACTION_SAME) {
428
+ const existing = validCols[ci++] || createColumn();
429
+ result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));
430
+ } else {
431
+ const existing = validCols[ci++] || createColumn();
432
+ result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));
433
+ }
434
+ }
435
+
436
+ columns.value = result;
437
+ progress.value = 0;
438
+ const start = performance.now();
439
+ const dur = props.duration;
440
+ const easeFn = easingFunctions[props.easing] || easingFunctions.linear;
441
+ let lastUpdate = 0;
442
+
443
+ const animate = (now) => {
444
+ const linearP = Math.min((now - start) / dur, 1);
445
+ const p = easeFn(linearP);
446
+ if (now - lastUpdate >= 16 || linearP >= 1) {
447
+ lastUpdate = now;
448
+ progress.value = p;
449
+ }
450
+ if (linearP < 1) animId = requestAnimationFrame(animate);
451
+ else {
452
+ const final = columns.value.map(c => applyProgress(c, 1).col).filter(c => c.currentWidth > 0);
453
+ columns.value = final;
454
+ progress.value = 1;
455
+ animId = undefined;
456
+ }
457
+ };
458
+ animId = requestAnimationFrame(animate);
459
+ }, { immediate: true });
460
+
461
+ onUnmounted(() => { if (animId) cancelAnimationFrame(animId); });
462
+
463
+ const charHeight = 1.2;
464
+ const renderedColumns = computed(() => {
465
+ return columns.value.map(col => {
466
+ const { charIdx, delta } = applyProgress(col, progress.value);
467
+ const logicalWidth = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;
468
+ const width = logicalWidth * props.charWidth * 0.8;
469
+ if (width <= 0) return { width, chars: [] };
470
+ const chars = [];
471
+ const list = col.charList || [];
472
+ const deltaEm = delta * charHeight;
473
+ const add = (idx, offsetMod, keyPrefix) => {
474
+ if (idx >= 0 && idx < list.length) {
475
+ chars.push({ key: keyPrefix + '-' + idx, char: list[idx], offset: deltaEm + offsetMod });
476
+ }
477
+ };
478
+ add(charIdx, 0, 'c');
479
+ add(charIdx + 1, -charHeight, 'n');
480
+ add(charIdx - 1, charHeight, 'p');
481
+ return { width, chars };
482
+ }).filter(c => c.width > 0);
483
+ });
484
+
485
+ return { renderedColumns, EMPTY_CHAR };
486
+ }
487
+ };
488
+
489
+ createApp({
490
+ components: { TickerComponent },
491
+ setup() {
492
+ const priceSequence = ['73.18', '76.58', '173.50', '9.1'];
493
+ const heroPrice = ref(priceSequence[0]);
494
+ const animDuration = ref(800);
495
+ const easing = ref('easeInOut');
496
+ const charWidth = ref(1);
497
+ const durationOptions = [400, 800, 1200];
498
+ const widthOptions = [0.8, 1, 1.2];
499
+ const easingOptions = [
500
+ { name: 'linear', label: '线性' },
501
+ { name: 'easeInOut', label: '先加后减' },
502
+ { name: 'bounce', label: '回弹' },
503
+ ];
504
+
505
+ let interval;
506
+
507
+ watch(animDuration, (newDur) => {
508
+ if (interval) clearInterval(interval);
509
+ startInterval();
510
+ });
511
+
512
+ const startInterval = () => {
513
+ let priceIdx = 0;
514
+ interval = setInterval(() => {
515
+ priceIdx = (priceIdx + 1) % priceSequence.length;
516
+ heroPrice.value = priceSequence[priceIdx];
517
+ }, animDuration.value + 1000);
518
+ };
519
+
520
+ onMounted(() => {
521
+ startInterval();
522
+ });
523
+
524
+ onUnmounted(() => {
525
+ if (interval) clearInterval(interval);
526
+ });
527
+
528
+ return {
529
+ heroPrice,
530
+ animDuration,
531
+ easing,
532
+ charWidth,
533
+ durationOptions,
534
+ widthOptions,
535
+ easingOptions
536
+ };
537
+ }
538
+ }).mount('#app');
539
+ </script>
540
+ </body>
541
+
542
+ </html>
package/dist/vue.cjs ADDED
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const vue = require("vue");
4
+ const TickerCore = require("./TickerCore-DSrG8V7Z.cjs");
5
+ const charHeight = 1.2;
6
+ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
7
+ __name: "Ticker",
8
+ props: {
9
+ value: { type: String, required: true },
10
+ characterLists: { type: Array, default: () => ["0123456789"] },
11
+ duration: { type: Number, default: 500 },
12
+ direction: { type: String, default: "ANY" },
13
+ easing: { type: String, default: "easeInOut" },
14
+ className: { type: String, default: "" }
15
+ },
16
+ setup(__props) {
17
+ const props = __props;
18
+ const columns = vue.ref([]);
19
+ const progress = vue.ref(1);
20
+ let animId;
21
+ const lists = vue.computed(() => props.characterLists.map((s) => new TickerCore.TickerCharacterList(s)));
22
+ const supported = vue.computed(() => {
23
+ const set = /* @__PURE__ */ new Set();
24
+ lists.value.forEach((l) => l.getSupportedCharacters().forEach((c) => set.add(c)));
25
+ return set;
26
+ });
27
+ vue.watch(() => props.value, (newValue, oldValue) => {
28
+ if (newValue === oldValue) return;
29
+ if (animId) {
30
+ cancelAnimationFrame(animId);
31
+ animId = void 0;
32
+ }
33
+ let currentCols = columns.value;
34
+ if (progress.value < 1 && progress.value > 0) {
35
+ currentCols = currentCols.map((c) => TickerCore.applyProgress(c, progress.value, true).col);
36
+ }
37
+ const targetChars = newValue.split("");
38
+ const sourceChars = currentCols.map((c) => c.currentChar);
39
+ const actions = TickerCore.computeColumnActions(sourceChars, targetChars, supported.value);
40
+ let ci = 0, ti = 0;
41
+ const result = [];
42
+ const validCols = currentCols.filter((c) => c.currentWidth > 0);
43
+ for (const action of actions) {
44
+ if (action === TickerCore.ACTION_INSERT) {
45
+ result.push(TickerCore.setTarget(TickerCore.createColumn(), targetChars[ti++], lists.value, props.direction));
46
+ } else if (action === TickerCore.ACTION_SAME) {
47
+ const existing = validCols[ci++] || TickerCore.createColumn();
48
+ result.push(TickerCore.setTarget(existing, targetChars[ti++], lists.value, props.direction));
49
+ } else {
50
+ const existing = validCols[ci++] || TickerCore.createColumn();
51
+ result.push(TickerCore.setTarget(existing, TickerCore.EMPTY_CHAR, lists.value, props.direction));
52
+ }
53
+ }
54
+ columns.value = result;
55
+ progress.value = 0;
56
+ const start = performance.now();
57
+ const dur = props.duration;
58
+ const easeFn = TickerCore.easingFunctions[props.easing] || TickerCore.easingFunctions.linear;
59
+ let lastUpdate = 0;
60
+ const animate = (now) => {
61
+ const linearP = Math.min((now - start) / dur, 1);
62
+ const p = easeFn(linearP);
63
+ const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;
64
+ if (shouldUpdate) {
65
+ lastUpdate = now;
66
+ progress.value = p;
67
+ }
68
+ if (linearP < 1) {
69
+ animId = requestAnimationFrame(animate);
70
+ } else {
71
+ const final = columns.value.map((c) => TickerCore.applyProgress(c, 1).col).filter((c) => c.currentWidth > 0);
72
+ columns.value = final;
73
+ progress.value = 1;
74
+ animId = void 0;
75
+ }
76
+ };
77
+ animId = requestAnimationFrame(animate);
78
+ }, { immediate: true });
79
+ vue.onUnmounted(() => {
80
+ if (animId) cancelAnimationFrame(animId);
81
+ });
82
+ const renderedColumns = vue.computed(() => {
83
+ return columns.value.map((col) => {
84
+ const { charIdx, delta } = TickerCore.applyProgress(col, progress.value);
85
+ const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;
86
+ if (width <= 0) return { width, chars: [] };
87
+ const chars = [];
88
+ const list = col.charList || [];
89
+ const deltaEm = delta * charHeight;
90
+ const add = (idx, offsetMod, keyPrefix) => {
91
+ if (idx >= 0 && idx < list.length) {
92
+ chars.push({
93
+ key: `${keyPrefix}-${idx}`,
94
+ char: list[idx],
95
+ offset: deltaEm + offsetMod
96
+ });
97
+ }
98
+ };
99
+ add(charIdx, 0, "c");
100
+ add(charIdx + 1, -charHeight, "n");
101
+ add(charIdx - 1, charHeight, "p");
102
+ return { width, chars };
103
+ }).filter((c) => c.width > 0);
104
+ });
105
+ return (_ctx, _cache) => {
106
+ return vue.openBlock(), vue.createElementBlock("div", {
107
+ class: vue.normalizeClass(["ticker", __props.className])
108
+ }, [
109
+ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(renderedColumns.value, (col, i) => {
110
+ return vue.openBlock(), vue.createElementBlock("div", {
111
+ key: i,
112
+ class: "ticker-column",
113
+ style: vue.normalizeStyle({ width: `${col.width}em` })
114
+ }, [
115
+ (vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(col.chars, (charObj) => {
116
+ return vue.openBlock(), vue.createElementBlock("div", {
117
+ key: charObj.key,
118
+ class: "ticker-char",
119
+ style: vue.normalizeStyle({ transform: `translateY(${charObj.offset}em)` })
120
+ }, vue.toDisplayString(charObj.char === vue.unref(TickerCore.EMPTY_CHAR) ? " " : charObj.char), 5);
121
+ }), 128))
122
+ ], 4);
123
+ }), 128))
124
+ ], 2);
125
+ };
126
+ }
127
+ });
128
+ const _export_sfc = (sfc, props) => {
129
+ const target = sfc.__vccOpts || sfc;
130
+ for (const [key, val] of props) {
131
+ target[key] = val;
132
+ }
133
+ return target;
134
+ };
135
+ const Ticker = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-d7db53e5"]]);
136
+ exports.ACTION_DELETE = TickerCore.ACTION_DELETE;
137
+ exports.ACTION_INSERT = TickerCore.ACTION_INSERT;
138
+ exports.ACTION_SAME = TickerCore.ACTION_SAME;
139
+ exports.EMPTY_CHAR = TickerCore.EMPTY_CHAR;
140
+ exports.TickerCharacterList = TickerCore.TickerCharacterList;
141
+ exports.TickerUtils = TickerCore.TickerUtils;
142
+ exports.applyProgress = TickerCore.applyProgress;
143
+ exports.computeColumnActions = TickerCore.computeColumnActions;
144
+ exports.createColumn = TickerCore.createColumn;
145
+ exports.easingFunctions = TickerCore.easingFunctions;
146
+ exports.setTarget = TickerCore.setTarget;
147
+ exports.Ticker = Ticker;
148
+ //# sourceMappingURL=vue.cjs.map