Files
WXProgram/miniprogram/components/bottom-modal/bottom-modal.js
2025-10-16 21:32:16 +08:00

143 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Component({
options: {
// 关键修改:设置样式隔离选项
styleIsolation: 'shared' // 完全共享页面和组件样式
},
properties: {
visible: {
type: Boolean,
value: false,
observer: function (newVal) {
if (newVal) {
this.showModal();
} else {
this.hideModal();
}
}
},
modalContentClass: {
type: String,
value: ''
}
},
data: {
animationClass: 'modal-transition', // 保持过渡效果类
containerHeight: '25vh', // 设置为1/4屏幕高度
initialTranslate: '100%', // 初始在屏幕外
startY: 0, // 触摸起始位置
currentState: 'hidden', // 'hidden', 'bottom', 'half', 'full'
states: {
bottom: { height: '25vh', translate: '0' }, // 1/4屏幕高度设置translate为0使其正确显示在底部
full: { height: '100vh', translate: '0' } // 全屏高度
}
},
methods: {
showModal() {
this.setData({
visible: true,
currentState: 'bottom', // 默认显示1/4屏幕高度
containerHeight: this.data.states['bottom'].height,
initialTranslate: this.data.states['bottom'].translate
});
},
hideModal() {
this.setData({
initialTranslate: '100%', // 设置为底部位置
currentState: 'hidden'
});
setTimeout(() => {
if (this.data.currentState === 'hidden') {
this.setData({ visible: false });
}
}, 300); // 与动画时间一致
},
onHideModal() {
this._triggerCloseEvent();
},
// 遮罩层点击事件(如果有遮罩层的话)
onOverlayTap() {
this._triggerCloseEvent();
},
// 共用的关闭事件触发方法
_triggerCloseEvent() {
this.triggerEvent('close');
},
preventTouchMove() {},
onTouchStart(e) {
this.setData({ startY: e.touches[0].clientY });
this.setData({ animationClass: '' }); // 开始拖动时移除过渡效果,使拖动更跟手
},
onTouchMove(e) {
if (!this.data.visible) return;
const currentY = e.touches[0].clientY;
const deltaY = currentY - this.data.startY;
let newTranslate = '';
// 根据当前状态和拖动方向计算新的位移
if (this.data.currentState === 'full') {
newTranslate = `calc(0px + ${deltaY}px)`; // 从全屏位置开始拖
} else if (this.data.currentState === 'bottom') {
newTranslate = `calc(75vh + ${deltaY}px)`; // 从1/4屏幕高度位置开始拖
}
// 限制拖动范围,不能拖出屏幕顶部或底部之外
if (this.data.currentState === 'full' && parseInt(newTranslate) > 0) {
newTranslate = '0';
} else if (this.data.currentState === 'bottom' && deltaY < -300) {
// 向下拖动不超过屏幕
newTranslate = 'calc(75vh - 300px)';
}
this.setData({ initialTranslate: newTranslate });
},
onTouchEnd(e) {
const endY = e.changedTouches[0].clientY;
const deltaY = endY - this.data.startY;
const threshold = 50; // 判断状态切换的阈值
// 根据拖动距离和方向,以及当前状态,决定下一个状态
let nextState = this.data.currentState;
if (Math.abs(deltaY) > threshold) {
if (deltaY > 0) { // 向下拖
if (this.data.currentState === 'full') {
nextState = 'bottom';
}
} else { // 向上拖
if (this.data.currentState === 'bottom') {
nextState = 'full';
}
}
} else {
// 拖动距离小于阈值,则回到当前状态
nextState = this.data.currentState;
}
// 在切换状态前,添加过渡动画类
this.setData({
animationClass: 'modal-transition'
}, () => {
// 在回调中执行状态切换,确保动画类已经设置
this.switchToState(nextState);
});
},
switchToState(state) {
if (state === 'hidden') {
this.hideModal();
return;
}
const targetState = this.data.states[state];
if (targetState) {
this.setData({
currentState: state,
containerHeight: targetState.height,
initialTranslate: targetState.translate
});
// 通知父组件状态变化
this.triggerEvent('stateChange', { state: state });
}
}
}
});