UNPKG

wx-mini-weview

Version:

weixin UI

450 lines (386 loc) 11.4 kB
Component({ options: { styleIsolation: 'apply-shared' }, relations: { '../swiper-item/swiper-item': { type: 'child', linked(target) { this.updateChildren(); }, linkChanged() { this.updateChildren(); }, unlinked() { this.updateChildren(); } } }, properties: { // 当前激活项索引或名称 active: { type: null, value: 0, observer(newVal, oldVal) { if (newVal !== oldVal) { this.setCurrentItem(newVal); } } }, // 切换动画持续时间 duration: { type: Number, value: 300 }, // 是否循环播放 circular: { type: Boolean, value: false }, // 是否支持手势滑动 swipeable: { type: Boolean, value: true }, // 自动轮播 autoplay: { type: Boolean, value: false }, // 自动轮播间隔时间(ms) interval: { type: Number, value: 3000 }, // 是否显示指示点 indicatorDots: { type: Boolean, value: true }, // 指示点颜色 indicatorColor: { type: String, value: 'rgba(0, 0, 0, .3)' }, // 当前指示点颜色 indicatorActiveColor: { type: String, value: '#000000' }, // 高度,可以是rpx或px height: { type: String, value: '300rpx' }, // 自定义类名 customClass: { type: String, value: '' } }, data: { // 当前激活项索引 currentIndex: 0, // 子项数量 itemCount: 0, // 容器宽度 containerWidth: 0, // 开始触摸x坐标 touchStartX: 0, // 开始触摸y坐标 touchStartY: 0, // 是否正在触摸 touching: false, // 当前触摸偏移量 touchOffset: 0, // 名称到索引的映射 nameToIndexMap: {}, // 索引到名称的映射 indexToNameMap: {}, // 自动播放定时器ID autoplayTimer: null }, lifetimes: { attached() { this.updateChildren(); }, ready() { this.getContainerWidth(); this.setCurrentItem(this.data.active); if (this.data.autoplay) { this.startAutoplay(); } // 触发初始化就绪事件 this.triggerEvent('ready', { active: this.data.active, currentItem: this._getCurrentItemInfo() }); }, detached() { if (this.data.autoplayTimer) { clearInterval(this.data.autoplayTimer); } } }, methods: { // 获取容器宽度 getContainerWidth() { const query = this.createSelectorQuery(); query.select('.wv-swiper-container').boundingClientRect(rect => { if (rect) { this.setData({ containerWidth: rect.width }); this.updateItemPositions(); } }).exec(); }, // 更新子组件状态 updateChildren() { const children = this.getRelationNodes('../swiper-item/swiper-item'); if (!children || !children.length) return; const nameToIndexMap = {}; const indexToNameMap = {}; // 建立name和index的映射关系 children.forEach((child, index) => { const name = child.data.name || `swiper_item_${index}`; nameToIndexMap[name] = index; indexToNameMap[index] = name; child.updateActive(index === this.data.currentIndex, index); }); this.setData({ itemCount: children.length, nameToIndexMap, indexToNameMap }); this.updateItemPositions(); }, // 设置当前项 setCurrentItem(itemNameOrIndex, animationDuration = this.data.duration) { const { nameToIndexMap, indexToNameMap, currentIndex } = this.data; let index = -1; let name = ''; // 解析输入为索引或名称 if (typeof itemNameOrIndex === 'number') { index = itemNameOrIndex; name = indexToNameMap[index] || ''; } else if (typeof itemNameOrIndex === 'string') { name = itemNameOrIndex; index = nameToIndexMap[name] || -1; } // 边界检查 if (index < 0 || index >= this.data.itemCount) { if (this.data.itemCount > 0) { index = 0; name = indexToNameMap[0] || ''; } else { return; } } // 如果索引没有变化,不执行 if (index === currentIndex) return; const oldIndex = currentIndex; const oldName = indexToNameMap[oldIndex] || ''; this.setData({ currentIndex: index }); this.updateItemActive(index); this.updateItemPositions(animationDuration); // 触发变更事件 this.triggerEvent('change', { index, name, previous: oldIndex, previousName: oldName }); }, // 更新项目激活状态 updateItemActive(activeIndex) { const children = this.getRelationNodes('../swiper-item/swiper-item'); if (!children || !children.length) return; children.forEach((child, index) => { child.updateActive(index === activeIndex, index); }); }, // 更新项目位置 updateItemPositions(duration = this.data.duration) { const children = this.getRelationNodes('../swiper-item/swiper-item'); if (!children || !children.length) return; const { currentIndex, containerWidth, touchOffset } = this.data; children.forEach((child, index) => { let offset = (index - currentIndex) * containerWidth; offset += touchOffset; // 如果启用循环,特殊处理边界位置 if (this.data.circular) { if (currentIndex === 0 && index === children.length - 1) { offset = -containerWidth; } else if (currentIndex === children.length - 1 && index === 0) { offset = containerWidth; } } const style = `transform: translateX(${offset}px); transition-duration: ${duration}ms;`; child.updateStyle(style); }); }, // 处理触摸开始事件 onTouchStart(e) { if (!this.data.swipeable) return; if (this.data.autoplayTimer) { clearInterval(this.data.autoplayTimer); this.data.autoplayTimer = null; } this.setData({ touching: true, touchStartX: e.touches[0].clientX, touchStartY: e.touches[0].clientY, touchOffset: 0 }); }, // 处理触摸移动事件 onTouchMove(e) { if (!this.data.swipeable || !this.data.touching) return; const touchX = e.touches[0].clientX; const touchY = e.touches[0].clientY; // 计算水平和垂直方向的位移 const deltaX = touchX - this.data.touchStartX; const deltaY = touchY - this.data.touchStartY; // 如果垂直滑动大于水平滑动,则不处理 if (Math.abs(deltaY) > Math.abs(deltaX)) { return; } // 阻止页面滚动 e.preventDefault && e.preventDefault(); let offset = deltaX; // 如果循环禁用,对第一项和最后一项进行边界限制 if (!this.data.circular) { if ( (this.data.currentIndex === 0 && offset > 0) || (this.data.currentIndex === this.data.itemCount - 1 && offset < 0) ) { // 在边界情况下,减小滑动效果 offset = offset / 3; } } this.setData({ touchOffset: offset }); // 更新位置,但不使用动画 this.updateItemPositions(0); }, // 处理触摸结束事件 onTouchEnd(e) { if (!this.data.swipeable || !this.data.touching) return; const { touchOffset, containerWidth, currentIndex, itemCount } = this.data; // 根据滑动距离判断是否切换到下一项 const threshold = containerWidth / 4; let newIndex = currentIndex; if (touchOffset < -threshold) { // 向左滑动超过阈值,切换到下一项 newIndex = currentIndex + 1; } else if (touchOffset > threshold) { // 向右滑动超过阈值,切换到上一项 newIndex = currentIndex - 1; } // 边界检查 if (!this.data.circular) { newIndex = Math.max(0, Math.min(itemCount - 1, newIndex)); } else { if (newIndex < 0) { newIndex = itemCount - 1; } else if (newIndex >= itemCount) { newIndex = 0; } } this.setData({ touching: false, touchOffset: 0 }); if (newIndex !== currentIndex) { // 如果索引变化了,切换到新项目 this.setCurrentItem(newIndex); } else { // 如果索引没有变化,重置当前项目位置 this.updateItemPositions(this.data.duration); } // 如果启用了自动播放,重新开始 if (this.data.autoplay) { this.startAutoplay(); } }, // 开始自动播放 startAutoplay() { if (this.data.autoplayTimer) { clearInterval(this.data.autoplayTimer); } this.data.autoplayTimer = setInterval(() => { let nextIndex = this.data.currentIndex + 1; if (nextIndex >= this.data.itemCount) { if (this.data.circular) { nextIndex = 0; } else { clearInterval(this.data.autoplayTimer); this.data.autoplayTimer = null; return; } } this.setCurrentItem(nextIndex); }, this.data.interval); }, // 公开方法: 切换到下一项 next() { let nextIndex = this.data.currentIndex + 1; if (nextIndex >= this.data.itemCount) { if (this.data.circular) { nextIndex = 0; } else { return false; } } this.setCurrentItem(nextIndex); return true; }, // 公开方法: 切换到上一项 prev() { let prevIndex = this.data.currentIndex - 1; if (prevIndex < 0) { if (this.data.circular) { prevIndex = this.data.itemCount - 1; } else { return false; } } this.setCurrentItem(prevIndex); return true; }, // 公开方法: 切换到指定项 switchTo(nameOrIndex) { this.setCurrentItem(nameOrIndex); return true; }, // 公开方法: 获取当前项信息 getCurrentItem() { return this._getCurrentItemInfo(); }, // 公开方法: 获取所有项信息 getAllItems() { const children = this.getRelationNodes('../swiper-item/swiper-item'); if (!children || !children.length) return []; return children.map((child, index) => { return { index, name: child.data.name, active: child.data.active }; }); }, // 内部方法: 获取当前项信息 _getCurrentItemInfo() { const { currentIndex, indexToNameMap, itemCount } = this.data; return { index: currentIndex, name: indexToNameMap[currentIndex] || '', total: itemCount }; } } });