@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.
- package/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/TickerCore-DDdYkrsq.js +244 -0
- package/dist/TickerCore-DDdYkrsq.js.map +1 -0
- package/dist/TickerCore-DSrG8V7Z.cjs +243 -0
- package/dist/TickerCore-DSrG8V7Z.cjs.map +1 -0
- package/dist/index.cjs +153 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.js +154 -0
- package/dist/index.js.map +1 -0
- package/dist/logo-dark.svg +7 -0
- package/dist/logo.svg +7 -0
- package/dist/style.css +53 -0
- package/dist/vue-demo.html +542 -0
- package/dist/vue.cjs +148 -0
- package/dist/vue.cjs.map +1 -0
- package/dist/vue.d.ts +122 -0
- package/dist/vue.js +149 -0
- package/dist/vue.js.map +1 -0
- package/package.json +75 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import React, { useState, useRef, useMemo, useEffect } from "react";
|
|
3
|
+
import { T as TickerUtils, a as TickerCharacterList, b as applyProgress, c as computeColumnActions, A as ACTION_INSERT, s as setTarget, d as createColumn, e as ACTION_SAME, E as EMPTY_CHAR, f as easingFunctions } from "./TickerCore-DDdYkrsq.js";
|
|
4
|
+
import { g } from "./TickerCore-DDdYkrsq.js";
|
|
5
|
+
const Ticker = ({
|
|
6
|
+
value,
|
|
7
|
+
characterLists: charListStrings = [TickerUtils.provideNumberList()],
|
|
8
|
+
duration = 500,
|
|
9
|
+
direction = "ANY",
|
|
10
|
+
easing = "easeInOut",
|
|
11
|
+
// Robinhood 默认: AccelerateDecelerateInterpolator
|
|
12
|
+
className = "",
|
|
13
|
+
charWidth = 1
|
|
14
|
+
}) => {
|
|
15
|
+
const [columns, setColumns] = useState([]);
|
|
16
|
+
const [progress, setProgress] = useState(1);
|
|
17
|
+
const animRef = useRef();
|
|
18
|
+
const colsRef = useRef([]);
|
|
19
|
+
const progressRef = useRef(1);
|
|
20
|
+
const isFirstRef = useRef(true);
|
|
21
|
+
const prevValueRef = useRef("");
|
|
22
|
+
const lists = useMemo(() => charListStrings.map((s) => new TickerCharacterList(s)), [charListStrings]);
|
|
23
|
+
const supported = useMemo(() => {
|
|
24
|
+
const set = /* @__PURE__ */ new Set();
|
|
25
|
+
lists.forEach((l) => l.getSupportedCharacters().forEach((c) => set.add(c)));
|
|
26
|
+
return set;
|
|
27
|
+
}, [lists]);
|
|
28
|
+
const listsRef = useRef(lists);
|
|
29
|
+
const supportedRef = useRef(supported);
|
|
30
|
+
const directionRef = useRef(direction);
|
|
31
|
+
const durationRef = useRef(duration);
|
|
32
|
+
const easingRef = useRef(easing);
|
|
33
|
+
listsRef.current = lists;
|
|
34
|
+
supportedRef.current = supported;
|
|
35
|
+
directionRef.current = direction;
|
|
36
|
+
durationRef.current = duration;
|
|
37
|
+
easingRef.current = easing;
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (value === prevValueRef.current) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
prevValueRef.current = value;
|
|
43
|
+
if (animRef.current) {
|
|
44
|
+
cancelAnimationFrame(animRef.current);
|
|
45
|
+
animRef.current = void 0;
|
|
46
|
+
}
|
|
47
|
+
let currentCols = colsRef.current;
|
|
48
|
+
if (progressRef.current < 1 && progressRef.current > 0) {
|
|
49
|
+
currentCols = currentCols.map((c) => applyProgress(c, progressRef.current, true).col);
|
|
50
|
+
colsRef.current = currentCols;
|
|
51
|
+
}
|
|
52
|
+
const targetChars = value.split("");
|
|
53
|
+
const sourceChars = currentCols.map((c) => c.currentChar);
|
|
54
|
+
const actions = computeColumnActions(sourceChars, targetChars, supportedRef.current);
|
|
55
|
+
let ci = 0, ti = 0;
|
|
56
|
+
const result = [];
|
|
57
|
+
const validCols = currentCols.filter((c) => c.currentWidth > 0);
|
|
58
|
+
for (const action of actions) {
|
|
59
|
+
if (action === ACTION_INSERT) {
|
|
60
|
+
result.push(setTarget(createColumn(), targetChars[ti++], listsRef.current, directionRef.current));
|
|
61
|
+
} else if (action === ACTION_SAME) {
|
|
62
|
+
const existing = validCols[ci++] || createColumn();
|
|
63
|
+
result.push(setTarget(existing, targetChars[ti++], listsRef.current, directionRef.current));
|
|
64
|
+
} else {
|
|
65
|
+
const existing = validCols[ci++] || createColumn();
|
|
66
|
+
result.push(setTarget(existing, EMPTY_CHAR, listsRef.current, directionRef.current));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
colsRef.current = result;
|
|
70
|
+
setColumns(result);
|
|
71
|
+
if (!isFirstRef.current && result.length > 0) {
|
|
72
|
+
progressRef.current = 0;
|
|
73
|
+
setProgress(0);
|
|
74
|
+
const start = performance.now();
|
|
75
|
+
const dur = durationRef.current;
|
|
76
|
+
const easeFn = easingFunctions[easingRef.current] || easingFunctions.linear;
|
|
77
|
+
let lastUpdate = 0;
|
|
78
|
+
const animate = (now) => {
|
|
79
|
+
const linearP = Math.min((now - start) / dur, 1);
|
|
80
|
+
const p = easeFn(linearP);
|
|
81
|
+
progressRef.current = p;
|
|
82
|
+
if (now - lastUpdate >= 16 || linearP >= 1) {
|
|
83
|
+
lastUpdate = now;
|
|
84
|
+
setProgress(p);
|
|
85
|
+
}
|
|
86
|
+
if (linearP < 1) {
|
|
87
|
+
animRef.current = requestAnimationFrame(animate);
|
|
88
|
+
} else {
|
|
89
|
+
const final = colsRef.current.map((c) => applyProgress(c, 1).col).filter((c) => c.currentWidth > 0);
|
|
90
|
+
colsRef.current = final;
|
|
91
|
+
setColumns(final);
|
|
92
|
+
animRef.current = void 0;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
animRef.current = requestAnimationFrame(animate);
|
|
96
|
+
} else {
|
|
97
|
+
isFirstRef.current = false;
|
|
98
|
+
progressRef.current = 1;
|
|
99
|
+
setProgress(1);
|
|
100
|
+
const final = result.map((c) => applyProgress(c, 1).col).filter((c) => c.currentWidth > 0);
|
|
101
|
+
colsRef.current = final;
|
|
102
|
+
setColumns(final);
|
|
103
|
+
}
|
|
104
|
+
return () => {
|
|
105
|
+
if (animRef.current) cancelAnimationFrame(animRef.current);
|
|
106
|
+
};
|
|
107
|
+
}, [value]);
|
|
108
|
+
const charHeight = 1.2;
|
|
109
|
+
const rendered = useMemo(() => {
|
|
110
|
+
return columns.map((col, i) => {
|
|
111
|
+
const { charIdx, delta } = applyProgress(col, progress);
|
|
112
|
+
const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress;
|
|
113
|
+
if (width <= 0) return null;
|
|
114
|
+
const chars = [];
|
|
115
|
+
const list = col.charList || [];
|
|
116
|
+
const deltaEm = delta * charHeight;
|
|
117
|
+
if (charIdx >= 0 && charIdx < list.length) {
|
|
118
|
+
chars.push(
|
|
119
|
+
/* @__PURE__ */ jsx("div", { className: "ticker-char", style: { transform: `translateY(${deltaEm}em)` }, children: list[charIdx] === EMPTY_CHAR ? " " : list[charIdx] }, `c-${charIdx}`)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const nextIdx = charIdx + 1;
|
|
123
|
+
if (nextIdx >= 0 && nextIdx < list.length) {
|
|
124
|
+
chars.push(
|
|
125
|
+
/* @__PURE__ */ jsx("div", { className: "ticker-char", style: { transform: `translateY(${deltaEm - charHeight}em)` }, children: list[nextIdx] === EMPTY_CHAR ? " " : list[nextIdx] }, `n-${nextIdx}`)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
const prevIdx = charIdx - 1;
|
|
129
|
+
if (prevIdx >= 0 && prevIdx < list.length) {
|
|
130
|
+
chars.push(
|
|
131
|
+
/* @__PURE__ */ jsx("div", { className: "ticker-char", style: { transform: `translateY(${deltaEm + charHeight}em)` }, children: list[prevIdx] === EMPTY_CHAR ? " " : list[prevIdx] }, `p-${prevIdx}`)
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return /* @__PURE__ */ jsx("div", { className: "ticker-column", style: { width: `${width * 0.8 * charWidth}em` }, children: chars }, i);
|
|
135
|
+
});
|
|
136
|
+
}, [columns, progress, charWidth]);
|
|
137
|
+
return /* @__PURE__ */ jsx("div", { className: `ticker ${className}`.trim(), children: rendered });
|
|
138
|
+
};
|
|
139
|
+
React.memo(Ticker);
|
|
140
|
+
export {
|
|
141
|
+
g as ACTION_DELETE,
|
|
142
|
+
ACTION_INSERT,
|
|
143
|
+
ACTION_SAME,
|
|
144
|
+
EMPTY_CHAR,
|
|
145
|
+
Ticker,
|
|
146
|
+
TickerCharacterList,
|
|
147
|
+
TickerUtils,
|
|
148
|
+
applyProgress,
|
|
149
|
+
computeColumnActions,
|
|
150
|
+
createColumn,
|
|
151
|
+
easingFunctions,
|
|
152
|
+
setTarget
|
|
153
|
+
};
|
|
154
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/components/Ticker.tsx"],"sourcesContent":["/**\r\n * Ticker - 直接翻译自 Robinhood Android Ticker\r\n * https://github.com/robinhood/ticker\r\n */\r\nimport React, { useEffect, useRef, useState, useMemo } from 'react';\r\nimport './Ticker.css';\r\nimport {\r\n TickerUtils,\r\n TickerCharacterList,\r\n ScrollingDirection,\r\n ColumnState,\r\n EMPTY_CHAR,\r\n computeColumnActions,\r\n createColumn,\r\n setTarget,\r\n applyProgress,\r\n easingFunctions,\r\n ACTION_INSERT,\r\n ACTION_SAME,\r\n} from '../core/TickerCore';\r\n\r\n// 导出 Utils 供外部使用\r\nexport { TickerUtils };\r\n\r\n// ============================================================================\r\n// Ticker Component\r\n// ============================================================================\r\nexport interface TickerProps {\r\n value: string;\r\n characterLists?: string[];\r\n duration?: number;\r\n direction?: ScrollingDirection;\r\n easing?: string;\r\n className?: string;\r\n charWidth?: number;\r\n}\r\n\r\nexport const Ticker: React.FC<TickerProps> = ({\r\n value,\r\n characterLists: charListStrings = [TickerUtils.provideNumberList()],\r\n duration = 500,\r\n direction = 'ANY',\r\n easing = 'easeInOut', // Robinhood 默认: AccelerateDecelerateInterpolator\r\n className = '',\r\n charWidth = 1,\r\n}) => {\r\n const [columns, setColumns] = useState<ColumnState[]>([]);\r\n const [progress, setProgress] = useState(1);\r\n const animRef = useRef<number>();\r\n const colsRef = useRef<ColumnState[]>([]);\r\n const progressRef = useRef(1);\r\n const isFirstRef = useRef(true);\r\n const prevValueRef = useRef('');\r\n\r\n const lists = useMemo(() => charListStrings.map(s => new TickerCharacterList(s)), [charListStrings]);\r\n const supported = useMemo(() => {\r\n const set = new Set<string>();\r\n lists.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n }, [lists]);\r\n\r\n // 用 ref 存储依赖项,避免 useEffect 因为这些依赖重复触发\r\n const listsRef = useRef(lists);\r\n const supportedRef = useRef(supported);\r\n const directionRef = useRef(direction);\r\n const durationRef = useRef(duration);\r\n const easingRef = useRef(easing);\r\n listsRef.current = lists;\r\n supportedRef.current = supported;\r\n directionRef.current = direction;\r\n durationRef.current = duration;\r\n easingRef.current = easing;\r\n\r\n // 主要逻辑:value 变化时处理\r\n useEffect(() => {\r\n // 防止相同 value 重复触发(React StrictMode)\r\n if (value === prevValueRef.current) {\r\n return;\r\n }\r\n prevValueRef.current = value;\r\n\r\n // 取消正在进行的动画\r\n if (animRef.current) {\r\n cancelAnimationFrame(animRef.current);\r\n animRef.current = undefined;\r\n }\r\n\r\n // 如果动画进行中,先用当前进度更新列状态\r\n let currentCols = colsRef.current;\r\n if (progressRef.current < 1 && progressRef.current > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progressRef.current, true).col);\r\n colsRef.current = currentCols;\r\n }\r\n\r\n const targetChars = value.split('');\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supportedRef.current);\r\n\r\n let ci = 0, ti = 0;\r\n const result: ColumnState[] = [];\r\n const validCols = currentCols.filter(c => c.currentWidth > 0);\r\n\r\n for (const action of actions) {\r\n if (action === ACTION_INSERT) {\r\n result.push(setTarget(createColumn(), targetChars[ti++], listsRef.current, directionRef.current));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], listsRef.current, directionRef.current));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, listsRef.current, directionRef.current));\r\n }\r\n }\r\n\r\n colsRef.current = result;\r\n setColumns(result);\r\n\r\n // 动画\r\n if (!isFirstRef.current && result.length > 0) {\r\n progressRef.current = 0;\r\n setProgress(0);\r\n const start = performance.now();\r\n const dur = durationRef.current;\r\n const easeFn = easingFunctions[easingRef.current] || easingFunctions.linear;\r\n let lastUpdate = 0;\r\n\r\n const animate = (now: number) => {\r\n const linearP = Math.min((now - start) / dur, 1);\r\n const p = easeFn(linearP); // 应用 easing 函数\r\n progressRef.current = p;\r\n\r\n // 节流:每 16ms 最多更新一次视图 (约 60fps)\r\n if (now - lastUpdate >= 16 || linearP >= 1) {\r\n lastUpdate = now;\r\n setProgress(p);\r\n }\r\n\r\n if (linearP < 1) {\r\n animRef.current = requestAnimationFrame(animate);\r\n } else {\r\n // 动画结束\r\n const final = colsRef.current\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n colsRef.current = final;\r\n setColumns(final);\r\n animRef.current = undefined;\r\n }\r\n };\r\n animRef.current = requestAnimationFrame(animate);\r\n } else {\r\n // 首次渲染,直接显示\r\n isFirstRef.current = false;\r\n progressRef.current = 1;\r\n setProgress(1);\r\n const final = result.map(c => applyProgress(c, 1).col).filter(c => c.currentWidth > 0);\r\n colsRef.current = final;\r\n setColumns(final);\r\n }\r\n\r\n return () => {\r\n if (animRef.current) cancelAnimationFrame(animRef.current);\r\n };\r\n }, [value]); // 只依赖 value\r\n\r\n // 渲染\r\n const charHeight = 1.2; // 与 CSS line-height 匹配\r\n\r\n const rendered = useMemo(() => {\r\n return columns.map((col, i) => {\r\n const { charIdx, delta } = applyProgress(col, progress);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress;\r\n if (width <= 0) return null;\r\n\r\n const chars: React.ReactNode[] = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // 当前字符\r\n if (charIdx >= 0 && charIdx < list.length) {\r\n chars.push(\r\n <div key={`c-${charIdx}`} className=\"ticker-char\" style={{ transform: `translateY(${deltaEm}em)` }}>\r\n {list[charIdx] === EMPTY_CHAR ? '\\u00A0' : list[charIdx]}\r\n </div>\r\n );\r\n }\r\n // 下一个字符\r\n const nextIdx = charIdx + 1;\r\n if (nextIdx >= 0 && nextIdx < list.length) {\r\n chars.push(\r\n <div key={`n-${nextIdx}`} className=\"ticker-char\" style={{ transform: `translateY(${deltaEm - charHeight}em)` }}>\r\n {list[nextIdx] === EMPTY_CHAR ? '\\u00A0' : list[nextIdx]}\r\n </div>\r\n );\r\n }\r\n // 上一个字符(处理中断)\r\n const prevIdx = charIdx - 1;\r\n if (prevIdx >= 0 && prevIdx < list.length) {\r\n chars.push(\r\n <div key={`p-${prevIdx}`} className=\"ticker-char\" style={{ transform: `translateY(${deltaEm + charHeight}em)` }}>\r\n {list[prevIdx] === EMPTY_CHAR ? '\\u00A0' : list[prevIdx]}\r\n </div>\r\n );\r\n }\r\n\r\n // 基准宽度 0.8em * 倍率\r\n return (\r\n <div key={i} className=\"ticker-column\" style={{ width: `${width * 0.8 * charWidth}em` }}>\r\n {chars}\r\n </div>\r\n );\r\n });\r\n }, [columns, progress, charWidth]);\r\n\r\n return <div className={`ticker ${className}`.trim()}>{rendered}</div>;\r\n};\r\n\r\nexport default React.memo(Ticker);\r\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect x="6" y="8" width="5" height="16" rx="2.5" fill="#888888" fill-opacity="0.5" />
|
|
3
|
+
<rect x="13.5" y="4" width="5" height="16" rx="2.5" fill="#f0f0f5">
|
|
4
|
+
<animate attributeName="y" values="4; 12; 4" dur="4s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.2 1; 0.4 0 0.2 1" />
|
|
5
|
+
</rect>
|
|
6
|
+
<rect x="21" y="10" width="5" height="16" rx="2.5" fill="#888888" fill-opacity="0.8" />
|
|
7
|
+
</svg>
|
package/dist/logo.svg
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect x="6" y="8" width="5" height="16" rx="2.5" fill="#888888" fill-opacity="0.3" />
|
|
3
|
+
<rect x="13.5" y="4" width="5" height="16" rx="2.5" fill="#202022">
|
|
4
|
+
<animate attributeName="y" values="4; 12; 4" dur="4s" repeatCount="indefinite" calcMode="spline" keySplines="0.4 0 0.2 1; 0.4 0 0.2 1" />
|
|
5
|
+
</rect>
|
|
6
|
+
<rect x="21" y="10" width="5" height="16" rx="2.5" fill="#888888" fill-opacity="0.6" />
|
|
7
|
+
</svg>
|
package/dist/style.css
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
.ticker {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
overflow: hidden;
|
|
4
|
+
font-family: 'JetBrains Mono', monospace;
|
|
5
|
+
font-weight: 600;
|
|
6
|
+
line-height: 1.2;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.ticker-column {
|
|
10
|
+
display: inline-block;
|
|
11
|
+
height: 1.2em;
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
position: relative;
|
|
14
|
+
/* 确保字符边缘不会露出 */
|
|
15
|
+
clip-path: inset(0);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.ticker-char {
|
|
19
|
+
position: absolute;
|
|
20
|
+
top: 0;
|
|
21
|
+
left: 0;
|
|
22
|
+
right: 0;
|
|
23
|
+
height: 1.2em;
|
|
24
|
+
display: flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
white-space: pre;
|
|
28
|
+
}
|
|
29
|
+
.ticker[data-v-d7db53e5] {
|
|
30
|
+
display: inline-flex;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
font-family: 'JetBrains Mono', monospace;
|
|
33
|
+
font-weight: 600;
|
|
34
|
+
line-height: 1.2;
|
|
35
|
+
}
|
|
36
|
+
.ticker-column[data-v-d7db53e5] {
|
|
37
|
+
display: inline-block;
|
|
38
|
+
height: 1.2em;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
position: relative;
|
|
41
|
+
clip-path: inset(0);
|
|
42
|
+
}
|
|
43
|
+
.ticker-char[data-v-d7db53e5] {
|
|
44
|
+
position: absolute;
|
|
45
|
+
top: 0;
|
|
46
|
+
left: 0;
|
|
47
|
+
right: 0;
|
|
48
|
+
height: 1.2em;
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: center;
|
|
52
|
+
white-space: pre;
|
|
53
|
+
}
|