wx-mini-weview
Version:
weixin UI
450 lines (386 loc) • 11.4 kB
JavaScript
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
};
}
}
});