tencent.jquery.pix.component 1.0.53

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.
@@ -0,0 +1,450 @@
1
+ import "./banner.scss"
2
+ import { $, windowEnv } from "../config";
3
+ import { addResizeFunc, nextAnimationFrame, removeResizeFunc } from "../utils/utils";
4
+
5
+ /**
6
+ * 在页面上放置一个banner组件
7
+ * @constructor
8
+ * @param {object} options 选项
9
+ * @param {string} options.container 容器元素的jquery选择器
10
+ * @param {Array<{url: string, title: string}>} options.list 轮播图列表, url 为图片背景, title 为图片标题
11
+ * @param {number} [options.index=0] 轮播图初始位置
12
+ * @param {boolean} [options.isTitleEnabled=true] 是否启用标题、翻页栏
13
+ * @param {number} [options.durationMs=0] 轮播图切换时间间隔,单位毫秒,0表示不自动轮播,默认为 0
14
+ * @param {boolean} [options.autoResize=true] 是否自动调整容器宽度以适应屏幕宽度,默认为 true
15
+ * @param {number} [options.dragThreshold=0.2] 拖动翻页阈值,表示拖动距离占宽度的比例,默认为 0.2(20%)
16
+ * @param {function} [options.click] 点击轮播图时触发,第一个参数为触发元素的jq,第二个参数为 options.list 中对应的项,第三个参数为当前页码
17
+ * @param {function} [options.pageChanged] 翻页时触发,第一个参数为新生效页面元素的jq,第二个参数为 options.list 中对应的项,第三个参数为当前页码
18
+ * @param {function} [options.renderCallback] 自定义渲染回调,第一个参数为 options.list 中对应的项,第二个参数为当前页码,返回值为渲染后的元素
19
+ */
20
+ export function Banner(options = {}){
21
+ this.options = options;
22
+ this.options.isTitleEnabled ??= true;
23
+ this.options.autoResize ??= true;
24
+ this.options.durationMs = Number(options.durationMs || 0) || 0;
25
+ this.options.dragThreshold = Number(options.dragThreshold || 0.2) || 0.2;
26
+ this.index = Number(options.index || 0) || 0;
27
+ this.init();
28
+ }
29
+
30
+ Banner.prototype.setWidth = function(width) {
31
+ this.signWidth = width;
32
+ this.allWidth = this.signWidth * (this.options.list.length + 2);
33
+ $(this.options.container).width(width);
34
+ this.$inner.width(this.allWidth);
35
+ this.$inner.find('.banner-inner-li').width(this.signWidth);
36
+ this.setTranslate(-this.signWidth * (this.index + 1));
37
+ }
38
+
39
+ Banner.prototype.fitWidth = function() {
40
+ console.log('banner fitWidth');
41
+ const newWidth = $(this.options.container).parent().width();
42
+ if (newWidth != this.signWidth) {
43
+ this.setWidth(newWidth);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * 跳转到上一页
49
+ */
50
+ Banner.prototype.prevPage = async function() {
51
+ this.pauseAutoPlay();
52
+ await this.gotoPageUnchecked(this.index - 1);
53
+ this.startAutoPlay();
54
+ }
55
+
56
+ /**
57
+ * 跳转到下一页
58
+ */
59
+ Banner.prototype.nextPage = async function() {
60
+ this.pauseAutoPlay();
61
+ await this.gotoPageUnchecked(this.index + 1);
62
+ this.startAutoPlay();
63
+ }
64
+
65
+ /**
66
+ * 跳转到指定页
67
+ * @param {number} newIdx 要切换到的页码 [0, len)
68
+ */
69
+ Banner.prototype.gotoPage = async function(newIdx) {
70
+ if (newIdx < 0 || newIdx >= this.options.list.length) {
71
+ throw new Error('index out of range');
72
+ }
73
+ if (newIdx == this.index) {
74
+ return
75
+ }
76
+ this.pauseAutoPlay();
77
+ await this.gotoPageUnchecked(newIdx);
78
+ this.startAutoPlay();
79
+ }
80
+
81
+ Banner.prototype.gotoPageUnchecked = async function(newIdx) {
82
+ const len = this.options.list.length;
83
+ let wrap = 0;
84
+ if (newIdx <= -1) {
85
+ newIdx = len - 1;
86
+ wrap = 1;
87
+ } else if (newIdx >= len) {
88
+ newIdx = 0;
89
+ wrap = -1;
90
+ }
91
+
92
+ if (wrap !== 0) {
93
+ this.$inner.css('transition', 'none');
94
+ this.setTranslate(-this.signWidth * (newIdx + 1 + wrap));
95
+ await nextAnimationFrame();
96
+ await nextAnimationFrame();
97
+ }
98
+
99
+ this.$inner.css('transition', 'transform 0.3s ease-out');
100
+ const newTranslate = -this.signWidth * (newIdx+1);
101
+ this.setTranslate(newTranslate);
102
+ if (this.options.isTitleEnabled) {
103
+ this.$pagination.children().eq(newIdx+1).addClass('active').siblings().removeClass('active');
104
+ this.$titleBox.text(this.options.list[newIdx].title);
105
+ }
106
+ if (this.options.pageChanged && newIdx != this.index) {
107
+ this.options.pageChanged(this.$inner.children().eq(newIdx+1), this.options.list[newIdx], newIdx);
108
+ }
109
+ this.index = newIdx;
110
+ }
111
+
112
+ /**
113
+ * 获取当前页的序号
114
+ * @returns {number} 当前页的序号 [0,len]
115
+ */
116
+ Banner.prototype.getCurrentPage = function() {
117
+ return this.index;
118
+ }
119
+
120
+ Banner.prototype.init = function(){
121
+ const $t = $(this.options.container)
122
+ console.log('banner container:', $t.dom);
123
+ const signWidth = $t.width()
124
+ $t.width(`${signWidth}px`);
125
+ $t.css('touch-action', 'none');
126
+ if (this.options.renderCallback) {
127
+ this.createCustomHtml();
128
+ } else {
129
+ this.createHtml();
130
+ }
131
+ this.bindEvent();
132
+ if (this.options.autoResize) {
133
+ addResizeFunc({
134
+ obj: this,
135
+ func: this.fitWidth,
136
+ });
137
+ }
138
+ this.startAutoPlay();
139
+ console.log('list:', this.options.list, ' signWidth:',signWidth)
140
+ }
141
+
142
+ Banner.prototype.startAutoPlay = function() {
143
+ if (this.options.durationMs > 0 && this.timer == null) {
144
+ this.timer = setInterval(() => {
145
+ this.nextPage()
146
+ }, this.options.durationMs);
147
+ }
148
+ }
149
+
150
+ Banner.prototype.pauseAutoPlay = function() {
151
+ clearInterval(this.timer);
152
+ this.timer = null;
153
+ }
154
+
155
+ Banner.prototype.createHtml = function(){
156
+ const len = this.options.list.length
157
+ if(len<1){
158
+ return
159
+ }
160
+ const $t = $(this.options.container)
161
+ $t.empty();
162
+ const signWidth = this.signWidth = $t.width()
163
+
164
+ const allWidth = this.allWidth = signWidth * (len + 2)
165
+ const $inner = this.$inner = $(`<div class="banner-inner-transform"></div>`)
166
+ this.currentTranslate = -signWidth * (this.index + 1)
167
+ $inner.width(allWidth).css('transform',`translateX(${this.currentTranslate}px)`)
168
+
169
+ // 加第0个位置
170
+ $inner.append(`<div class="banner-inner-li" data-background-url="${this.options.list[len-1].url}" style="width:${this.signWidth}px;">
171
+ </div>
172
+ `)
173
+ for(let i=0;i<len;i++){
174
+ const item = this.options.list[i]
175
+ const $li = $(`<div class="banner-inner-li" data-background-url="${item.url}" style="width:${this.signWidth}px;">
176
+ </div>
177
+ `)
178
+ $inner.append($li)
179
+ }
180
+ // 加第N+1
181
+ $inner.append(`<div class="banner-inner-li" data-background-url="${this.options.list[0].url}" style="width:${this.signWidth}px;">
182
+ </div>
183
+ `)
184
+
185
+ const preloadIdx = this.index;
186
+ this.loadBackground(preloadIdx);
187
+ this.loadBackground((preloadIdx + 1) % len); // prev
188
+ this.loadBackground((preloadIdx - 1 + len) % len); // next
189
+
190
+ $t.append($inner)
191
+
192
+ if (this.options.isTitleEnabled) {
193
+ // 创建 titlebox
194
+ const $titleBox = $(`<div class="banner-title-box">${this.options.list[this.index].title}</div>`)
195
+
196
+ // 创建 pagination
197
+ const $pagination = $(`<div class="banner-pagination"></div>`)
198
+
199
+ $pagination.append($titleBox);
200
+ for (let i = 0; i < len; i++) {
201
+ $pagination.append(`<div class="banner-pagination-item ${i === 0 ? 'active' : ''}"></div>`)
202
+ }
203
+
204
+ this.$titleBox = $titleBox
205
+ this.$pagination = $pagination
206
+
207
+ $pagination.children().eq(this.index+1).addClass('active').siblings().removeClass('active')
208
+
209
+ $t.append($pagination)
210
+ }
211
+
212
+ setTimeout(() => {
213
+ this.loadAllBackgrounds();
214
+ }, 1000);
215
+ }
216
+
217
+ /**
218
+ * 由 this.options.renderCallback 生成页面
219
+ */
220
+ Banner.prototype.createCustomHtml = function() {
221
+ const len = this.options.list.length
222
+ if(len<1){
223
+ return
224
+ }
225
+ const $t = $(this.options.container)
226
+ $t.empty();
227
+ const signWidth = this.signWidth = $t.width()
228
+
229
+ const allWidth = this.allWidth = signWidth * (len + 2)
230
+ const $inner = this.$inner = $(`<div class="banner-inner-transform"></div>`)
231
+ this.currentTranslate = -signWidth * (this.index + 1)
232
+ $inner.width(allWidth).css('transform',`translateX(${this.currentTranslate}px)`)
233
+
234
+ let $li = $(this.options.renderCallback(this.options.list[len-1], len-1));
235
+ $li.width(signWidth);
236
+ $inner.append($li);
237
+
238
+ for (let i = 0; i < len; i++) {
239
+ $li = $(this.options.renderCallback(this.options.list[i], i));
240
+ $li.width(signWidth);
241
+ $inner.append($li);
242
+ }
243
+
244
+ $li = $(this.options.renderCallback(this.options.list[0], 0));
245
+ $li.width(signWidth);
246
+ $inner.append($li);
247
+
248
+ $t.append($inner);
249
+ }
250
+
251
+ Banner.prototype.bindEvent = function(){
252
+ const self = this
253
+ const len = this.options.list.length;
254
+ const $inner = this.$inner;
255
+
256
+ // pStart 时的 clientX
257
+ let originalX = 0;
258
+
259
+ // 随 pMove 的触发动态更新的 clientX
260
+ let startX = 0;
261
+
262
+ let originalTranslate = 0;
263
+ let isDown = false;
264
+
265
+ const pStart = (x, capture) => {
266
+ if (isDown) {
267
+ return;
268
+ }
269
+ if (capture) {
270
+ capture();
271
+ }
272
+ this.pauseAutoPlay();
273
+ originalX = x;
274
+ startX = x;
275
+ originalTranslate = this.currentTranslate;
276
+ $inner.css('transition', 'none')
277
+ isDown = true;
278
+ };
279
+
280
+ const pMove = x => {
281
+ if (!isDown) {
282
+ return;
283
+ }
284
+ const curX = x;
285
+ const moveX = curX - startX;
286
+ let newTranslate = this.currentTranslate + moveX;
287
+ if (Math.abs(newTranslate - originalTranslate) > this.signWidth) {
288
+ // 限制滑动范围不超过一个 sign 的宽度
289
+ newTranslate = Math.max(Math.min(newTranslate, originalTranslate + this.signWidth), originalTranslate - this.signWidth);
290
+ } else {
291
+ startX = curX;
292
+ }
293
+ this.setTranslate(newTranslate);
294
+ };
295
+
296
+ const pEnd = async (capture) => {
297
+ if (!isDown) {
298
+ return;
299
+ }
300
+ if (capture) {
301
+ capture();
302
+ }
303
+ isDown = false;
304
+ // let newTranslate = Math.round(this.currentTranslate / this.signWidth) * this.signWidth;
305
+ // newTranslate = Math.max(Math.min(newTranslate, 0), -this.signWidth * (len+1));
306
+ let newTranslate;
307
+ if (Math.abs(this.currentTranslate - originalTranslate) > this.signWidth * this.options.dragThreshold) {
308
+ newTranslate = Math.sign(this.currentTranslate - originalTranslate) * this.signWidth + originalTranslate;
309
+ if (newTranslate > -this.signWidth) {
310
+ newTranslate -= this.signWidth * len;
311
+ } else if (newTranslate < -this.signWidth * len) {
312
+ newTranslate += this.signWidth * len;
313
+ }
314
+
315
+ // 在移动到新位置前,先变换当前位置到正确区域
316
+ await this.normalizeTranslate();
317
+ } else {
318
+ newTranslate = originalTranslate;
319
+ }
320
+
321
+ $inner.css('transition', 'transform 0.3s ease-out')
322
+ $inner.css('transform',`translateX(${this.currentTranslate}px)`);
323
+
324
+ if (newTranslate !== this.currentTranslate) {
325
+ this.setTranslate(newTranslate);
326
+ }
327
+
328
+ // 计算 newIndex ,取值范围 [0, len)
329
+ // 由于 newTranslate 包含了前后的循环占位(位置 0 和 len+1 ),需要减1后取模
330
+ const newIndex = (Math.round(-newTranslate / this.signWidth) + len - 1) % len;
331
+ if (this.options.isTitleEnabled) {
332
+ this.$pagination.children().eq(newIndex+1).addClass('active').siblings().removeClass('active');
333
+ this.$titleBox.text(this.options.list[newIndex].title);
334
+ }
335
+ if (this.options.pageChanged && newIndex != this.index) {
336
+ this.options.pageChanged(this.$inner.children().eq(newIndex+1), this.options.list[newIndex], newIndex);
337
+ }
338
+ this.index = newIndex;
339
+ this.startAutoPlay();
340
+ };
341
+
342
+ $inner.off();
343
+
344
+ if (windowEnv === 'h5') { // 浏览器环境
345
+ $inner
346
+ .attr('draggable', 'false')
347
+ .on('pointerdown', function(e) {
348
+ pStart(e.originalEvent.clientX, () => {
349
+ this.setPointerCapture(e.originalEvent.pointerId);
350
+ });
351
+ })
352
+ .on('pointermove', function(e) {
353
+ pMove(e.originalEvent.clientX);
354
+ })
355
+ .on('pointerup', async function(e) {
356
+ await pEnd(() => {
357
+ this.releasePointerCapture(e.originalEvent.pointerId);
358
+ });
359
+ });
360
+ } else { // app或模拟器
361
+ $inner
362
+ .attr('draggable', 'true')
363
+ .on('dragstart', e => {
364
+ pStart(e.clientX);
365
+ })
366
+ .on('drag', e => {
367
+ pMove(e.clientX);
368
+ })
369
+ .on('dragend', async () => {
370
+ await pEnd();
371
+ })
372
+ }
373
+
374
+ $inner.on('transitionend', () => {
375
+ $inner.css('transition', 'none');
376
+ })
377
+ .on('click', function(e){
378
+ if(!self.options.click){
379
+ return
380
+ }
381
+ if (windowEnv === 'h5' && Math.abs(e.clientX - originalX) > 10) {
382
+ // 防止浏览器中拖动时触发click
383
+ return
384
+ }
385
+
386
+ const $t = self.$inner.children().eq(self.index + 1);
387
+ self.options.click($t, self.options.list[self.index], self.index);
388
+ });
389
+ }
390
+
391
+ Banner.prototype.setTranslate = function (transX) {
392
+ this.currentTranslate = transX;
393
+ this.$inner.css('transform',`translateX(${this.currentTranslate}px)`);
394
+ }
395
+
396
+ /**
397
+ * 当currentTranslate位于补位区时,平移一个周期到另一侧
398
+ * 如果需要移动,则使用requestAnimationFrame确保动作完成
399
+ */
400
+ Banner.prototype.normalizeTranslate = async function () {
401
+ const len = this.options.list.length;
402
+ if (this.currentTranslate > -this.signWidth) {
403
+ this.setTranslate(this.currentTranslate - this.signWidth * len);
404
+ await nextAnimationFrame();
405
+ await nextAnimationFrame();
406
+ } else if (this.currentTranslate < -this.signWidth * len) {
407
+ this.setTranslate(this.currentTranslate + this.signWidth * len);
408
+ await nextAnimationFrame();
409
+ await nextAnimationFrame();
410
+ }
411
+ }
412
+
413
+ /**
414
+ * 加载所有背景图片,将data-background-url属性设置为背景图片
415
+ */
416
+ Banner.prototype.loadAllBackgrounds = function () {
417
+ this.$inner.children().each(function() {
418
+ $(this).css('background-image', `url(${$(this).attr('data-background-url')})`)
419
+ });
420
+ // console.log('banner images loaded');
421
+ }
422
+
423
+ /**
424
+ * 加载背景图片,将data-background-url属性设置为背景图片
425
+ * @param {number} pos 为加载的图片编号,取值[0, len),如果为0或len-1,会同时加载滚动占位的图片
426
+ */
427
+ Banner.prototype.loadBackground = function (pos) {
428
+ console.log('loadBackground pos === ', pos);
429
+ let $item = this.$inner.children().eq(pos + 1);
430
+ $item.css('background-image', `url(${$item.attr('data-background-url')})`);
431
+
432
+ // 加载滚动占位的图片
433
+ if (pos === 0) {
434
+ $item = this.$inner.children().eq(this.options.list.length + 1);
435
+ $item.css('background-image', `url(${$item.attr('data-background-url')})`);
436
+ }
437
+
438
+ if (pos === this.options.list.length - 1) {
439
+ $item = this.$inner.children().eq(0);
440
+ $item.css('background-image', `url(${$item.attr('data-background-url')})`);
441
+ }
442
+ }
443
+
444
+ Banner.prototype.remove = function () {
445
+ $(this.options.container).empty();
446
+ removeResizeFunc({
447
+ obj: this,
448
+ func: this.fitWidth,
449
+ });
450
+ }
@@ -0,0 +1,46 @@
1
+ .banner-inner-transform {
2
+ display: flex;
3
+ flex-direction: row;
4
+ height: 100%;
5
+ }
6
+
7
+ .banner-pagination-item {
8
+ flex-shrink: 0;
9
+ width: 0.2rem;
10
+ height: 0.2rem;
11
+ background: rgba(255, 255, 255, 0.5);
12
+ margin: 0 0.075rem;
13
+ border-radius: 50%;
14
+ }
15
+
16
+ .banner-pagination {
17
+ display: flex;
18
+ position: absolute;
19
+ bottom: 0;
20
+ left: 0;
21
+ height: 0.6rem;
22
+ width: 100%;
23
+ background: rgba(0, 0, 0, 0.7);
24
+ justify-content: flex-end;
25
+ align-items: center;
26
+ }
27
+
28
+ .banner-pagination-item.active {
29
+ background: #fff;
30
+ }
31
+
32
+ .banner-title-box {
33
+ flex-shrink: 1;
34
+ bottom: 0;
35
+ left: 0;
36
+ width: 100%;
37
+ height: 0.6rem;
38
+ display: flex;
39
+ justify-content: start;
40
+ font-size: 0.36rem;
41
+ align-items: center;
42
+ z-index: 10;
43
+ text-wrap: nowrap;
44
+ overflow: hidden;
45
+ color: white;
46
+ }
@@ -0,0 +1,23 @@
1
+ import { startEventResize, startEventScroll } from './utils/utils.js'
2
+
3
+ export let windowEnv;
4
+ export let $;
5
+
6
+ export const setEnv = (config) => {
7
+ windowEnv = config.windowEnv;
8
+ $ = config.$;
9
+ console.log('setEnv', config);
10
+
11
+ // 页面resize事件
12
+ startEventResize();
13
+
14
+ // 页面scroll事件
15
+ startEventScroll($)
16
+ }
17
+
18
+ export const getEnv = () => {
19
+ return {
20
+ windowEnv,
21
+ $,
22
+ }
23
+ }