u-row-notice.vue
9.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
<template>
<view
class="u-notice"
@tap="clickHandler"
>
<slot name="icon">
<view
class="u-notice__left-icon"
v-if="icon"
>
<u-icon
:name="icon"
:color="color"
size="19"
></u-icon>
</view>
</slot>
<view
class="u-notice__content"
ref="u-notice__content"
>
<view
ref="u-notice__content__text"
class="u-notice__content__text"
:style="[animationStyle]"
>
<text
v-for="(item, index) in innerText"
:key="index"
:style="[textStyle]"
>{{item}}</text>
</view>
</view>
<view
class="u-notice__right-icon"
v-if="['link', 'closable'].includes(mode)"
>
<u-icon
v-if="mode === 'link'"
name="arrow-right"
:size="17"
:color="color"
></u-icon>
<u-icon
v-if="mode === 'closable'"
@click="close"
name="close"
:size="16"
:color="color"
></u-icon>
</view>
</view>
</template>
<script>
import props from './props.js';
// #ifdef APP-NVUE
const animation = uni.requireNativePlugin('animation')
const dom = uni.requireNativePlugin('dom')
// #endif
/**
* RowNotice 滚动通知中的水平滚动模式
* @description 水平滚动
* @tutorial https://www.uviewui.com/components/noticeBar.html
* @property {String | Number} text 显示的内容,字符串
* @property {String} icon 是否显示左侧的音量图标 (默认 'volume' )
* @property {String} mode 通告模式,link-显示右箭头,closable-显示右侧关闭图标
* @property {String} color 文字颜色,各图标也会使用文字颜色 (默认 '#f9ae3d' )
* @property {String} bgColor 背景颜色 (默认 ''#fdf6ec' )
* @property {String | Number} fontSize 字体大小,单位px (默认 14 )
* @property {String | Number} speed 水平滚动时的滚动速度,即每秒滚动多少px(rpx),这有利于控制文字无论多少时,都能有一个恒定的速度 (默认 80 )
*
* @event {Function} click 点击通告文字触发
* @event {Function} close 点击右侧关闭图标触发
* @example
*/
export default {
name: 'u-row-notice',
mixins: [uni.$u.mpMixin, uni.$u.mixin,props],
data() {
return {
animationDuration: '0', // 动画执行时间
animationPlayState: 'paused', // 动画的开始和结束执行
// nvue下,内容发生变化,导致滚动宽度也变化,需要标志为是否需要重新计算宽度
// 不能在内容变化时直接重新计算,因为nvue的animation模块上一次的滚动不是刚好结束,会有影响
nvueInit: true,
show: true
};
},
watch: {
text: {
immediate: true,
handler(newValue, oldValue) {
// #ifdef APP-NVUE
this.nvueInit = true
// #endif
// #ifndef APP-NVUE
this.vue()
// #endif
if(!uni.$u.test.string(newValue)) {
uni.$u.error('noticebar组件direction为row时,要求text参数为字符串形式')
}
}
},
fontSize() {
// #ifdef APP-NVUE
this.nvueInit = true
// #endif
// #ifndef APP-NVUE
this.vue()
// #endif
},
speed() {
// #ifdef APP-NVUE
this.nvueInit = true
// #endif
// #ifndef APP-NVUE
this.vue()
// #endif
}
},
computed: {
// 文字内容的样式
textStyle() {
let style = {}
style.color = this.color
style.fontSize = uni.$u.addUnit(this.fontSize)
return style
},
animationStyle() {
let style = {}
style.animationDuration = this.animationDuration
style.animationPlayState = this.animationPlayState
return style
},
// 内部对用户传入的数据进一步分割,放到多个text标签循环,否则如果用户传入的字符串很长(100个字符以上)
// 放在一个text标签中进行滚动,在低端安卓机上,动画可能会出现抖动现象,需要分割到多个text中可解决此问题
innerText() {
let result = [],
// 每组text标签的字符长度
len = 20
const textArr = this.text.split('')
for (let i = 0; i < textArr.length; i += len) {
// 对拆分的后的text进行slice分割,得到的为数组再进行join拼接为字符串
result.push(textArr.slice(i, i + len).join(''))
}
return result
}
},
mounted() {
// #ifdef APP-PLUS
// 在APP上(含nvue),监听当前webview是否处于隐藏状态(进入下一页时即为hide状态)
// 如果webivew隐藏了,为了节省性能的损耗,应停止动画的执行,同时也是为了保持进入下一页返回后,滚动位置保持不变
var pages = getCurrentPages()
var page = pages[pages.length - 1]
var currentWebview = page.$getAppWebview()
currentWebview.addEventListener('hide', () => {
this.webviewHide = true
})
currentWebview.addEventListener('show', () => {
this.webviewHide = false
})
// #endif
this.init()
},
methods: {
init() {
// #ifdef APP-NVUE
this.nvue()
// #endif
// #ifndef APP-NVUE
this.vue()
// #endif
if(!uni.$u.test.string(this.text)) {
uni.$u.error('noticebar组件direction为row时,要求text参数为字符串形式')
}
},
// vue版处理
async vue() {
// #ifndef APP-NVUE
let boxWidth = 0,
textWidth = 0
// 进行一定的延时
await uni.$u.sleep()
// 查询盒子和文字的宽度
textWidth = (await this.$uGetRect('.u-notice__content__text')).width
boxWidth = (await this.$uGetRect('.u-notice__content')).width
// 根据t=s/v(时间=路程/速度),这里为何不需要加上#u-notice-box的宽度,因为中设置了.u-notice-content样式中设置了padding-left: 100%
// 恰巧计算出来的结果中已经包含了#u-notice-box的宽度
this.animationDuration = `${textWidth / uni.$u.getPx(this.speed)}s`
// 这里必须这样开始动画,否则在APP上动画速度不会改变
this.animationPlayState = 'paused'
setTimeout(() => {
this.animationPlayState = 'running'
}, 10)
// #endif
},
// nvue版处理
async nvue() {
// #ifdef APP-NVUE
this.nvueInit = false
let boxWidth = 0,
textWidth = 0
// 进行一定的延时
await uni.$u.sleep()
// 查询盒子和文字的宽度
textWidth = (await this.getNvueRect('u-notice__content__text')).width
boxWidth = (await this.getNvueRect('u-notice__content')).width
// 将文字移动到盒子的右边沿,之所以需要这么做,是因为nvue不支持100%单位,否则可以通过css设置
animation.transition(this.$refs['u-notice__content__text'], {
styles: {
transform: `translateX(${boxWidth}px)`
},
}, () => {
// 如果非禁止动画,则开始滚动
!this.stopAnimation && this.loopAnimation(textWidth, boxWidth)
});
// #endif
},
loopAnimation(textWidth, boxWidth) {
// #ifdef APP-NVUE
animation.transition(this.$refs['u-notice__content__text'], {
styles: {
// 目标移动终点为-textWidth,也即当文字的最右边贴到盒子的左边框的位置
transform: `translateX(-${textWidth}px)`
},
// 滚动时间的计算为,时间 = 路程(boxWidth + textWidth) / 速度,最后转为毫秒
duration: (boxWidth + textWidth) / uni.$u.getPx(this.speed) * 1000,
delay: 10
}, () => {
animation.transition(this.$refs['u-notice__content__text'], {
styles: {
// 重新将文字移动到盒子的右边沿
transform: `translateX(${this.stopAnimation ? 0 : boxWidth}px)`
},
}, () => {
// 如果非禁止动画,则继续下一轮滚动
if (!this.stopAnimation) {
// 判断是否需要初始化计算尺寸
if (this.nvueInit) {
this.nvue()
} else {
this.loopAnimation(textWidth, boxWidth)
}
}
});
})
// #endif
},
getNvueRect(el) {
// #ifdef APP-NVUE
// 返回一个promise
return new Promise(resolve => {
dom.getComponentRect(this.$refs[el], (res) => {
resolve(res.size)
})
})
// #endif
},
// 点击通告栏
clickHandler(index) {
this.$emit('click')
},
// 点击右侧按钮,需要判断点击的是关闭图标还是箭头图标
close() {
this.$emit('close')
}
},
// #ifdef APP-NVUE
beforeDestroy() {
this.stopAnimation = true
},
// #endif
};
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
.u-notice {
@include flex;
align-items: center;
justify-content: space-between;
&__left-icon {
align-items: center;
margin-right: 5px;
}
&__right-icon {
margin-left: 5px;
align-items: center;
}
&__content {
text-align: right;
flex: 1;
@include flex;
flex-wrap: nowrap;
overflow: hidden;
&__text {
font-size: 14px;
color: $u-warning;
/* #ifndef APP-NVUE */
// 这一句很重要,为了能让滚动左右连接起来
padding-left: 100%;
word-break: keep-all;
white-space: nowrap;
animation: u-loop-animation 10s linear infinite both;
/* #endif */
@include flex(row);
}
}
}
@keyframes u-loop-animation {
0% {
transform: translate3d(0, 0, 0);
}
100% {
transform: translate3d(-100%, 0, 0);
}
}
</style>