/** * swiper API 文档 * props default 说明 * autoplay true 是否自动播放 ✅ * interval 2000 轮播间隔, 单位 ms ✅ * loop true 是否循环播放 ✅ * count 1 轮播的子节点数量, 必传!!!! ✅ * active - 手动控制索引值 ✅ * defaultIndex 0 初始位置索引值 ✅ * showIndicators true 是否显示指示器 ✅ * indicatorsPosition center 指示器位置 支持 center left right ✅ * vertical false 是否是纵向滚动 ❌ 不支持 * keepIndex false 设置轮播器中的数据发生变化后是否保持变化前的页面序号。 ⚠️ 不支持 * touchable true 是否可以通过手势滑动 ❌ 不支持 * * swiper 事件文档 * 事件名 回调参数 说明 * change index 当前页的索引 每一页轮播结束后触发 ✅ * touchStart index 当前页的索引 开始滑动 swiper 的时候触发 ✅ * * swiper refs 方法 * 方法名 参数 说明 * prev - 切换到上一轮播 ✅ * next - 切换到下一轮播 ✅ * swipeTo index 切换到指定的轮播 ✅ * * swiper slots 文档 * 名称 说明 * default 轮播内容 ⚠️ 内容不会变化 * indicator 自定义指示器 ⚠️ 具名插槽不能设置默认内容 */ /** TODO 处理插槽相关问题, 处理 this 对象获取, 拿到实例, 调用组件方法 */ import { Component, ComponentProps, setInterval, clearInterval, CustomEvent, Element, console, Event } from 'waft'; import { JSONArray, JSONInteger, JSONObject, JSONString } from "waft-json"; import { lodash, refs } from "waft-ui-common"; const indicatorsPosition: Map = new Map(); indicatorsPosition.set('center', 'center'); indicatorsPosition.set('left', 'flex-start'); indicatorsPosition.set('right', 'flex-end'); export class Swiper extends Component { private index: i64 = 0; // 当前展示的下标节点 private count: i64 = 1; // 子节点数量, 不能直接获取到 children 数量时通过外部传入 private timer: i32 = 0; // 定时器, 用于控制暂停轮播等 private itemRef: string = ''; // 组件的 ID, 外部传入的属性 private touch: boolean = false; // 是否手指点击 private indexLocked: boolean = false; // index lock, 用于在设置 index 到轮播动画完成不会被新的切换事件干扰 constructor(props: ComponentProps) { super(props); } willMount(props: JSONObject): void { super.willMount(props); // 设置组件 ID this.itemRef = lodash.get(props, 'ref', ''); if (this.itemRef != '') { refs.set(this.itemRef, this); } this.index = lodash.get(props, 'defaultIndex', this.index); this.index = lodash.get(props, 'active', this.index); // index 控制的优先级更高, 所以后获取 const state = new JSONObject(); state.set('index', this.index); // 处理指示器位置, 转换 center -> center left -> flex-start right -> flex-end const indicatorsPositionProps = lodash.get(props, 'indicatorsPosition', 'center'); if (indicatorsPosition.has(indicatorsPositionProps)) { state.set('usedIndicatorsPosition', indicatorsPosition.get(indicatorsPositionProps)); } this.setState(state); this.updateCount(); // 开始轮播 this.beginLoop(); console.log('======willMount==========') } didUnmount(): void { clearInterval(this.timer); // 组件被销毁要清除定时器 // 清除组件实例的 ref 存储 if (this.itemRef != '') { refs.remove(this.itemRef, this); } } onChange(e: Event): void{ console.log("======== swiper onChange:" + e.toString()); const index = lodash.get((e as CustomEvent).detail, 'index', 0 as i64); this.changeIndex(index - this.index, true); this.indexLocked = false // 先暂停定时器, 然后重新设置定时器, 保证从活动操作结束后停留时长够 interval clearInterval(this.timer); this.beginLoop(); } onTouchStart(e: Event): void{ console.log('====onTouchStart===') clearInterval(this.timer); // 手指滑动时要清除定时器 this.touch = true; console.log(this.timer.toString()+'===this.timer===') const data = new JSONObject(); data.set('index', this.index); this.props.dispatch('onTouchStart', data); } onTouchEnd(e: Event): void{ // console.log('==onTouchEnd==') this.touch = false; this.beginLoop(); } /** * 开始轮播 todo 优化, 隐藏不显示的时候停止轮播, 需要 onHide 事件 * todo 需要有 props 改变后通知组件的生命周期, 来动态响应 loop */ private beginLoop(): void { // console.log('=========beginLoop======='+ this.touch.toString()) if(this.touch) return; const interval: i64 = lodash.toInteger(this.props.get('interval')); this.timer = setInterval((event, target) => { if (target) { const _this = target as Swiper; const autoplay: boolean = lodash.toBoolean(_this.props.get('autoplay')); if (autoplay) { _this.changeIndex(); // 自增轮播 } // 重新回调自身, 进行 loop 使用 interval 不使用 setTimeout // _this.beginLoop(); } }, interval, this); } /** * 更换轮播组件的 index TODO marks this.props.get() 能够获取到 state 的值 */ private changeIndex(num: i64 = 1, isOnChangeCallback: boolean = false): void { if (this.indexLocked) { return } if (num != 0 && !isOnChangeCallback) { this.indexLocked = true } this.updateCount(); // 更新 count 传入的值, todo 能够监听到 props 改变时去掉这一步 // 先获取 props 的 index, 如果是受控组件, 不执行内部的逻辑 const propsIndex = this.props.get('active'); if (propsIndex instanceof JSONString) { this.index = parseInt(propsIndex.toString()) as i64; } else if (propsIndex instanceof JSONInteger) { this.index = (propsIndex as JSONInteger).intValue(); } else { this.index += num; const loop: boolean = lodash.toBoolean(this.props.get('loop')); // 是否开启循环 if (this.index >= this.count) { this.index = loop ? 0 : this.count - 1; } if (this.index < 0) { this.index = loop ? this.count - 1 : 0; } } /** * 这里做这个处理是因为 用户手动滑动到其他地方, 但是传给 slider 的 index 没有发生变化导致内容与真实下标不一致, * 所以手动设置一个不存在的下标在设置回去, 保证传给 slider 的 index 发生了变化, 让显示的下标与实际下标保持一致 */ if (lodash.get(this.state, 'index', this.index) == this.index) { const state = new JSONObject(); state.set('index', -1); this.setState(state); } // 设置下标数据 const state = new JSONObject(); state.set('index', this.index); this.setState(state); // 触发 props 的 change 事件 if (this.props.attributes.has('onChange')) { this.props.dispatch('onChange', state); } } /** * 获取最新的 count 并保存起来, todo 如有 props 改变的通知这里取消 */ private updateCount(): void { this.count = lodash.toInteger(this.props.getAttribute('count')); // 同时构建指示器的数组 const arr = new JSONArray(); for (let i = 0; i < this.count; i++) { arr.push(i); } const state = new JSONObject(); state.set('countArray', arr); this.setState(state); } /** * 暴露给外界, 走到上一个内容 */ public prev(): void { this.changeIndex(-1); } /** * 暴露给外界, 走到下一个内容 */ public next(): void { this.changeIndex(); } /** * 暴露给外界, 跳到指定内容 */ public swipeTo(index: i64): void { this.changeIndex(index - this.index); } }