@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,199 @@
|
|
|
1
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
2
|
+
|
|
3
|
+
window.__HIPPY_COMPONENT_REGISTRY__ = window.__HIPPY_COMPONENT_REGISTRY__ || new Map()
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* QtProgressBar Component for web platform
|
|
7
|
+
*/
|
|
8
|
+
export class QtProgressBar extends QtBaseComponent {
|
|
9
|
+
constructor(context, id, pId) {
|
|
10
|
+
super(context, id, pId)
|
|
11
|
+
this.tagName = 'TVProgressBarViewComponent'
|
|
12
|
+
this.dom = document.createElement('div')
|
|
13
|
+
this.dom.style.display = 'block'
|
|
14
|
+
this.dom.style.position = 'relative'
|
|
15
|
+
this.dom.style.overflow = 'hidden'
|
|
16
|
+
window.__HIPPY_COMPONENT_REGISTRY__.set(this.dom, this)
|
|
17
|
+
|
|
18
|
+
this._maxProgress = 100
|
|
19
|
+
this._progress = 0
|
|
20
|
+
this._secondaryProgress = 0
|
|
21
|
+
this._cornerRadius = 0
|
|
22
|
+
this._backgroundColor = '#CCCCCC'
|
|
23
|
+
this._progressColor = '#FF0000'
|
|
24
|
+
|
|
25
|
+
this._bgEl = null
|
|
26
|
+
this._secondaryEl = null
|
|
27
|
+
this._progressEl = null
|
|
28
|
+
|
|
29
|
+
this._initDOM()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_initDOM() {
|
|
33
|
+
// Background layer
|
|
34
|
+
this._bgEl = document.createElement('div')
|
|
35
|
+
this._bgEl.style.position = 'absolute'
|
|
36
|
+
this._bgEl.style.top = '0'
|
|
37
|
+
this._bgEl.style.left = '0'
|
|
38
|
+
this._bgEl.style.width = '100%'
|
|
39
|
+
this._bgEl.style.height = '100%'
|
|
40
|
+
this._bgEl.style.boxSizing = 'border-box'
|
|
41
|
+
this.dom.appendChild(this._bgEl)
|
|
42
|
+
|
|
43
|
+
// Secondary progress layer
|
|
44
|
+
this._secondaryEl = document.createElement('div')
|
|
45
|
+
this._secondaryEl.style.position = 'absolute'
|
|
46
|
+
this._secondaryEl.style.top = '0'
|
|
47
|
+
this._secondaryEl.style.left = '0'
|
|
48
|
+
this._secondaryEl.style.height = '100%'
|
|
49
|
+
this._secondaryEl.style.boxSizing = 'border-box'
|
|
50
|
+
this.dom.appendChild(this._secondaryEl)
|
|
51
|
+
|
|
52
|
+
// Progress layer
|
|
53
|
+
this._progressEl = document.createElement('div')
|
|
54
|
+
this._progressEl.style.position = 'absolute'
|
|
55
|
+
this._progressEl.style.top = '0'
|
|
56
|
+
this._progressEl.style.left = '0'
|
|
57
|
+
this._progressEl.style.height = '100%'
|
|
58
|
+
this._progressEl.style.boxSizing = 'border-box'
|
|
59
|
+
this.dom.appendChild(this._progressEl)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_updateStyles() {
|
|
63
|
+
const radius = this._cornerRadius + 'px'
|
|
64
|
+
|
|
65
|
+
// Background
|
|
66
|
+
this._bgEl.style.backgroundColor = this._backgroundColor
|
|
67
|
+
this._bgEl.style.borderRadius = radius
|
|
68
|
+
|
|
69
|
+
// Secondary progress
|
|
70
|
+
const secondaryWidth = (this._secondaryProgress / this._maxProgress) * 100 + '%'
|
|
71
|
+
this._secondaryEl.style.width = secondaryWidth
|
|
72
|
+
this._secondaryEl.style.backgroundColor = this._parseColor(this._progressColor, 0.3)
|
|
73
|
+
this._secondaryEl.style.borderRadius = radius
|
|
74
|
+
|
|
75
|
+
// Progress
|
|
76
|
+
const progressWidth = (this._progress / this._maxProgress) * 100 + '%'
|
|
77
|
+
this._progressEl.style.width = progressWidth
|
|
78
|
+
this._applyProgressColor()
|
|
79
|
+
this._progressEl.style.borderRadius = radius
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_parseColor(color, alpha) {
|
|
83
|
+
if (typeof color === 'string') {
|
|
84
|
+
// If it's a hex color
|
|
85
|
+
if (color.startsWith('#')) {
|
|
86
|
+
const hex = color.slice(1)
|
|
87
|
+
const r = parseInt(hex.substr(0, 2), 16)
|
|
88
|
+
const g = parseInt(hex.substr(2, 2), 16)
|
|
89
|
+
const b = parseInt(hex.substr(4, 2), 16)
|
|
90
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`
|
|
91
|
+
}
|
|
92
|
+
return color
|
|
93
|
+
}
|
|
94
|
+
return color
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
_applyProgressColor() {
|
|
98
|
+
const color = this._progressColor
|
|
99
|
+
|
|
100
|
+
if (color && typeof color === 'object' && color.startColor && color.endColor) {
|
|
101
|
+
// Gradient color
|
|
102
|
+
const type = color.type || 0 // 0: linear, 1: radial
|
|
103
|
+
const shape = color.shape || 0 // 0: rectangle, 1: oval
|
|
104
|
+
const start = color.startColor
|
|
105
|
+
const end = color.endColor
|
|
106
|
+
|
|
107
|
+
if (type === 0) {
|
|
108
|
+
// Linear gradient - horizontal
|
|
109
|
+
this._progressEl.style.background = `linear-gradient(to right, ${start}, ${end})`
|
|
110
|
+
} else {
|
|
111
|
+
// Radial gradient
|
|
112
|
+
this._progressEl.style.background = `radial-gradient(circle, ${start}, ${end})`
|
|
113
|
+
}
|
|
114
|
+
} else if (typeof color === 'string') {
|
|
115
|
+
this._progressEl.style.backgroundColor = color
|
|
116
|
+
} else if (typeof color === 'number') {
|
|
117
|
+
// Convert int color to hex
|
|
118
|
+
const a = ((color >> 24) & 0xff) / 255
|
|
119
|
+
const r = (color >> 16) & 0xff
|
|
120
|
+
const g = (color >> 8) & 0xff
|
|
121
|
+
const b = color & 0xff
|
|
122
|
+
this._progressEl.style.backgroundColor = `rgba(${r}, ${g}, ${b}, ${a})`
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
updateProperty(key, value) {
|
|
127
|
+
switch (key) {
|
|
128
|
+
case 'maxProgress':
|
|
129
|
+
case 'max-progress':
|
|
130
|
+
this._maxProgress = value || 100
|
|
131
|
+
this._updateStyles()
|
|
132
|
+
break
|
|
133
|
+
case 'progress':
|
|
134
|
+
this._progress = value || 0
|
|
135
|
+
this._updateStyles()
|
|
136
|
+
break
|
|
137
|
+
case 'secondaryProgress':
|
|
138
|
+
case 'secondary-progress':
|
|
139
|
+
this._secondaryProgress = value || 0
|
|
140
|
+
this._updateStyles()
|
|
141
|
+
break
|
|
142
|
+
case 'cornerRadius':
|
|
143
|
+
case 'corner-radius':
|
|
144
|
+
this._cornerRadius = value || 0
|
|
145
|
+
this._updateStyles()
|
|
146
|
+
break
|
|
147
|
+
case 'backgroundColor':
|
|
148
|
+
case 'background-color':
|
|
149
|
+
this._backgroundColor = value || '#CCCCCC'
|
|
150
|
+
this._updateStyles()
|
|
151
|
+
break
|
|
152
|
+
case 'color':
|
|
153
|
+
this._progressColor = value
|
|
154
|
+
this._updateStyles()
|
|
155
|
+
break
|
|
156
|
+
default:
|
|
157
|
+
super.updateProperty(key, value)
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// API methods called via callUIFunction
|
|
162
|
+
setMaxProgress(params) {
|
|
163
|
+
this._maxProgress = params[0] || 100
|
|
164
|
+
this._updateStyles()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setProgress(params) {
|
|
168
|
+
this._progress = params[0] || 0
|
|
169
|
+
this._updateStyles()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
setSecondaryProgress(params) {
|
|
173
|
+
this._secondaryProgress = params[0] || 0
|
|
174
|
+
this._updateStyles()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
setProgressColor(params) {
|
|
178
|
+
this._progressColor = params[0]
|
|
179
|
+
this._updateStyles()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
setProgressDefaultColor(params) {
|
|
183
|
+
this._backgroundColor = params[0]
|
|
184
|
+
this._updateStyles()
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
init(props) {
|
|
188
|
+
super.init(props)
|
|
189
|
+
|
|
190
|
+
if (props.maxProgress !== undefined) this._maxProgress = props.maxProgress
|
|
191
|
+
if (props.progress !== undefined) this._progress = props.progress
|
|
192
|
+
if (props.secondaryProgress !== undefined) this._secondaryProgress = props.secondaryProgress
|
|
193
|
+
if (props.cornerRadius !== undefined) this._cornerRadius = props.cornerRadius
|
|
194
|
+
if (props.backgroundColor !== undefined) this._backgroundColor = props.backgroundColor
|
|
195
|
+
if (props.color !== undefined) this._progressColor = props.color
|
|
196
|
+
|
|
197
|
+
this._updateStyles()
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
2
|
+
|
|
3
|
+
window.__HIPPY_COMPONENT_REGISTRY__ = window.__HIPPY_COMPONENT_REGISTRY__ || new Map()
|
|
4
|
+
|
|
5
|
+
// Cache for qrcode library
|
|
6
|
+
let QRCodeLib = null
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* QtQRCode Component using qrcode library (web only)
|
|
10
|
+
*/
|
|
11
|
+
export class QtQRCode extends QtBaseComponent {
|
|
12
|
+
constructor(context, id, pId) {
|
|
13
|
+
super(context, id, pId)
|
|
14
|
+
this.tagName = 'QrCodeComponent'
|
|
15
|
+
this.dom = document.createElement('div')
|
|
16
|
+
this.dom.style.display = 'block'
|
|
17
|
+
window.__HIPPY_COMPONENT_REGISTRY__.set(this.dom, this)
|
|
18
|
+
this._content = ''
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async _render() {
|
|
22
|
+
if (!this._content || !this.dom) return
|
|
23
|
+
|
|
24
|
+
// Lazy load qrcode library (web platform only)
|
|
25
|
+
if (!QRCodeLib) {
|
|
26
|
+
try {
|
|
27
|
+
QRCodeLib = await import('qrcode').then((m) => m.default || m)
|
|
28
|
+
} catch (e) {
|
|
29
|
+
console.error('[QtQRCode] Failed to load qrcode library:', e)
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const rect = this.dom.getBoundingClientRect()
|
|
35
|
+
const size = Math.max(rect.width || 200, rect.height || 200)
|
|
36
|
+
|
|
37
|
+
QRCodeLib.toDataURL(
|
|
38
|
+
this._content,
|
|
39
|
+
{
|
|
40
|
+
width: size,
|
|
41
|
+
margin: 2,
|
|
42
|
+
color: {
|
|
43
|
+
dark: '#000000',
|
|
44
|
+
light: '#FFFFFF',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
(err, url) => {
|
|
48
|
+
if (err) {
|
|
49
|
+
console.error('[QtQRCode] Error:', err)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.dom.innerHTML = ''
|
|
54
|
+
const img = document.createElement('img')
|
|
55
|
+
img.src = url
|
|
56
|
+
img.style.width = '100%'
|
|
57
|
+
img.style.height = '100%'
|
|
58
|
+
img.style.display = 'block'
|
|
59
|
+
this.dom.appendChild(img)
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
updateProperty(key, value) {
|
|
65
|
+
if (key === 'content') {
|
|
66
|
+
this._content = value || ''
|
|
67
|
+
this._render()
|
|
68
|
+
} else {
|
|
69
|
+
super.updateProperty(key, value)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
init(props) {
|
|
74
|
+
super.init(props)
|
|
75
|
+
this._content = props.content || ''
|
|
76
|
+
this._render()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// QtReplaceChild - Web adapter for qt-replace-child
|
|
2
|
+
// In native Android, this component marks a placeholder position for view replacement
|
|
3
|
+
// On web platform, we simplify it as a container with SID support
|
|
4
|
+
|
|
5
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
6
|
+
import {
|
|
7
|
+
registerComponent,
|
|
8
|
+
registerComponentBySid,
|
|
9
|
+
unregisterComponentBySid,
|
|
10
|
+
} from '../core/componentRegistry'
|
|
11
|
+
|
|
12
|
+
export class QtReplaceChild extends QtBaseComponent {
|
|
13
|
+
constructor(context, id, pId) {
|
|
14
|
+
super(context, id, pId)
|
|
15
|
+
this.tagName = 'QtReplaceChild'
|
|
16
|
+
|
|
17
|
+
// Create a div as placeholder container
|
|
18
|
+
this.dom = document.createElement('div')
|
|
19
|
+
this.dom.style.cssText = `
|
|
20
|
+
position: absolute;
|
|
21
|
+
width: 1920px;
|
|
22
|
+
height: 1080px;
|
|
23
|
+
background-color: transparent;
|
|
24
|
+
`
|
|
25
|
+
|
|
26
|
+
// State
|
|
27
|
+
this._markChildSID = ''
|
|
28
|
+
this._replaceOnVisibilityChanged = false
|
|
29
|
+
this._currentSid = null
|
|
30
|
+
|
|
31
|
+
// Register component
|
|
32
|
+
registerComponent(id, this)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Mark child SID - the target view that will be replaced into this position
|
|
37
|
+
* In native, this enables the replacement mechanism
|
|
38
|
+
* On web, we just store it for reference
|
|
39
|
+
*/
|
|
40
|
+
setMarkChildSID(sid) {
|
|
41
|
+
this._markChildSID = sid
|
|
42
|
+
console.log('[QtReplaceChild] markChildSID set to:', sid)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Set child SID dynamically - triggers view replacement
|
|
47
|
+
* In native, this moves the target view to this placeholder
|
|
48
|
+
* On web, we can simulate by finding and cloning the target
|
|
49
|
+
*/
|
|
50
|
+
setChildSID(sid) {
|
|
51
|
+
console.log('[QtReplaceChild] setChildSID:', sid)
|
|
52
|
+
|
|
53
|
+
if (!sid) return
|
|
54
|
+
|
|
55
|
+
// Find the target component by SID
|
|
56
|
+
const targetComponent = this._findComponentBySID(sid)
|
|
57
|
+
|
|
58
|
+
if (targetComponent && targetComponent.dom) {
|
|
59
|
+
console.log('[QtReplaceChild] Found target component, cloning to placeholder')
|
|
60
|
+
|
|
61
|
+
// Clear current content
|
|
62
|
+
this.dom.innerHTML = ''
|
|
63
|
+
|
|
64
|
+
// Clone the target DOM and append
|
|
65
|
+
const clonedDom = targetComponent.dom.cloneNode(true)
|
|
66
|
+
this.dom.appendChild(clonedDom)
|
|
67
|
+
} else {
|
|
68
|
+
console.log('[QtReplaceChild] Target component not found for SID:', sid)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Set replace on visibility changed
|
|
74
|
+
*/
|
|
75
|
+
setReplaceOnVisibilityChanged(value) {
|
|
76
|
+
this._replaceOnVisibilityChanged = value
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Handle property updates
|
|
81
|
+
*/
|
|
82
|
+
updateProperty(key, value) {
|
|
83
|
+
// Handle sid (string id) property - register to sidRegistry
|
|
84
|
+
if (key === 'sid') {
|
|
85
|
+
// Unregister previous sid if exists
|
|
86
|
+
if (this._currentSid) {
|
|
87
|
+
unregisterComponentBySid(this._currentSid)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Register new sid
|
|
91
|
+
this._currentSid = value
|
|
92
|
+
if (value) {
|
|
93
|
+
registerComponentBySid(value, this)
|
|
94
|
+
// Also set as DOM attribute for debugging
|
|
95
|
+
this.dom.setAttribute('sid', String(value))
|
|
96
|
+
} else {
|
|
97
|
+
this.dom.removeAttribute('sid')
|
|
98
|
+
}
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
switch (key) {
|
|
103
|
+
case 'markChildSID':
|
|
104
|
+
case 'markChildSid':
|
|
105
|
+
this.setMarkChildSID(value)
|
|
106
|
+
break
|
|
107
|
+
case 'replaceOnVisibilityChanged':
|
|
108
|
+
this.setReplaceOnVisibilityChanged(value)
|
|
109
|
+
break
|
|
110
|
+
default:
|
|
111
|
+
super.updateProperty(key, value)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Find component by SID from global registry
|
|
117
|
+
*/
|
|
118
|
+
_findComponentBySID(sid) {
|
|
119
|
+
// Import findComponentBySid from componentRegistry
|
|
120
|
+
const { findComponentBySid } = require('../core/componentRegistry')
|
|
121
|
+
|
|
122
|
+
// First try using the official registry
|
|
123
|
+
const component = findComponentBySid(sid)
|
|
124
|
+
if (component) {
|
|
125
|
+
return component
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Fallback: Try to find from DOM by sid attribute
|
|
129
|
+
const domElement = document.querySelector(`[sid="${sid}"]`)
|
|
130
|
+
if (domElement) {
|
|
131
|
+
return { dom: domElement }
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Called when component is unmounted - cleanup sid registration
|
|
138
|
+
beforeUnmount() {
|
|
139
|
+
if (this._currentSid) {
|
|
140
|
+
unregisterComponentBySid(this._currentSid)
|
|
141
|
+
this._currentSid = null
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Legacy destroy method
|
|
146
|
+
destroy() {
|
|
147
|
+
this.beforeUnmount?.()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// QtRippleView - A ripple animation view component
|
|
2
|
+
import { QtBaseComponent } from './QtBaseComponent'
|
|
3
|
+
import { registerComponent } from '../core/componentRegistry'
|
|
4
|
+
|
|
5
|
+
export class QtRippleView extends QtBaseComponent {
|
|
6
|
+
constructor(context, id, pId) {
|
|
7
|
+
super(context, id, pId)
|
|
8
|
+
this.tagName = 'RippleViewComponent'
|
|
9
|
+
this.dom = document.createElement('div')
|
|
10
|
+
this.dom.setAttribute('data-component-name', 'QtRippleView')
|
|
11
|
+
|
|
12
|
+
this.dom.style.cssText = `
|
|
13
|
+
display: block;
|
|
14
|
+
position: relative;
|
|
15
|
+
box-sizing: border-box;
|
|
16
|
+
overflow: hidden;
|
|
17
|
+
`
|
|
18
|
+
|
|
19
|
+
this._isShowRipple = false
|
|
20
|
+
this._rippleVisible = 'visible'
|
|
21
|
+
this._color = '#FF4E46'
|
|
22
|
+
this._animationFrame = null
|
|
23
|
+
|
|
24
|
+
registerComponent(id, this)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
defaultStyle() {
|
|
28
|
+
return {
|
|
29
|
+
display: 'block',
|
|
30
|
+
position: 'relative',
|
|
31
|
+
boxSizing: 'border-box',
|
|
32
|
+
overflow: 'hidden',
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
updateProperty(key, value) {
|
|
37
|
+
switch (key) {
|
|
38
|
+
case 'isShowRipple':
|
|
39
|
+
case 'isshowripple':
|
|
40
|
+
this._isShowRipple = value === true || value === 'true'
|
|
41
|
+
this._updateRippleAnimation()
|
|
42
|
+
break
|
|
43
|
+
case 'rippleVisible':
|
|
44
|
+
case 'ripplevisible':
|
|
45
|
+
this._rippleVisible = value || 'visible'
|
|
46
|
+
this.dom.style.visibility = this._rippleVisible
|
|
47
|
+
break
|
|
48
|
+
case 'color':
|
|
49
|
+
this._color = value || '#FF4E46'
|
|
50
|
+
this._updateRippleColor()
|
|
51
|
+
break
|
|
52
|
+
case 'delayLoad':
|
|
53
|
+
case 'delayload':
|
|
54
|
+
if (value > 0) {
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
this._startRippleAnimation()
|
|
57
|
+
}, value)
|
|
58
|
+
} else {
|
|
59
|
+
this._startRippleAnimation()
|
|
60
|
+
}
|
|
61
|
+
break
|
|
62
|
+
default:
|
|
63
|
+
super.updateProperty(key, value)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_updateRippleAnimation() {
|
|
68
|
+
if (this._isShowRipple) {
|
|
69
|
+
this._startRippleAnimation()
|
|
70
|
+
} else {
|
|
71
|
+
this._stopRippleAnimation()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
_updateRippleColor() {
|
|
76
|
+
const ripples = this.dom.querySelectorAll('.ripple-circle')
|
|
77
|
+
ripples.forEach((ripple) => {
|
|
78
|
+
ripple.style.backgroundColor = this._color
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_startRippleAnimation() {
|
|
83
|
+
if (!this._isShowRipple) return
|
|
84
|
+
|
|
85
|
+
this._clearRipples()
|
|
86
|
+
|
|
87
|
+
const rect = this.dom.getBoundingClientRect()
|
|
88
|
+
const size = Math.max(rect.width, rect.height)
|
|
89
|
+
const centerX = rect.width / 2
|
|
90
|
+
const centerY = rect.height / 2
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < 3; i++) {
|
|
93
|
+
setTimeout(() => {
|
|
94
|
+
if (!this._isShowRipple) return
|
|
95
|
+
this._createRippleCircle(centerX, centerY, size, i)
|
|
96
|
+
}, i * 300)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
_createRippleCircle(centerX, centerY, size, index) {
|
|
101
|
+
const ripple = document.createElement('div')
|
|
102
|
+
ripple.className = 'ripple-circle'
|
|
103
|
+
ripple.style.cssText = `
|
|
104
|
+
position: absolute;
|
|
105
|
+
left: ${centerX}px;
|
|
106
|
+
top: ${centerY}px;
|
|
107
|
+
width: 0;
|
|
108
|
+
height: 0;
|
|
109
|
+
border-radius: 50%;
|
|
110
|
+
background-color: ${this._color};
|
|
111
|
+
opacity: 0.6;
|
|
112
|
+
transform: translate(-50%, -50%);
|
|
113
|
+
pointer-events: none;
|
|
114
|
+
`
|
|
115
|
+
this.dom.appendChild(ripple)
|
|
116
|
+
|
|
117
|
+
let progress = 0
|
|
118
|
+
const maxProgress = size * 1.5
|
|
119
|
+
const duration = 1500 + index * 200
|
|
120
|
+
|
|
121
|
+
const animate = () => {
|
|
122
|
+
if (!this._isShowRipple) {
|
|
123
|
+
ripple.remove()
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
progress += (maxProgress / duration) * 16
|
|
128
|
+
|
|
129
|
+
if (progress < maxProgress) {
|
|
130
|
+
const opacity = 0.6 * (1 - progress / maxProgress)
|
|
131
|
+
|
|
132
|
+
ripple.style.width = `${progress}px`
|
|
133
|
+
ripple.style.height = `${progress}px`
|
|
134
|
+
ripple.style.opacity = opacity.toString()
|
|
135
|
+
|
|
136
|
+
this._animationFrame = requestAnimationFrame(animate)
|
|
137
|
+
} else {
|
|
138
|
+
ripple.style.width = '0'
|
|
139
|
+
ripple.style.height = '0'
|
|
140
|
+
ripple.style.opacity = '0.6'
|
|
141
|
+
progress = 0
|
|
142
|
+
this._animationFrame = requestAnimationFrame(animate)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
animate()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
_stopRippleAnimation() {
|
|
150
|
+
if (this._animationFrame) {
|
|
151
|
+
cancelAnimationFrame(this._animationFrame)
|
|
152
|
+
this._animationFrame = null
|
|
153
|
+
}
|
|
154
|
+
this._clearRipples()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
_clearRipples() {
|
|
158
|
+
const ripples = this.dom.querySelectorAll('.ripple-circle')
|
|
159
|
+
ripples.forEach((ripple) => ripple.remove())
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
destroy() {
|
|
163
|
+
this._stopRippleAnimation()
|
|
164
|
+
super.destroy()
|
|
165
|
+
}
|
|
166
|
+
}
|