@quicktvui/web-renderer 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/package.json +24 -0
- package/src/adapters/es3-video-player.js +828 -0
- package/src/components/Modal.js +119 -0
- package/src/components/QtAnimationView.js +678 -0
- package/src/components/QtBaseComponent.js +165 -0
- package/src/components/QtFastListView.js +1920 -0
- package/src/components/QtFlexView.js +799 -0
- package/src/components/QtImage.js +203 -0
- package/src/components/QtItemFrame.js +239 -0
- package/src/components/QtItemStoreView.js +93 -0
- package/src/components/QtItemView.js +125 -0
- package/src/components/QtListView.js +331 -0
- package/src/components/QtLoadingView.js +55 -0
- package/src/components/QtPageRootView.js +19 -0
- package/src/components/QtPlayMark.js +168 -0
- package/src/components/QtProgressBar.js +199 -0
- package/src/components/QtQRCode.js +78 -0
- package/src/components/QtReplaceChild.js +149 -0
- package/src/components/QtRippleView.js +166 -0
- package/src/components/QtSeekBar.js +409 -0
- package/src/components/QtText.js +679 -0
- package/src/components/QtTransitionImage.js +170 -0
- package/src/components/QtView.js +706 -0
- package/src/components/QtWebView.js +613 -0
- package/src/components/TabsView.js +420 -0
- package/src/components/ViewPager.js +206 -0
- package/src/components/index.js +24 -0
- package/src/components/plugins/TextV2Component.js +70 -0
- package/src/components/plugins/index.js +7 -0
- package/src/core/SceneBuilder.js +58 -0
- package/src/core/TVFocusManager.js +2014 -0
- package/src/core/asyncLocalStorage.js +175 -0
- package/src/core/autoProxy.js +165 -0
- package/src/core/componentRegistry.js +84 -0
- package/src/core/constants.js +6 -0
- package/src/core/index.js +8 -0
- package/src/core/moduleUtils.js +36 -0
- package/src/core/patches.js +958 -0
- package/src/core/templateBinding.js +666 -0
- package/src/index.js +246 -0
- package/src/modules/AndroidDevelopModule.js +101 -0
- package/src/modules/AndroidDeviceModule.js +341 -0
- package/src/modules/AndroidNetworkModule.js +178 -0
- package/src/modules/AndroidSharedPreferencesModule.js +100 -0
- package/src/modules/ESDeviceInfoModule.js +450 -0
- package/src/modules/ESGroupDataModule.js +195 -0
- package/src/modules/ESIJKAudioPlayerModule.js +477 -0
- package/src/modules/ESLocalStorageModule.js +100 -0
- package/src/modules/ESLogModule.js +65 -0
- package/src/modules/ESModule.js +106 -0
- package/src/modules/ESNetworkSpeedModule.js +117 -0
- package/src/modules/ESToastModule.js +172 -0
- package/src/modules/EsNativeModule.js +117 -0
- package/src/modules/FastListModule.js +101 -0
- package/src/modules/FocusModule.js +145 -0
- package/src/modules/RuntimeDeviceModule.js +176 -0
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
// QtListView - Adapter for Hippy's ListView with TV-specific features 标签对应ul标签
|
|
2
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
3
|
+
import { registerComponent } from '../core/componentRegistry'
|
|
4
|
+
|
|
5
|
+
export class QtListView extends QtBaseComponent {
|
|
6
|
+
constructor(context, id, pId) {
|
|
7
|
+
super(context, id, pId)
|
|
8
|
+
this.tagName = 'ListView'
|
|
9
|
+
this.dom = document.createElement('div')
|
|
10
|
+
this.dom.setAttribute('data-component-name', 'ListView')
|
|
11
|
+
registerComponent(id, this)
|
|
12
|
+
|
|
13
|
+
// TV-specific properties
|
|
14
|
+
this._horizontal = false
|
|
15
|
+
this._fadingEdgeLength = 0
|
|
16
|
+
this._horizontalFadingEdgeEnabled = false
|
|
17
|
+
this._verticalFadingEdgeEnabled = false
|
|
18
|
+
this._clipChildren = true
|
|
19
|
+
|
|
20
|
+
// Child items
|
|
21
|
+
this._children = []
|
|
22
|
+
|
|
23
|
+
// Gradient overlays for fading edge effect
|
|
24
|
+
this._startGradient = null
|
|
25
|
+
this._endGradient = null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
defaultStyle() {
|
|
29
|
+
return {
|
|
30
|
+
display: 'flex',
|
|
31
|
+
flexDirection: this._horizontal ? 'row' : 'column',
|
|
32
|
+
flexShrink: 0,
|
|
33
|
+
boxSizing: 'border-box',
|
|
34
|
+
overflow: 'hidden',
|
|
35
|
+
position: 'relative',
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
updateProperty(key, value) {
|
|
40
|
+
switch (key) {
|
|
41
|
+
case 'horizontal':
|
|
42
|
+
this._horizontal = !!value
|
|
43
|
+
this._updateLayoutDirection()
|
|
44
|
+
break
|
|
45
|
+
|
|
46
|
+
case 'fadingEdgeLength':
|
|
47
|
+
this._fadingEdgeLength = value || 0
|
|
48
|
+
this._updateFadingEdge()
|
|
49
|
+
break
|
|
50
|
+
|
|
51
|
+
case 'horizontalFadingEdgeEnabled':
|
|
52
|
+
this._horizontalFadingEdgeEnabled = !!value
|
|
53
|
+
this._updateFadingEdge()
|
|
54
|
+
break
|
|
55
|
+
|
|
56
|
+
case 'verticalFadingEdgeEnabled':
|
|
57
|
+
this._verticalFadingEdgeEnabled = !!value
|
|
58
|
+
this._updateFadingEdge()
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
case 'clipChildren':
|
|
62
|
+
this._clipChildren = value !== false
|
|
63
|
+
this._updateClipChildren()
|
|
64
|
+
break
|
|
65
|
+
|
|
66
|
+
case 'scrollEnabled':
|
|
67
|
+
this._scrollEnabled = value !== false
|
|
68
|
+
this._updateScrollEnabled()
|
|
69
|
+
break
|
|
70
|
+
|
|
71
|
+
default:
|
|
72
|
+
if (typeof value === 'string' && value.startsWith('${')) {
|
|
73
|
+
this.dom.setAttribute(key, value)
|
|
74
|
+
}
|
|
75
|
+
super.updateProperty(key, value)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_updateLayoutDirection() {
|
|
80
|
+
if (!this.dom) return
|
|
81
|
+
this.dom.style.flexDirection = this._horizontal ? 'row' : 'column'
|
|
82
|
+
this._updateFadingEdge()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
_updateClipChildren() {
|
|
86
|
+
if (!this.dom) return
|
|
87
|
+
this.dom.style.overflow = this._clipChildren ? 'hidden' : 'visible'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
_updateScrollEnabled() {
|
|
91
|
+
if (!this.dom) return
|
|
92
|
+
this.dom.style.overflow = this._scrollEnabled ? 'auto' : 'hidden'
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
_updateFadingEdge() {
|
|
96
|
+
if (!this.dom) return
|
|
97
|
+
|
|
98
|
+
// Remove existing gradients
|
|
99
|
+
if (this._startGradient) {
|
|
100
|
+
this._startGradient.remove()
|
|
101
|
+
this._startGradient = null
|
|
102
|
+
}
|
|
103
|
+
if (this._endGradient) {
|
|
104
|
+
this._endGradient.remove()
|
|
105
|
+
this._endGradient = null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check if fading edge is enabled
|
|
109
|
+
const fadingEnabled = this._horizontal
|
|
110
|
+
? this._horizontalFadingEdgeEnabled
|
|
111
|
+
: this._verticalFadingEdgeEnabled
|
|
112
|
+
|
|
113
|
+
if (!fadingEnabled || this._fadingEdgeLength <= 0) {
|
|
114
|
+
return
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const length = this._fadingEdgeLength
|
|
118
|
+
|
|
119
|
+
// Create gradient overlays
|
|
120
|
+
if (this._horizontal) {
|
|
121
|
+
// Left gradient (start)
|
|
122
|
+
this._startGradient = document.createElement('div')
|
|
123
|
+
this._startGradient.style.cssText = `
|
|
124
|
+
position: absolute;
|
|
125
|
+
left: 0;
|
|
126
|
+
top: 0;
|
|
127
|
+
bottom: 0;
|
|
128
|
+
width: ${length}px;
|
|
129
|
+
background: linear-gradient(to right, rgba(0,0,0,0.3), transparent);
|
|
130
|
+
pointer-events: none;
|
|
131
|
+
z-index: 10;
|
|
132
|
+
`
|
|
133
|
+
this.dom.appendChild(this._startGradient)
|
|
134
|
+
|
|
135
|
+
// Right gradient (end)
|
|
136
|
+
this._endGradient = document.createElement('div')
|
|
137
|
+
this._endGradient.style.cssText = `
|
|
138
|
+
position: absolute;
|
|
139
|
+
right: 0;
|
|
140
|
+
top: 0;
|
|
141
|
+
bottom: 0;
|
|
142
|
+
width: ${length}px;
|
|
143
|
+
background: linear-gradient(to left, rgba(0,0,0,0.3), transparent);
|
|
144
|
+
pointer-events: none;
|
|
145
|
+
z-index: 10;
|
|
146
|
+
`
|
|
147
|
+
this.dom.appendChild(this._endGradient)
|
|
148
|
+
} else {
|
|
149
|
+
// Top gradient (start)
|
|
150
|
+
this._startGradient = document.createElement('div')
|
|
151
|
+
this._startGradient.style.cssText = `
|
|
152
|
+
position: absolute;
|
|
153
|
+
left: 0;
|
|
154
|
+
top: 0;
|
|
155
|
+
right: 0;
|
|
156
|
+
height: ${length}px;
|
|
157
|
+
background: linear-gradient(to bottom, rgba(0,0,0,0.3), transparent);
|
|
158
|
+
pointer-events: none;
|
|
159
|
+
z-index: 10;
|
|
160
|
+
`
|
|
161
|
+
this.dom.appendChild(this._startGradient)
|
|
162
|
+
|
|
163
|
+
// Bottom gradient (end)
|
|
164
|
+
this._endGradient = document.createElement('div')
|
|
165
|
+
this._endGradient.style.cssText = `
|
|
166
|
+
position: absolute;
|
|
167
|
+
left: 0;
|
|
168
|
+
bottom: 0;
|
|
169
|
+
right: 0;
|
|
170
|
+
height: ${length}px;
|
|
171
|
+
background: linear-gradient(to top, rgba(0,0,0,0.3), transparent);
|
|
172
|
+
pointer-events: none;
|
|
173
|
+
z-index: 10;
|
|
174
|
+
`
|
|
175
|
+
this.dom.appendChild(this._endGradient)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Handle child components
|
|
180
|
+
insertChild(view, index) {
|
|
181
|
+
if (view && view.dom) {
|
|
182
|
+
// Insert before gradient overlays
|
|
183
|
+
if (this._endGradient) {
|
|
184
|
+
this.dom.insertBefore(view.dom, this._endGradient)
|
|
185
|
+
} else if (this._startGradient) {
|
|
186
|
+
this.dom.insertBefore(view.dom, this._startGradient)
|
|
187
|
+
} else {
|
|
188
|
+
this.dom.appendChild(view.dom)
|
|
189
|
+
}
|
|
190
|
+
this._children.push(view)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
removeChild(view) {
|
|
195
|
+
if (view && view.dom && view.dom.parentNode === this.dom) {
|
|
196
|
+
this.dom.removeChild(view.dom)
|
|
197
|
+
const idx = this._children.indexOf(view)
|
|
198
|
+
if (idx > -1) {
|
|
199
|
+
this._children.splice(idx, 1)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Scroll methods
|
|
205
|
+
scrollToIndex(index, animated = true) {
|
|
206
|
+
const child = this._children[index]
|
|
207
|
+
if (child && child.dom) {
|
|
208
|
+
child.dom.scrollIntoView({
|
|
209
|
+
behavior: animated ? 'smooth' : 'auto',
|
|
210
|
+
block: 'nearest',
|
|
211
|
+
inline: 'nearest',
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
scrollToPosition(position, offset = 0, animated = true) {
|
|
217
|
+
if (this._horizontal) {
|
|
218
|
+
this.dom.scrollTo({
|
|
219
|
+
left: position + offset,
|
|
220
|
+
behavior: animated ? 'smooth' : 'auto',
|
|
221
|
+
})
|
|
222
|
+
} else {
|
|
223
|
+
this.dom.scrollTo({
|
|
224
|
+
top: position + offset,
|
|
225
|
+
behavior: animated ? 'smooth' : 'auto',
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// End batch - called by UIManagerModule after batch updates
|
|
231
|
+
endBatch() {
|
|
232
|
+
// No-op for now
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
destroy() {
|
|
236
|
+
if (this._startGradient) {
|
|
237
|
+
this._startGradient.remove()
|
|
238
|
+
this._startGradient = null
|
|
239
|
+
}
|
|
240
|
+
if (this._endGradient) {
|
|
241
|
+
this._endGradient.remove()
|
|
242
|
+
this._endGradient = null
|
|
243
|
+
}
|
|
244
|
+
this._children = []
|
|
245
|
+
super.destroy()
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// QtListViewItem - Adapter for Hippy's ListViewItem,标签对应li标签
|
|
250
|
+
export class QtListViewItem extends QtBaseComponent {
|
|
251
|
+
constructor(context, id, pId) {
|
|
252
|
+
super(context, id, pId)
|
|
253
|
+
this.tagName = 'ListViewItem'
|
|
254
|
+
this.dom = document.createElement('div')
|
|
255
|
+
this.dom.setAttribute('data-component-name', 'ListViewItem')
|
|
256
|
+
registerComponent(id, this)
|
|
257
|
+
|
|
258
|
+
// Height tracking for virtual list
|
|
259
|
+
this.height = 0
|
|
260
|
+
this.isDirty = false
|
|
261
|
+
this.dirtyListener = null
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
defaultStyle() {
|
|
265
|
+
return {
|
|
266
|
+
display: 'flex',
|
|
267
|
+
flexDirection: 'column',
|
|
268
|
+
flexShrink: 0,
|
|
269
|
+
boxSizing: 'border-box',
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
updateProperty(key, value) {
|
|
274
|
+
switch (key) {
|
|
275
|
+
case 'sticky':
|
|
276
|
+
this._sticky = !!value
|
|
277
|
+
this._updateSticky()
|
|
278
|
+
break
|
|
279
|
+
|
|
280
|
+
case 'type':
|
|
281
|
+
// Item type for recycling
|
|
282
|
+
this._type = value
|
|
283
|
+
break
|
|
284
|
+
|
|
285
|
+
default:
|
|
286
|
+
if (typeof value === 'string' && value.startsWith('${')) {
|
|
287
|
+
this.dom.setAttribute(key, value)
|
|
288
|
+
}
|
|
289
|
+
super.updateProperty(key, value)
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
_updateSticky() {
|
|
294
|
+
if (!this.dom) return
|
|
295
|
+
if (this._sticky) {
|
|
296
|
+
this.dom.style.position = 'sticky'
|
|
297
|
+
this.dom.style.top = '0'
|
|
298
|
+
this.dom.style.zIndex = '100'
|
|
299
|
+
} else {
|
|
300
|
+
this.dom.style.position = ''
|
|
301
|
+
this.dom.style.top = ''
|
|
302
|
+
this.dom.style.zIndex = ''
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
addDirtyListener(callback) {
|
|
307
|
+
this.dirtyListener = callback
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
getItemHeight() {
|
|
311
|
+
return this.dom?.clientHeight || 0
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// End batch - called by UIManagerModule after batch updates
|
|
315
|
+
endBatch() {
|
|
316
|
+
// No-op for now
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
mounted() {
|
|
320
|
+
super.mounted()
|
|
321
|
+
// Update height after mount
|
|
322
|
+
requestAnimationFrame(() => {
|
|
323
|
+
this.height = this.getItemHeight()
|
|
324
|
+
})
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
destroy() {
|
|
328
|
+
this.dirtyListener = null
|
|
329
|
+
super.destroy()
|
|
330
|
+
}
|
|
331
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// QtLoadingView component for web platform
|
|
2
|
+
// Implements a loading spinner animation
|
|
3
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
4
|
+
|
|
5
|
+
export class QtLoadingView extends QtBaseComponent {
|
|
6
|
+
constructor(context, id, pId) {
|
|
7
|
+
super(context, id, pId)
|
|
8
|
+
this.tagName = 'div'
|
|
9
|
+
this.dom = document.createElement('div')
|
|
10
|
+
|
|
11
|
+
// Create spinner element
|
|
12
|
+
this.spinner = document.createElement('div')
|
|
13
|
+
this.spinner.className = 'qt-loading-spinner'
|
|
14
|
+
this.dom.appendChild(this.spinner)
|
|
15
|
+
|
|
16
|
+
// Inject styles if not already present
|
|
17
|
+
this._injectStyles()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
_injectStyles() {
|
|
21
|
+
const styleId = 'qt-loading-view-styles'
|
|
22
|
+
if (document.getElementById(styleId)) return
|
|
23
|
+
|
|
24
|
+
const style = document.createElement('style')
|
|
25
|
+
style.id = styleId
|
|
26
|
+
style.textContent = `
|
|
27
|
+
.qt-loading-spinner {
|
|
28
|
+
width: 100%;
|
|
29
|
+
height: 100%;
|
|
30
|
+
border: 5px solid rgba(200, 200, 200, 0.3);
|
|
31
|
+
border-top-color: #409eff;
|
|
32
|
+
border-radius: 50%;
|
|
33
|
+
animation: qt-loading-spin 0.8s linear infinite;
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
}
|
|
36
|
+
@keyframes qt-loading-spin {
|
|
37
|
+
to {
|
|
38
|
+
transform: rotate(360deg);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
`
|
|
42
|
+
document.head.appendChild(style)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
updateProperty(key, value) {
|
|
46
|
+
if (key === 'color') {
|
|
47
|
+
// Update spinner border color
|
|
48
|
+
if (value) {
|
|
49
|
+
this.spinner.style.borderTopColor = value
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
super.updateProperty(key, value)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { QtView } from './QtView'
|
|
2
|
+
|
|
3
|
+
export class QtPageRootView extends QtView {
|
|
4
|
+
static componentName = 'ESPageRootView'
|
|
5
|
+
|
|
6
|
+
constructor(context, id, pId) {
|
|
7
|
+
super(context, id, pId)
|
|
8
|
+
this.dom.setAttribute('data-component-name', 'ESPageRootView')
|
|
9
|
+
this._originalComponentName = 'ESPageRootView'
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
defaultStyle() {
|
|
13
|
+
const style = super.defaultStyle ? super.defaultStyle() : {}
|
|
14
|
+
return {
|
|
15
|
+
...style,
|
|
16
|
+
backgroundColor: '#000000', // default black background for TV root
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// QtPlayMark - A play mark animation view component
|
|
2
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
3
|
+
import { registerComponent } from '../core/componentRegistry'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* QtPlayMark Component for web platform
|
|
7
|
+
* Shows a playing indicator with animation
|
|
8
|
+
*/
|
|
9
|
+
export class QtPlayMark extends QtBaseComponent {
|
|
10
|
+
constructor(context, id, pId) {
|
|
11
|
+
super(context, id, pId)
|
|
12
|
+
this.tagName = 'ESPlayMarkViewComponent'
|
|
13
|
+
this.dom = document.createElement('div')
|
|
14
|
+
this.dom.setAttribute('data-component-name', 'QtPlayMark')
|
|
15
|
+
|
|
16
|
+
this.dom.style.cssText = `
|
|
17
|
+
display: block;
|
|
18
|
+
position: relative;
|
|
19
|
+
box-sizing: border-box;
|
|
20
|
+
overflow: hidden;
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
// Props
|
|
24
|
+
this._markColor = '#FF4E46'
|
|
25
|
+
this._gap = 2
|
|
26
|
+
this._showType = 0 // 0: bars, 1: dots
|
|
27
|
+
this._roundCorner = 0
|
|
28
|
+
this._startColor = null
|
|
29
|
+
this._endColor = null
|
|
30
|
+
this._isSupportGradient = false
|
|
31
|
+
this._animationFrame = null
|
|
32
|
+
this._bars = []
|
|
33
|
+
|
|
34
|
+
// Create bars immediately
|
|
35
|
+
this._createBars()
|
|
36
|
+
|
|
37
|
+
registerComponent(id, this)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
defaultStyle() {
|
|
41
|
+
return {
|
|
42
|
+
display: 'block',
|
|
43
|
+
position: 'relative',
|
|
44
|
+
boxSizing: 'border-box',
|
|
45
|
+
overflow: 'hidden',
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_createBars() {
|
|
50
|
+
const barCount = 4
|
|
51
|
+
for (let i = 0; i < barCount; i++) {
|
|
52
|
+
const bar = document.createElement('div')
|
|
53
|
+
bar.className = 'play-mark-bar'
|
|
54
|
+
bar.style.cssText = `
|
|
55
|
+
position: absolute;
|
|
56
|
+
bottom: 0;
|
|
57
|
+
background-color: ${this._markColor};
|
|
58
|
+
border-radius: ${this._roundCorner}px;
|
|
59
|
+
`
|
|
60
|
+
this.dom.appendChild(bar)
|
|
61
|
+
this._bars.push(bar)
|
|
62
|
+
}
|
|
63
|
+
this._startAnimation()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
updateProperty(key, value) {
|
|
67
|
+
switch (key) {
|
|
68
|
+
case 'markColor':
|
|
69
|
+
case 'markcolor':
|
|
70
|
+
this._markColor = value || '#FF4E46'
|
|
71
|
+
this._updateMarkColor()
|
|
72
|
+
break
|
|
73
|
+
case 'gap':
|
|
74
|
+
this._gap = value !== undefined ? Number(value) : 2
|
|
75
|
+
break
|
|
76
|
+
case 'showType':
|
|
77
|
+
case 'showtype':
|
|
78
|
+
this._showType = value !== undefined ? Number(value) : 0
|
|
79
|
+
break
|
|
80
|
+
case 'roundCorner':
|
|
81
|
+
case 'roundcorner':
|
|
82
|
+
this._roundCorner = value !== undefined ? Number(value) : 0
|
|
83
|
+
this._bars.forEach((bar) => {
|
|
84
|
+
bar.style.borderRadius = `${this._roundCorner}px`
|
|
85
|
+
})
|
|
86
|
+
break
|
|
87
|
+
case 'startColor':
|
|
88
|
+
case 'startcolor':
|
|
89
|
+
this._startColor = value
|
|
90
|
+
this._updateMarkColor()
|
|
91
|
+
break
|
|
92
|
+
case 'endColor':
|
|
93
|
+
case 'endcolor':
|
|
94
|
+
this._endColor = value
|
|
95
|
+
this._updateMarkColor()
|
|
96
|
+
break
|
|
97
|
+
case 'isSupportGradient':
|
|
98
|
+
case 'issupportgradient':
|
|
99
|
+
this._isSupportGradient = value === true || value === 'true'
|
|
100
|
+
this._updateMarkColor()
|
|
101
|
+
break
|
|
102
|
+
default:
|
|
103
|
+
super.updateProperty(key, value)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_getBarColor() {
|
|
108
|
+
if (this._isSupportGradient && this._startColor && this._endColor) {
|
|
109
|
+
return this._startColor
|
|
110
|
+
}
|
|
111
|
+
return this._markColor
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_updateMarkColor() {
|
|
115
|
+
const color = this._getBarColor()
|
|
116
|
+
this._bars.forEach((bar) => {
|
|
117
|
+
bar.style.backgroundColor = color
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_startAnimation() {
|
|
122
|
+
// 使用固定周期实现完美循环动画
|
|
123
|
+
const cycleDuration = 60 // 60帧一个完整周期
|
|
124
|
+
let frame = 0
|
|
125
|
+
|
|
126
|
+
const animate = () => {
|
|
127
|
+
const rect = this.dom.getBoundingClientRect()
|
|
128
|
+
const containerWidth = rect.width || 26
|
|
129
|
+
const containerHeight = rect.height || 30
|
|
130
|
+
const barCount = this._bars.length
|
|
131
|
+
|
|
132
|
+
// 计算 bar 宽度,确保不超出容器
|
|
133
|
+
const totalGap = this._gap * (barCount - 1)
|
|
134
|
+
const barWidth = Math.floor((containerWidth - totalGap) / barCount)
|
|
135
|
+
|
|
136
|
+
// 循环帧计数
|
|
137
|
+
frame = (frame + 1) % cycleDuration
|
|
138
|
+
const t = (frame / cycleDuration) * Math.PI * 2
|
|
139
|
+
|
|
140
|
+
this._bars.forEach((bar, i) => {
|
|
141
|
+
// 每条线有不同的相位偏移,形成波浪式音乐节奏效果
|
|
142
|
+
const phaseOffset = (i / barCount) * Math.PI * 2
|
|
143
|
+
const wave = Math.sin(t * 2 + phaseOffset)
|
|
144
|
+
|
|
145
|
+
// 高度范围:最小 20%,最大 95%
|
|
146
|
+
const heightRatio = 0.2 + (wave + 1) * 0.375
|
|
147
|
+
const barHeight = Math.max(4, containerHeight * heightRatio)
|
|
148
|
+
|
|
149
|
+
bar.style.width = `${barWidth}px`
|
|
150
|
+
bar.style.height = `${barHeight}px`
|
|
151
|
+
bar.style.left = `${i * (barWidth + this._gap)}px`
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
this._animationFrame = requestAnimationFrame(animate)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
animate()
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
destroy() {
|
|
161
|
+
if (this._animationFrame) {
|
|
162
|
+
cancelAnimationFrame(this._animationFrame)
|
|
163
|
+
this._animationFrame = null
|
|
164
|
+
}
|
|
165
|
+
this._bars = []
|
|
166
|
+
super.destroy()
|
|
167
|
+
}
|
|
168
|
+
}
|