@tombcato/smart-ticker 1.0.0 → 1.0.3
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/README.md +79 -28
- package/dist/style.css +5 -5
- package/dist/vue.cjs +4 -3
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.d.ts +9 -0
- package/dist/vue.js +4 -3
- package/dist/vue.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
<h1 align="center">Smart Ticker</h1>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
|
-
高性能智能文本差异滚动组件,基于 Levenshtein diff 算法,支持多字符集,适用于React/Vue
|
|
9
|
+
高性能智能文本差异滚动组件,基于 Levenshtein diff 算法,支持多字符集,适用于React/Vue,<a href="https://tombcato.github.io/smart-ticker/">官网演示></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -20,10 +20,6 @@
|
|
|
20
20
|
<img src="https://img.shields.io/npm/v/@tombcato/smart-ticker?color=cb3837&logo=npm" alt="npm" />
|
|
21
21
|
</p>
|
|
22
22
|
|
|
23
|
-
<p align="center">
|
|
24
|
-
<a href="https://tombcato.github.io/smart-ticker/">📺 官网演示</a> ·
|
|
25
|
-
<a href="https://www.npmjs.com/package/@tombcato/smart-ticker">📥 NPM</a>
|
|
26
|
-
</p>
|
|
27
23
|
|
|
28
24
|
## ✨ 特性
|
|
29
25
|
|
|
@@ -59,10 +55,25 @@ npm run dev
|
|
|
59
55
|
|
|
60
56
|
## 🚀 使用方法
|
|
61
57
|
|
|
58
|
+
### 📦 引入样式 (Import Styles)
|
|
59
|
+
|
|
60
|
+
**NPM 安装**时,**必须**显式引入样式文件组件才能正常工作。
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
import '@tombcato/smart-ticker/style.css'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> **源码集成**:如果您直接复制组件源码,React 版本需确保引入同目录的 `Ticker.css`,Vue 版本样式已内置在单文件组件中。
|
|
67
|
+
|
|
62
68
|
### React
|
|
63
69
|
|
|
64
70
|
```tsx
|
|
65
|
-
|
|
71
|
+
// NPM 方式
|
|
72
|
+
import { Ticker } from '@tombcato/smart-ticker';
|
|
73
|
+
import '@tombcato/smart-ticker/style.css';
|
|
74
|
+
|
|
75
|
+
// 源码方式
|
|
76
|
+
// import { Ticker } from './components/Ticker';
|
|
66
77
|
|
|
67
78
|
function App() {
|
|
68
79
|
const [price, setPrice] = useState(73.18);
|
|
@@ -82,6 +93,18 @@ function App() {
|
|
|
82
93
|
### Vue
|
|
83
94
|
|
|
84
95
|
```vue
|
|
96
|
+
<script setup>
|
|
97
|
+
// NPM 方式
|
|
98
|
+
import { Ticker } from '@tombcato/smart-ticker/vue';
|
|
99
|
+
import '@tombcato/smart-ticker/style.css';
|
|
100
|
+
|
|
101
|
+
// 源码方式
|
|
102
|
+
// import Ticker from './components/vue/Ticker.vue';
|
|
103
|
+
|
|
104
|
+
import { ref } from 'vue';
|
|
105
|
+
|
|
106
|
+
const price = ref('73.18');
|
|
107
|
+
</script>
|
|
85
108
|
<template>
|
|
86
109
|
<Ticker
|
|
87
110
|
:value="price"
|
|
@@ -91,17 +114,25 @@ function App() {
|
|
|
91
114
|
:character-lists="['0123456789.,']"
|
|
92
115
|
/>
|
|
93
116
|
</template>
|
|
117
|
+
```
|
|
94
118
|
|
|
95
|
-
|
|
96
|
-
import Ticker from './components/vue/Ticker.vue';
|
|
97
|
-
import { ref } from 'vue';
|
|
119
|
+
### 💅 样式自定义
|
|
98
120
|
|
|
99
|
-
|
|
100
|
-
|
|
121
|
+
#### 自定义字体
|
|
122
|
+
|
|
123
|
+
组件默认使用系统等宽字体栈。如果需要使用自定义字体(如 `JetBrains Mono`),请确保该字体是**等宽字体**,并使用 CSS 覆盖:
|
|
124
|
+
|
|
125
|
+
```css
|
|
126
|
+
/* 全局样式或组件样式中 */
|
|
127
|
+
.ticker {
|
|
128
|
+
font-family: 'JetBrains Mono', monospace !important;
|
|
129
|
+
}
|
|
101
130
|
```
|
|
102
131
|
|
|
103
|
-
|
|
132
|
+
> **注意**:必须使用**等宽字体**,否则字符滚动动画的对齐可能会出现偏差。
|
|
104
133
|
|
|
134
|
+
|
|
135
|
+
## ⚙️ API
|
|
105
136
|
### Props
|
|
106
137
|
|
|
107
138
|
| 属性 | 类型 | 默认值 | 说明 |
|
|
@@ -124,33 +155,55 @@ TickerUtils.provideAlphabeticalList() // 'abcdefghijklmnopqrstuvwxyz'
|
|
|
124
155
|
TickerUtils.provideHexadecimalList() // '0123456789ABCDEF'
|
|
125
156
|
```
|
|
126
157
|
|
|
127
|
-
##
|
|
158
|
+
## 💻 运行演示
|
|
128
159
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
160
|
+
本项目提供了完整基于 NPM 的 React 和 Vue 示例工程,位于 `examples` 目录下。
|
|
161
|
+
|
|
162
|
+
### 启动 React Demo
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
cd examples/react-demo
|
|
166
|
+
npm install
|
|
167
|
+
npm run dev
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### 启动 Vue Demo
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
cd examples/vue-demo
|
|
174
|
+
npm install
|
|
175
|
+
npm run dev
|
|
176
|
+
```
|
|
134
177
|
|
|
135
178
|
## 📁 项目结构
|
|
136
179
|
|
|
137
180
|
```
|
|
138
|
-
|
|
181
|
+
smart-ticker/
|
|
139
182
|
├── src/
|
|
140
183
|
│ ├── components/
|
|
141
|
-
│ │ ├── Ticker.tsx # React
|
|
142
|
-
│ │ ├── Ticker.css #
|
|
184
|
+
│ │ ├── Ticker.tsx # React 组件源码
|
|
185
|
+
│ │ ├── Ticker.css # 组件核心样式
|
|
143
186
|
│ │ └── vue/
|
|
144
|
-
│ │ └── Ticker.vue # Vue
|
|
187
|
+
│ │ └── Ticker.vue # Vue 组件源码
|
|
145
188
|
│ ├── core/
|
|
146
|
-
│ │ └── TickerCore.ts #
|
|
147
|
-
│
|
|
148
|
-
|
|
189
|
+
│ │ └── TickerCore.ts # 核心逻辑(Levenshtein diff 算法)
|
|
190
|
+
│ └── ...
|
|
191
|
+
├── examples/ # 独立示例工程
|
|
192
|
+
│ ├── react-demo/ # React Demo (Vite + React + TS)
|
|
193
|
+
│ └── vue-demo/ # Vue Demo (Vite + Vue + TS)
|
|
149
194
|
├── public/
|
|
150
|
-
│ └── vue-demo.html #
|
|
195
|
+
│ └── vue-demo.html # 单文件 CDN 引用示例
|
|
151
196
|
└── package.json
|
|
152
197
|
```
|
|
153
198
|
|
|
199
|
+
## 🎨 示例场景
|
|
200
|
+
|
|
201
|
+
- **金融数据** - 股票价格、加密货币行情
|
|
202
|
+
- **计数器** - 访问量、点赞数
|
|
203
|
+
- **比分牌** - 体育比赛实时比分
|
|
204
|
+
- **机场信息牌** - 航班号、登机口
|
|
205
|
+
- **隐私模式** - 余额隐藏/显示切换
|
|
206
|
+
|
|
154
207
|
## 🔧 技术栈
|
|
155
208
|
|
|
156
209
|
- **构建工具**: Vite
|
|
@@ -159,6 +212,4 @@ ticker-smart-text-diff/
|
|
|
159
212
|
- **样式**: CSS Variables + 响应式设计
|
|
160
213
|
|
|
161
214
|
## 📄 License
|
|
162
|
-
|
|
163
215
|
MIT
|
|
164
|
-
|
package/dist/style.css
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.ticker {
|
|
2
2
|
display: inline-flex;
|
|
3
3
|
overflow: hidden;
|
|
4
|
-
font-family:
|
|
4
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
5
5
|
font-weight: 600;
|
|
6
6
|
line-height: 1.2;
|
|
7
7
|
}
|
|
@@ -26,21 +26,21 @@
|
|
|
26
26
|
justify-content: center;
|
|
27
27
|
white-space: pre;
|
|
28
28
|
}
|
|
29
|
-
.ticker[data-v-
|
|
29
|
+
.ticker[data-v-9c58714d] {
|
|
30
30
|
display: inline-flex;
|
|
31
31
|
overflow: hidden;
|
|
32
|
-
font-family:
|
|
32
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
|
|
33
33
|
font-weight: 600;
|
|
34
34
|
line-height: 1.2;
|
|
35
35
|
}
|
|
36
|
-
.ticker-column[data-v-
|
|
36
|
+
.ticker-column[data-v-9c58714d] {
|
|
37
37
|
display: inline-block;
|
|
38
38
|
height: 1.2em;
|
|
39
39
|
overflow: hidden;
|
|
40
40
|
position: relative;
|
|
41
41
|
clip-path: inset(0);
|
|
42
42
|
}
|
|
43
|
-
.ticker-char[data-v-
|
|
43
|
+
.ticker-char[data-v-9c58714d] {
|
|
44
44
|
position: absolute;
|
|
45
45
|
top: 0;
|
|
46
46
|
left: 0;
|
package/dist/vue.cjs
CHANGED
|
@@ -11,7 +11,8 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
|
|
|
11
11
|
duration: { type: Number, default: 500 },
|
|
12
12
|
direction: { type: String, default: "ANY" },
|
|
13
13
|
easing: { type: String, default: "easeInOut" },
|
|
14
|
-
className: { type: String, default: "" }
|
|
14
|
+
className: { type: String, default: "" },
|
|
15
|
+
charWidth: { type: Number, default: 1 }
|
|
15
16
|
},
|
|
16
17
|
setup(__props) {
|
|
17
18
|
const props = __props;
|
|
@@ -110,7 +111,7 @@ const _sfc_main = /* @__PURE__ */ vue.defineComponent({
|
|
|
110
111
|
return vue.openBlock(), vue.createElementBlock("div", {
|
|
111
112
|
key: i,
|
|
112
113
|
class: "ticker-column",
|
|
113
|
-
style: vue.normalizeStyle({ width: `${col.width}em` })
|
|
114
|
+
style: vue.normalizeStyle({ width: `${col.width * 0.8 * __props.charWidth}em` })
|
|
114
115
|
}, [
|
|
115
116
|
(vue.openBlock(true), vue.createElementBlock(vue.Fragment, null, vue.renderList(col.chars, (charObj) => {
|
|
116
117
|
return vue.openBlock(), vue.createElementBlock("div", {
|
|
@@ -132,7 +133,7 @@ const _export_sfc = (sfc, props) => {
|
|
|
132
133
|
}
|
|
133
134
|
return target;
|
|
134
135
|
};
|
|
135
|
-
const Ticker = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
136
|
+
const Ticker = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9c58714d"]]);
|
|
136
137
|
exports.ACTION_DELETE = TickerCore.ACTION_DELETE;
|
|
137
138
|
exports.ACTION_INSERT = TickerCore.ACTION_INSERT;
|
|
138
139
|
exports.ACTION_SAME = TickerCore.ACTION_SAME;
|
package/dist/vue.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vue.cjs","sources":["../src/components/vue/Ticker.vue"],"sourcesContent":["<template>\r\n <div class=\"ticker\" :class=\"className\">\r\n <div\r\n v-for=\"(col, i) in renderedColumns\"\r\n :key=\"i\"\r\n class=\"ticker-column\"\r\n :style=\"{ width: `${col.width}em` }\"\r\n >\r\n <div\r\n v-for=\"charObj in col.chars\"\r\n :key=\"charObj.key\"\r\n class=\"ticker-char\"\r\n :style=\"{ transform: `translateY(${charObj.offset}em)` }\"\r\n >\r\n {{ charObj.char === EMPTY_CHAR ? '\\u00A0' : charObj.char }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, onUnmounted } from 'vue';\r\nimport {\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// ============================================================================\r\n// Vue Component Setup\r\n// ============================================================================\r\n\r\nconst props = defineProps({\r\n value: { type: String, required: true },\r\n characterLists: { type: Array as () => string[], default: () => ['0123456789'] },\r\n duration: { type: Number, default: 500 },\r\n direction: { type: String as () => ScrollingDirection, default: 'ANY' },\r\n easing: { type: String, default: 'easeInOut' },\r\n className: { type: String, default: '' },\r\n});\r\n\r\nconst columns = ref<ColumnState[]>([]);\r\nconst progress = ref(1);\r\nlet animId: number | undefined;\r\n\r\n// Initialization\r\nconst lists = computed(() => props.characterLists.map(s => new TickerCharacterList(s)));\r\nconst supported = computed(() => {\r\n const set = new Set<string>();\r\n lists.value.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n});\r\n\r\n// Watch for value changes\r\nwatch(() => props.value, (newValue, oldValue) => {\r\n if (newValue === oldValue) return;\r\n\r\n if (animId) {\r\n cancelAnimationFrame(animId);\r\n animId = undefined;\r\n }\r\n\r\n // Update current columns with current progress if mid-animation\r\n let currentCols = columns.value;\r\n if (progress.value < 1 && progress.value > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progress.value, true).col);\r\n }\r\n\r\n const targetChars = newValue.split('');\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supported.value);\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++], lists.value, props.direction));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));\r\n }\r\n }\r\n\r\n columns.value = result;\r\n\r\n // Start Animation\r\n progress.value = 0;\r\n const start = performance.now();\r\n const dur = props.duration;\r\n const easeFn = easingFunctions[props.easing] || 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);\r\n \r\n // Throttled update\r\n const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;\r\n \r\n if (shouldUpdate) {\r\n lastUpdate = now;\r\n progress.value = p;\r\n }\r\n\r\n if (linearP < 1) {\r\n animId = requestAnimationFrame(animate);\r\n } else {\r\n const final = columns.value\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n columns.value = final;\r\n progress.value = 1;\r\n animId = undefined;\r\n }\r\n };\r\n animId = requestAnimationFrame(animate);\r\n\r\n}, { immediate: true }); \r\n\r\nonUnmounted(() => {\r\n if (animId) cancelAnimationFrame(animId);\r\n});\r\n\r\n// Derived View Data\r\nconst charHeight = 1.2;\r\n\r\nconst renderedColumns = computed(() => {\r\n return columns.value.map(col => {\r\n const { charIdx, delta } = applyProgress(col, progress.value);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;\r\n if (width <= 0) return { width, chars: [] };\r\n\r\n const chars: Array<{ key: string, char: string, offset: number }> = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // Helper to add char\r\n const add = (idx: number, offsetMod: number, keyPrefix: string) => {\r\n if (idx >= 0 && idx < list.length) {\r\n chars.push({\r\n key: `${keyPrefix}-${idx}`,\r\n char: list[idx],\r\n offset: deltaEm + offsetMod\r\n });\r\n }\r\n };\r\n\r\n add(charIdx, 0, 'c');\r\n add(charIdx + 1, -charHeight, 'n');\r\n add(charIdx - 1, charHeight, 'p');\r\n\r\n return { width, chars };\r\n }).filter(c => c.width > 0);\r\n});\r\n\r\n</script>\r\n\r\n<style scoped>\r\n.ticker {\r\n display: inline-flex;\r\n overflow: hidden;\r\n font-family: 'JetBrains Mono', monospace;\r\n font-weight: 600;\r\n line-height: 1.2;\r\n}\r\n\r\n.ticker-column {\r\n display: inline-block;\r\n height: 1.2em;\r\n overflow: hidden;\r\n position: relative;\r\n clip-path: inset(0);\r\n}\r\n\r\n.ticker-char {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n height: 1.2em;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n white-space: pre;\r\n}\r\n</style>\r\n"],"names":["ref","computed","TickerCharacterList","watch","applyProgress","computeColumnActions","ACTION_INSERT","setTarget","createColumn","ACTION_SAME","EMPTY_CHAR","easingFunctions","onUnmounted","_createElementBlock","_normalizeClass","_openBlock","_Fragment","_renderList","_normalizeStyle","_toDisplayString","_unref"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vue.cjs","sources":["../src/components/vue/Ticker.vue"],"sourcesContent":["<template>\r\n <div class=\"ticker\" :class=\"className\">\r\n <div\r\n v-for=\"(col, i) in renderedColumns\"\r\n :key=\"i\"\r\n class=\"ticker-column\"\r\n :style=\"{ width: `${col.width * 0.8 * charWidth}em` }\"\r\n >\r\n <div\r\n v-for=\"charObj in col.chars\"\r\n :key=\"charObj.key\"\r\n class=\"ticker-char\"\r\n :style=\"{ transform: `translateY(${charObj.offset}em)` }\"\r\n >\r\n {{ charObj.char === EMPTY_CHAR ? '\\u00A0' : charObj.char }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, onUnmounted } from 'vue';\r\nimport {\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// ============================================================================\r\n// Vue Component Setup\r\n// ============================================================================\r\n\r\nconst props = defineProps({\r\n value: { type: String, required: true },\r\n characterLists: { type: Array as () => string[], default: () => ['0123456789'] },\r\n duration: { type: Number, default: 500 },\r\n direction: { type: String as () => ScrollingDirection, default: 'ANY' },\r\n easing: { type: String, default: 'easeInOut' },\r\n className: { type: String, default: '' },\r\n charWidth: { type: Number, default: 1 },\r\n});\r\n\r\nconst columns = ref<ColumnState[]>([]);\r\nconst progress = ref(1);\r\nlet animId: number | undefined;\r\n\r\n// Initialization\r\nconst lists = computed(() => props.characterLists.map(s => new TickerCharacterList(s)));\r\nconst supported = computed(() => {\r\n const set = new Set<string>();\r\n lists.value.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n});\r\n\r\n// Watch for value changes\r\nwatch(() => props.value, (newValue, oldValue) => {\r\n if (newValue === oldValue) return;\r\n\r\n if (animId) {\r\n cancelAnimationFrame(animId);\r\n animId = undefined;\r\n }\r\n\r\n // Update current columns with current progress if mid-animation\r\n let currentCols = columns.value;\r\n if (progress.value < 1 && progress.value > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progress.value, true).col);\r\n }\r\n\r\n const targetChars = newValue.split('');\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supported.value);\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++], lists.value, props.direction));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));\r\n }\r\n }\r\n\r\n columns.value = result;\r\n\r\n // Start Animation\r\n progress.value = 0;\r\n const start = performance.now();\r\n const dur = props.duration;\r\n const easeFn = easingFunctions[props.easing] || 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);\r\n \r\n // Throttled update\r\n const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;\r\n \r\n if (shouldUpdate) {\r\n lastUpdate = now;\r\n progress.value = p;\r\n }\r\n\r\n if (linearP < 1) {\r\n animId = requestAnimationFrame(animate);\r\n } else {\r\n const final = columns.value\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n columns.value = final;\r\n progress.value = 1;\r\n animId = undefined;\r\n }\r\n };\r\n animId = requestAnimationFrame(animate);\r\n\r\n}, { immediate: true }); \r\n\r\nonUnmounted(() => {\r\n if (animId) cancelAnimationFrame(animId);\r\n});\r\n\r\n// Derived View Data\r\nconst charHeight = 1.2;\r\n\r\nconst renderedColumns = computed(() => {\r\n return columns.value.map(col => {\r\n const { charIdx, delta } = applyProgress(col, progress.value);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;\r\n if (width <= 0) return { width, chars: [] };\r\n\r\n const chars: Array<{ key: string, char: string, offset: number }> = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // Helper to add char\r\n const add = (idx: number, offsetMod: number, keyPrefix: string) => {\r\n if (idx >= 0 && idx < list.length) {\r\n chars.push({\r\n key: `${keyPrefix}-${idx}`,\r\n char: list[idx],\r\n offset: deltaEm + offsetMod\r\n });\r\n }\r\n };\r\n\r\n add(charIdx, 0, 'c');\r\n add(charIdx + 1, -charHeight, 'n');\r\n add(charIdx - 1, charHeight, 'p');\r\n\r\n return { width, chars };\r\n }).filter(c => c.width > 0);\r\n});\r\n\r\n</script>\r\n\r\n<style scoped>\r\n.ticker {\r\n display: inline-flex;\r\n overflow: hidden;\r\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\r\n font-weight: 600;\r\n line-height: 1.2;\r\n}\r\n\r\n.ticker-column {\r\n display: inline-block;\r\n height: 1.2em;\r\n overflow: hidden;\r\n position: relative;\r\n clip-path: inset(0);\r\n}\r\n\r\n.ticker-char {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n height: 1.2em;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n white-space: pre;\r\n}\r\n</style>\r\n"],"names":["ref","computed","TickerCharacterList","watch","applyProgress","computeColumnActions","ACTION_INSERT","setTarget","createColumn","ACTION_SAME","EMPTY_CHAR","easingFunctions","onUnmounted","_createElementBlock","_normalizeClass","_openBlock","_Fragment","_renderList","_normalizeStyle","_toDisplayString","_unref"],"mappings":"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"}
|
package/dist/vue.d.ts
CHANGED
|
@@ -67,6 +67,10 @@ className: {
|
|
|
67
67
|
type: StringConstructor;
|
|
68
68
|
default: string;
|
|
69
69
|
};
|
|
70
|
+
charWidth: {
|
|
71
|
+
type: NumberConstructor;
|
|
72
|
+
default: number;
|
|
73
|
+
};
|
|
70
74
|
}>, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<ExtractPropTypes< {
|
|
71
75
|
value: {
|
|
72
76
|
type: StringConstructor;
|
|
@@ -92,12 +96,17 @@ className: {
|
|
|
92
96
|
type: StringConstructor;
|
|
93
97
|
default: string;
|
|
94
98
|
};
|
|
99
|
+
charWidth: {
|
|
100
|
+
type: NumberConstructor;
|
|
101
|
+
default: number;
|
|
102
|
+
};
|
|
95
103
|
}>> & Readonly<{}>, {
|
|
96
104
|
characterLists: string[];
|
|
97
105
|
duration: number;
|
|
98
106
|
direction: ScrollingDirection;
|
|
99
107
|
easing: string;
|
|
100
108
|
className: string;
|
|
109
|
+
charWidth: number;
|
|
101
110
|
}, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
|
|
102
111
|
|
|
103
112
|
export declare class TickerCharacterList {
|
package/dist/vue.js
CHANGED
|
@@ -10,7 +10,8 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
10
10
|
duration: { type: Number, default: 500 },
|
|
11
11
|
direction: { type: String, default: "ANY" },
|
|
12
12
|
easing: { type: String, default: "easeInOut" },
|
|
13
|
-
className: { type: String, default: "" }
|
|
13
|
+
className: { type: String, default: "" },
|
|
14
|
+
charWidth: { type: Number, default: 1 }
|
|
14
15
|
},
|
|
15
16
|
setup(__props) {
|
|
16
17
|
const props = __props;
|
|
@@ -109,7 +110,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
109
110
|
return openBlock(), createElementBlock("div", {
|
|
110
111
|
key: i,
|
|
111
112
|
class: "ticker-column",
|
|
112
|
-
style: normalizeStyle({ width: `${col.width}em` })
|
|
113
|
+
style: normalizeStyle({ width: `${col.width * 0.8 * __props.charWidth}em` })
|
|
113
114
|
}, [
|
|
114
115
|
(openBlock(true), createElementBlock(Fragment, null, renderList(col.chars, (charObj) => {
|
|
115
116
|
return openBlock(), createElementBlock("div", {
|
|
@@ -131,7 +132,7 @@ const _export_sfc = (sfc, props) => {
|
|
|
131
132
|
}
|
|
132
133
|
return target;
|
|
133
134
|
};
|
|
134
|
-
const Ticker = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-
|
|
135
|
+
const Ticker = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-9c58714d"]]);
|
|
135
136
|
export {
|
|
136
137
|
g as ACTION_DELETE,
|
|
137
138
|
ACTION_INSERT,
|
package/dist/vue.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vue.js","sources":["../src/components/vue/Ticker.vue"],"sourcesContent":["<template>\r\n <div class=\"ticker\" :class=\"className\">\r\n <div\r\n v-for=\"(col, i) in renderedColumns\"\r\n :key=\"i\"\r\n class=\"ticker-column\"\r\n :style=\"{ width: `${col.width}em` }\"\r\n >\r\n <div\r\n v-for=\"charObj in col.chars\"\r\n :key=\"charObj.key\"\r\n class=\"ticker-char\"\r\n :style=\"{ transform: `translateY(${charObj.offset}em)` }\"\r\n >\r\n {{ charObj.char === EMPTY_CHAR ? '\\u00A0' : charObj.char }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, onUnmounted } from 'vue';\r\nimport {\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// ============================================================================\r\n// Vue Component Setup\r\n// ============================================================================\r\n\r\nconst props = defineProps({\r\n value: { type: String, required: true },\r\n characterLists: { type: Array as () => string[], default: () => ['0123456789'] },\r\n duration: { type: Number, default: 500 },\r\n direction: { type: String as () => ScrollingDirection, default: 'ANY' },\r\n easing: { type: String, default: 'easeInOut' },\r\n className: { type: String, default: '' },\r\n});\r\n\r\nconst columns = ref<ColumnState[]>([]);\r\nconst progress = ref(1);\r\nlet animId: number | undefined;\r\n\r\n// Initialization\r\nconst lists = computed(() => props.characterLists.map(s => new TickerCharacterList(s)));\r\nconst supported = computed(() => {\r\n const set = new Set<string>();\r\n lists.value.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n});\r\n\r\n// Watch for value changes\r\nwatch(() => props.value, (newValue, oldValue) => {\r\n if (newValue === oldValue) return;\r\n\r\n if (animId) {\r\n cancelAnimationFrame(animId);\r\n animId = undefined;\r\n }\r\n\r\n // Update current columns with current progress if mid-animation\r\n let currentCols = columns.value;\r\n if (progress.value < 1 && progress.value > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progress.value, true).col);\r\n }\r\n\r\n const targetChars = newValue.split('');\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supported.value);\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++], lists.value, props.direction));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));\r\n }\r\n }\r\n\r\n columns.value = result;\r\n\r\n // Start Animation\r\n progress.value = 0;\r\n const start = performance.now();\r\n const dur = props.duration;\r\n const easeFn = easingFunctions[props.easing] || 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);\r\n \r\n // Throttled update\r\n const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;\r\n \r\n if (shouldUpdate) {\r\n lastUpdate = now;\r\n progress.value = p;\r\n }\r\n\r\n if (linearP < 1) {\r\n animId = requestAnimationFrame(animate);\r\n } else {\r\n const final = columns.value\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n columns.value = final;\r\n progress.value = 1;\r\n animId = undefined;\r\n }\r\n };\r\n animId = requestAnimationFrame(animate);\r\n\r\n}, { immediate: true }); \r\n\r\nonUnmounted(() => {\r\n if (animId) cancelAnimationFrame(animId);\r\n});\r\n\r\n// Derived View Data\r\nconst charHeight = 1.2;\r\n\r\nconst renderedColumns = computed(() => {\r\n return columns.value.map(col => {\r\n const { charIdx, delta } = applyProgress(col, progress.value);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;\r\n if (width <= 0) return { width, chars: [] };\r\n\r\n const chars: Array<{ key: string, char: string, offset: number }> = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // Helper to add char\r\n const add = (idx: number, offsetMod: number, keyPrefix: string) => {\r\n if (idx >= 0 && idx < list.length) {\r\n chars.push({\r\n key: `${keyPrefix}-${idx}`,\r\n char: list[idx],\r\n offset: deltaEm + offsetMod\r\n });\r\n }\r\n };\r\n\r\n add(charIdx, 0, 'c');\r\n add(charIdx + 1, -charHeight, 'n');\r\n add(charIdx - 1, charHeight, 'p');\r\n\r\n return { width, chars };\r\n }).filter(c => c.width > 0);\r\n});\r\n\r\n</script>\r\n\r\n<style scoped>\r\n.ticker {\r\n display: inline-flex;\r\n overflow: hidden;\r\n font-family:
|
|
1
|
+
{"version":3,"file":"vue.js","sources":["../src/components/vue/Ticker.vue"],"sourcesContent":["<template>\r\n <div class=\"ticker\" :class=\"className\">\r\n <div\r\n v-for=\"(col, i) in renderedColumns\"\r\n :key=\"i\"\r\n class=\"ticker-column\"\r\n :style=\"{ width: `${col.width * 0.8 * charWidth}em` }\"\r\n >\r\n <div\r\n v-for=\"charObj in col.chars\"\r\n :key=\"charObj.key\"\r\n class=\"ticker-char\"\r\n :style=\"{ transform: `translateY(${charObj.offset}em)` }\"\r\n >\r\n {{ charObj.char === EMPTY_CHAR ? '\\u00A0' : charObj.char }}\r\n </div>\r\n </div>\r\n </div>\r\n</template>\r\n\r\n<script lang=\"ts\" setup>\r\nimport { ref, computed, watch, onUnmounted } from 'vue';\r\nimport {\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// ============================================================================\r\n// Vue Component Setup\r\n// ============================================================================\r\n\r\nconst props = defineProps({\r\n value: { type: String, required: true },\r\n characterLists: { type: Array as () => string[], default: () => ['0123456789'] },\r\n duration: { type: Number, default: 500 },\r\n direction: { type: String as () => ScrollingDirection, default: 'ANY' },\r\n easing: { type: String, default: 'easeInOut' },\r\n className: { type: String, default: '' },\r\n charWidth: { type: Number, default: 1 },\r\n});\r\n\r\nconst columns = ref<ColumnState[]>([]);\r\nconst progress = ref(1);\r\nlet animId: number | undefined;\r\n\r\n// Initialization\r\nconst lists = computed(() => props.characterLists.map(s => new TickerCharacterList(s)));\r\nconst supported = computed(() => {\r\n const set = new Set<string>();\r\n lists.value.forEach(l => l.getSupportedCharacters().forEach(c => set.add(c)));\r\n return set;\r\n});\r\n\r\n// Watch for value changes\r\nwatch(() => props.value, (newValue, oldValue) => {\r\n if (newValue === oldValue) return;\r\n\r\n if (animId) {\r\n cancelAnimationFrame(animId);\r\n animId = undefined;\r\n }\r\n\r\n // Update current columns with current progress if mid-animation\r\n let currentCols = columns.value;\r\n if (progress.value < 1 && progress.value > 0) {\r\n currentCols = currentCols.map(c => applyProgress(c, progress.value, true).col);\r\n }\r\n\r\n const targetChars = newValue.split('');\r\n const sourceChars = currentCols.map(c => c.currentChar);\r\n const actions = computeColumnActions(sourceChars, targetChars, supported.value);\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++], lists.value, props.direction));\r\n } else if (action === ACTION_SAME) {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, targetChars[ti++], lists.value, props.direction));\r\n } else {\r\n const existing = validCols[ci++] || createColumn();\r\n result.push(setTarget(existing, EMPTY_CHAR, lists.value, props.direction));\r\n }\r\n }\r\n\r\n columns.value = result;\r\n\r\n // Start Animation\r\n progress.value = 0;\r\n const start = performance.now();\r\n const dur = props.duration;\r\n const easeFn = easingFunctions[props.easing] || 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);\r\n \r\n // Throttled update\r\n const shouldUpdate = now - lastUpdate >= 16 || linearP >= 1;\r\n \r\n if (shouldUpdate) {\r\n lastUpdate = now;\r\n progress.value = p;\r\n }\r\n\r\n if (linearP < 1) {\r\n animId = requestAnimationFrame(animate);\r\n } else {\r\n const final = columns.value\r\n .map(c => applyProgress(c, 1).col)\r\n .filter(c => c.currentWidth > 0);\r\n columns.value = final;\r\n progress.value = 1;\r\n animId = undefined;\r\n }\r\n };\r\n animId = requestAnimationFrame(animate);\r\n\r\n}, { immediate: true }); \r\n\r\nonUnmounted(() => {\r\n if (animId) cancelAnimationFrame(animId);\r\n});\r\n\r\n// Derived View Data\r\nconst charHeight = 1.2;\r\n\r\nconst renderedColumns = computed(() => {\r\n return columns.value.map(col => {\r\n const { charIdx, delta } = applyProgress(col, progress.value);\r\n const width = col.sourceWidth + (col.targetWidth - col.sourceWidth) * progress.value;\r\n if (width <= 0) return { width, chars: [] };\r\n\r\n const chars: Array<{ key: string, char: string, offset: number }> = [];\r\n const list = col.charList || [];\r\n const deltaEm = delta * charHeight;\r\n\r\n // Helper to add char\r\n const add = (idx: number, offsetMod: number, keyPrefix: string) => {\r\n if (idx >= 0 && idx < list.length) {\r\n chars.push({\r\n key: `${keyPrefix}-${idx}`,\r\n char: list[idx],\r\n offset: deltaEm + offsetMod\r\n });\r\n }\r\n };\r\n\r\n add(charIdx, 0, 'c');\r\n add(charIdx + 1, -charHeight, 'n');\r\n add(charIdx - 1, charHeight, 'p');\r\n\r\n return { width, chars };\r\n }).filter(c => c.width > 0);\r\n});\r\n\r\n</script>\r\n\r\n<style scoped>\r\n.ticker {\r\n display: inline-flex;\r\n overflow: hidden;\r\n font-family: ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace;\r\n font-weight: 600;\r\n line-height: 1.2;\r\n}\r\n\r\n.ticker-column {\r\n display: inline-block;\r\n height: 1.2em;\r\n overflow: hidden;\r\n position: relative;\r\n clip-path: inset(0);\r\n}\r\n\r\n.ticker-char {\r\n position: absolute;\r\n top: 0;\r\n left: 0;\r\n right: 0;\r\n height: 1.2em;\r\n display: flex;\r\n align-items: center;\r\n justify-content: center;\r\n white-space: pre;\r\n}\r\n</style>\r\n"],"names":["_createElementBlock","_normalizeClass","_openBlock","_Fragment","_renderList","_normalizeStyle","_toDisplayString","_unref"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tombcato/smart-ticker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Smart text animation component with diff-based character scrollin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -72,4 +72,4 @@
|
|
|
72
72
|
"vite": "^5.0.0",
|
|
73
73
|
"vite-plugin-dts": "^3.9.1"
|
|
74
74
|
}
|
|
75
|
-
}
|
|
75
|
+
}
|