diff --git a/uni-components/wsd-virtual-swiper/changelog.md b/uni-components/wsd-virtual-swiper/changelog.md new file mode 100644 index 0000000..4e8e2c4 --- /dev/null +++ b/uni-components/wsd-virtual-swiper/changelog.md @@ -0,0 +1,3 @@ +## 1.0.1(2024-11-15) +feat: hook方法提供current转数据索引 +feat: hook方法支持忽略手动滑动swiper引起的swiper-change事件 diff --git a/uni-components/wsd-virtual-swiper/components/wsd-virtual-swiper/wsd-virtual-swiper.vue b/uni-components/wsd-virtual-swiper/components/wsd-virtual-swiper/wsd-virtual-swiper.vue index e0f593a..76f7ca7 100644 --- a/uni-components/wsd-virtual-swiper/components/wsd-virtual-swiper/wsd-virtual-swiper.vue +++ b/uni-components/wsd-virtual-swiper/components/wsd-virtual-swiper/wsd-virtual-swiper.vue @@ -19,9 +19,10 @@ - + @@ -38,6 +39,10 @@ const props = defineProps({ type: Number, default: 0, }, + ignoreChangeByManual: { + type: Boolean, + default: false + }, data: { type: Array, default: () => [], @@ -88,6 +93,7 @@ const { finalyCircular, onSwiperChange, scrollIntoSwiper, + index2Current, } = useVirtualSwiper(props, emits); defineExpose({ @@ -100,6 +106,7 @@ defineExpose({ finalyCircular, onSwiperChange, scrollIntoSwiper, + index2Current, }); diff --git a/uni-components/wsd-virtual-swiper/hooks/useVirtualSwiper.ts b/uni-components/wsd-virtual-swiper/hooks/useVirtualSwiper.ts index 0740fa0..71a0079 100644 --- a/uni-components/wsd-virtual-swiper/hooks/useVirtualSwiper.ts +++ b/uni-components/wsd-virtual-swiper/hooks/useVirtualSwiper.ts @@ -1,4 +1,14 @@ // swiper 数组渲染优化 +/** + * @description 大量swiper数据渲染导致dom节点过多问题优化-动态控制渲染的swiper渲染 + * 目前默认按 3 个swiper-item进行轮播渲染 + * 根据当前data-current对应的数据项,动态维护数据项前一项和后一项内容 + * 然后数据子集根据swiper当前current值进行偏移对齐,保证swiper展示item与数据项对齐。 + * + * @warning acceleration 属性必须关闭否则容易存在渲染结果偏差 + * 由于这种对数据子集进行偏移的操作,对于数据项总数有要求,否则在首尾位置时可能偏移后与实际数据顺序又不一致, + * 因此对于首尾位置需要进行特殊处理,子集数量可以大于3,但不超过5. + */ import { unref, ref, watch, computed, onMounted } from 'vue'; import type { Ref } from 'vue'; @@ -15,6 +25,8 @@ export interface VirtualSwiperProps { circular?: boolean; keyField?: string; duration?: number; + ignoreChangeByManual?: boolean; + triggerWhenMounted?: boolean; } export interface VirtualSwiperReturn { @@ -27,6 +39,7 @@ export interface VirtualSwiperReturn { finalyCircular: Ref; onSwiperChange: (e: { current: number }) => void; scrollIntoSwiper: (index: number) => void; + index2Current: (index: number) => number; } export interface VirtualSwiperEmits { @@ -52,6 +65,7 @@ export default function useVirtualSwiper( let _durationTimeout: ReturnType; let _swiperTimeout: ReturnType; + const changeManualFlag = ref(false); const defaultCurrent = unref(props).defaultCurrent ?? 0; const defaultDuration = unref(props).duration ?? 250; const defaultCircular = unref(props).circular ?? false; @@ -65,8 +79,12 @@ export default function useVirtualSwiper( const normalizeData = computed(() => { return unref(props).data.map((item: string | number | object) => { - if (typeof item === 'object') return item; + if (typeof item === 'object') { + item['_id'] = item[unref(finalyKeyField)]; + return item; + } return { + _id: item, [unref(finalyKeyField)]: item, }; }); @@ -80,21 +98,33 @@ export default function useVirtualSwiper( ); const swiperMaxCounts = computed(() => 3 + unref(swiperOffset)); - watch(dataCurrent, (val) => { - emits && - emits( - 'current-change', - currentKey.value, - val, - getPrevIndex(val, unref(swiperCounts)), - getNextIndex(val, unref(swiperCounts)) - ); - }); + watch( + dataCurrent, + (val) => { + emits && + emits( + 'current-change', + currentKey.value, + val, + getPrevIndex(val, unref(swiperCounts)), + getNextIndex(val, unref(swiperCounts)) + ); + }, + { immediate: false } + ); // swiper 绑定 change事件 function onSwiperChange(e: any) { - // console.log('>>>swiper change', e) - if (_swiperTimeout) { + console.log( + 'swiper change', + swiperCurrent.value, + e.detail.current, + changeManualFlag.value + ); + if ( + _swiperTimeout && + (!unref(props).ignoreChangeByManual || !changeManualFlag.value) + ) { clearTimeout(_swiperTimeout); } @@ -105,15 +135,19 @@ export default function useVirtualSwiper( swiperCurrent.value = e.detail.current; - _swiperTimeout = setTimeout(() => { - finalyDuration.value = 0; + if (changeManualFlag.value) { + changeManualFlag.value = false; + } else { + _swiperTimeout = setTimeout(() => { + finalyDuration.value = 0; - updateCurrentSwiper(); + updateCurrentSwiper(); - _durationTimeout = setTimeout(() => { - finalyDuration.value = defaultDuration; - }, defaultDuration); - }, defaultDuration + 1); + _durationTimeout = setTimeout(() => { + finalyDuration.value = defaultDuration; + }, defaultDuration); + }, defaultDuration + 1); + } } function getCurrentIndex(index: number, len: number) { @@ -233,7 +267,7 @@ export default function useVirtualSwiper( } // 更新渲染的swiper-item数据集 - function updateDataCurrent(index: number) { + function updateDataCurrent(index: number, trigger = true) { dataCurrent.value = getCurrentIndex(index, unref(swiperCounts)); if (defaultCircular) { @@ -242,7 +276,8 @@ export default function useVirtualSwiper( updateCurrentSwiperByDefault(); } - emits && + trigger && + emits && emits( 'swiper-change', unref(currentKey), @@ -251,21 +286,40 @@ export default function useVirtualSwiper( ); } - // 滚动到指定索引的数据项 - function scrollIntoSwiper(index: number) { + function index2Current(index: number) { if (unref(finalyCircular)) { - swiperCurrent.value = index % 3; + return index % 3; } else { - swiperCurrent.value = - index && index > unref(swiperEndOffset) - ? ((index - unref(swiperOffset)) % 3) + unref(swiperOffset) - : index % 3; + return index && index > unref(swiperEndOffset) + ? ((index - unref(swiperOffset)) % 3) + unref(swiperOffset) + : index % 3; + } + } + + /** + * @description滚动到指定索引的数据项 + * @param index 滚动到指定数据索引item + * @param trigger 是否触发swiper-change事件 + * @param init 初始化渲染标识(mounted阶段),避免初始化swiper不会触发swiper-change事件导致 changeManualFlag 标识未重置。 + */ + function scrollIntoSwiper(index: number, trigger = false, init = false) { + const oldCurrent = swiperCurrent.value; + const newCurrent = index2Current(index); + if (oldCurrent !== newCurrent) { + swiperCurrent.value = newCurrent; // 触发 swiper change 事件 + if (!init && unref(props).ignoreChangeByManual) { + changeManualFlag.value = true; + } } - updateDataCurrent(index); + updateDataCurrent(index, trigger); } onMounted(() => { - scrollIntoSwiper(getCurrentIndex(defaultCurrent, unref(swiperCounts))); + scrollIntoSwiper( + getCurrentIndex(defaultCurrent, unref(swiperCounts)), + !!unref(props).triggerWhenMounted, + true + ); }); return { @@ -278,5 +332,6 @@ export default function useVirtualSwiper( currentSwipers, onSwiperChange, scrollIntoSwiper, + index2Current, }; } diff --git a/uni-components/wsd-virtual-swiper/hooks/useVirtualSwiperCircular.ts b/uni-components/wsd-virtual-swiper/hooks/useVirtualSwiperCircular.ts index 7f818a2..4554693 100644 --- a/uni-components/wsd-virtual-swiper/hooks/useVirtualSwiperCircular.ts +++ b/uni-components/wsd-virtual-swiper/hooks/useVirtualSwiperCircular.ts @@ -5,199 +5,235 @@ * 目前默认按 3 个swiper-item进行轮播渲染 * 根据当前data-current对应的数据项,动态维护数据项前一项和后一项内容 * 然后对swiper-current进行重置保证保证swiper可以一直滑动,由于手动调整swiper-current会产生渲染抖动 - * + * * @warning acceleration 属性必须关闭否则容易存在渲染结果偏差 - * + * * 本hook实现思想相对简单,能够简单掌握原理。 */ -import { unref, ref, watch, computed, onMounted } from "vue"; -import type { Ref } from "vue"; +import { unref, ref, watch, computed, onMounted } from 'vue'; +import type { Ref } from 'vue'; -export type SwiperItem = Record +export type SwiperItem = Record; export interface VirtualSwiperProps { - defaultCurrent?: number - data: Array - circular?: boolean - keyField?: string - duration?: number + defaultCurrent?: number; + data: Array; + circular?: boolean; + keyField?: string; + duration?: number; + defaultEmit?: boolean; } export interface VirtualSwiperReturn { - swiperCurrent: Ref - dataCurrent: Ref - currentKey: Ref - currentSwipers: Ref - finalyKeyField: Ref - finalyDuration: Ref - finalyCircular: Ref - onSwiperChange: (e: {current: number}) => void - scrollIntoSwiper: (index: number) => void + swiperCurrent: Ref; + dataCurrent: Ref; + currentKey: Ref; + currentSwipers: Ref; + finalyKeyField: Ref; + finalyDuration: Ref; + finalyCircular: Ref; + onSwiperChange: (e: { current: number }) => void; + scrollIntoSwiper: (index: number) => void; } export interface VirtualSwiperEmits { - (event: 'swiper-change', key: string | number, dataCurrent: number, swiperCurrent: number): void - (event: 'current-change', key: string | number, dataCurrent: number, prevCurrent: number, nextCurrent: number): void + ( + event: 'swiper-change', + key: string | number, + dataCurrent: number, + swiperCurrent: number + ): void; + ( + event: 'current-change', + key: string | number, + dataCurrent: number, + prevCurrent: number, + nextCurrent: number + ): void; } -export default function useVirtualSwiperCircular(props: Ref, emits?: VirtualSwiperEmits): VirtualSwiperReturn { - let _durationTimeout: ReturnType; - let _swiperTimeout: ReturnType; - const defaultCurrent = unref(props).defaultCurrent ?? 0; - const defaultDuration = unref(props).duration ?? 300; - const defaultCircular = unref(props).circular ?? false; - const dataCurrent = ref(defaultCurrent); - const swiperCurrentLast = ref(0); - const swiperCurrent = ref(0); - const currentKey = ref(); - const currentSwipers: Ref = ref([]); - const finalyKeyField = ref(unref(props).keyField ?? 'id'); - const finalyDuration = ref(defaultDuration); - const finalyCircular = ref(defaultCircular); - - const normalizeData = computed(() => { - return unref(props).data.map((item: string | number | object) => { - if (typeof item === 'object') return item; - return ({ - [unref(finalyKeyField)]: item - }) - }) +export default function useVirtualSwiperCircular( + props: Ref, + emits?: VirtualSwiperEmits +): VirtualSwiperReturn { + let _durationTimeout: ReturnType; + let _swiperTimeout: ReturnType; + const defaultCurrent = unref(props).defaultCurrent ?? 0; + const defaultDuration = unref(props).duration ?? 300; + const defaultCircular = unref(props).circular ?? false; + const dataCurrent = ref(defaultCurrent); + const swiperCurrentLast = ref(0); + const swiperCurrent = ref(0); + const currentKey = ref(); + const currentSwipers: Ref = ref([]); + const finalyKeyField = ref(unref(props).keyField ?? 'id'); + const finalyDuration = ref(defaultDuration); + const finalyCircular = ref(defaultCircular); + + const normalizeData = computed(() => { + return unref(props).data.map((item: string | number | object) => { + if (typeof item === 'object') { + item['_id'] = item[unref(finalyKeyField)]; + return item; + } + return { + _id: item, + [unref(finalyKeyField)]: item, + }; }); + }); - const swiperCounts = computed(() => unref(normalizeData).length); - - watch(dataCurrent, (val) => { - emits && emits('current-change', currentKey.value, val, getPrevIndex(val, unref(swiperCounts)), getNextIndex(val, unref(swiperCounts))); - }) - - function onSwiperChange(e: any) { - // console.log('>>>swiper change', e) - if (_swiperTimeout) { - clearTimeout(_swiperTimeout); - } - - if (_durationTimeout) { - finalyDuration.value = defaultDuration; - clearTimeout(_durationTimeout); - } - - swiperCurrentLast.value = swiperCurrent.value; - swiperCurrent.value = e.detail.current; - - _swiperTimeout = setTimeout(() => { - finalyDuration.value = 0; - - updateCurrentSwiper(); - - _durationTimeout = setTimeout(() => { - finalyDuration.value = defaultDuration; - }, defaultDuration); - }, defaultDuration + 1); - } + const swiperCounts = computed(() => unref(normalizeData).length); - function getCurrentIndex(index: number, len: number) { - return !len ? 0 : (len + index) % len; - } - - function getNextIndex(index: number, len: number, step = 1) { - return !len ? 0 : (index + step) % len; - } - - function getPrevIndex(index: number, len: number, step = 1) { - return !len ? 0 : (len + (index - step)) % len; - } - - function updateCurrentSwiper() { - const direction = swiperCurrent.value - swiperCurrentLast.value; - let current = unref(dataCurrent); - if (direction === 1 || direction === -2) { - // 向后 - current = getNextIndex(unref(dataCurrent), unref(swiperCounts)); - } else if (direction === -1 || direction === 2) { - // 向前 - current = getPrevIndex(unref(dataCurrent), unref(swiperCounts)); - } - - updateDataCurrent(current); + watch(dataCurrent, (val) => { + emits && + emits( + 'current-change', + currentKey.value, + val, + getPrevIndex(val, unref(swiperCounts)), + getNextIndex(val, unref(swiperCounts)) + ); + }); + + function onSwiperChange(e: any) { + // console.log('>>>swiper change', e) + if (_swiperTimeout) { + clearTimeout(_swiperTimeout); } - - function updateCurrentSwiperByCircle() { - if (unref(swiperCounts) < 3) { - swiperCurrent.value = unref(dataCurrent); - currentSwipers.value = unref(normalizeData); - } else { - const cIndex = unref(dataCurrent); - const sIndex = getPrevIndex(cIndex, unref(swiperCounts)); - const eIndex = getNextIndex(cIndex, unref(swiperCounts)); - const newCurrentSwipers = [ - unref(normalizeData)[sIndex], - unref(normalizeData)[cIndex], - unref(normalizeData)[eIndex] - ] - swiperCurrent.value = 1; // 每次都重置到 1 - currentSwipers.value = newCurrentSwipers; - } + + if (_durationTimeout) { + finalyDuration.value = defaultDuration; + clearTimeout(_durationTimeout); } - - function updateCurrentSwiperByDefault() { - if (unref(swiperCounts) < 3) { - swiperCurrent.value = unref(dataCurrent); - currentSwipers.value = unref(normalizeData); - } else { - const cIndex = unref(dataCurrent); - - if (cIndex > 0 && cIndex < (unref(swiperCounts) - 1)) { - const sIndex = getPrevIndex(cIndex, unref(swiperCounts)); - const eIndex = getNextIndex(cIndex, unref(swiperCounts)); - const newCurrentSwipers = [ - unref(normalizeData)[sIndex], - unref(normalizeData)[cIndex], - unref(normalizeData)[eIndex] - ] - swiperCurrent.value = 1; // 每次都重置到 1 - currentSwipers.value = newCurrentSwipers; - } else if (cIndex === 0) { - swiperCurrent.value = unref(dataCurrent); - currentSwipers.value = unref(normalizeData).slice(0, 3); - } else if (cIndex === (unref(swiperCounts) - 1)) { - swiperCurrent.value = unref(dataCurrent); - currentSwipers.value = unref(normalizeData).slice(-3); - } - } - - // console.log('>>>swiper', dataCurrent.value, swiperCurrent.value, currentSwipers.value) + + swiperCurrentLast.value = swiperCurrent.value; + swiperCurrent.value = e.detail.current; + + _swiperTimeout = setTimeout(() => { + finalyDuration.value = 0; + + updateCurrentSwiper(); + + _durationTimeout = setTimeout(() => { + finalyDuration.value = defaultDuration; + }, defaultDuration); + }, defaultDuration + 1); + } + + function getCurrentIndex(index: number, len: number) { + return !len ? 0 : (len + index) % len; + } + + function getNextIndex(index: number, len: number, step = 1) { + return !len ? 0 : (index + step) % len; + } + + function getPrevIndex(index: number, len: number, step = 1) { + return !len ? 0 : (len + (index - step)) % len; + } + + function updateCurrentSwiper() { + const direction = swiperCurrent.value - swiperCurrentLast.value; + let current = unref(dataCurrent); + if (direction === 1 || direction === -2) { + // 向后 + current = getNextIndex(unref(dataCurrent), unref(swiperCounts)); + } else if (direction === -1 || direction === 2) { + // 向前 + current = getPrevIndex(unref(dataCurrent), unref(swiperCounts)); } - - function updateDataCurrent(index: number) { - dataCurrent.value = getCurrentIndex(index, unref(swiperCounts)); - currentKey.value = unref(normalizeData)[unref(dataCurrent)][unref(finalyKeyField)]; - if (unref(finalyCircular)) { - updateCurrentSwiperByCircle(); - } else { - updateCurrentSwiperByDefault(); - } - - emits && emits('swiper-change', unref(currentKey), unref(dataCurrent), unref(swiperCurrent)); + + updateDataCurrent(current); + } + + function updateCurrentSwiperByCircle() { + if (unref(swiperCounts) < 3) { + swiperCurrent.value = unref(dataCurrent); + currentSwipers.value = unref(normalizeData); + } else { + const cIndex = unref(dataCurrent); + const sIndex = getPrevIndex(cIndex, unref(swiperCounts)); + const eIndex = getNextIndex(cIndex, unref(swiperCounts)); + const newCurrentSwipers = [ + unref(normalizeData)[sIndex], + unref(normalizeData)[cIndex], + unref(normalizeData)[eIndex], + ]; + swiperCurrent.value = 1; // 每次都重置到 1 + currentSwipers.value = newCurrentSwipers; } - - function scrollIntoSwiper(index: number) { - updateDataCurrent(index); + } + + function updateCurrentSwiperByDefault() { + if (unref(swiperCounts) < 3) { + swiperCurrent.value = unref(dataCurrent); + currentSwipers.value = unref(normalizeData); + } else { + const cIndex = unref(dataCurrent); + + if (cIndex > 0 && cIndex < unref(swiperCounts) - 1) { + const sIndex = getPrevIndex(cIndex, unref(swiperCounts)); + const eIndex = getNextIndex(cIndex, unref(swiperCounts)); + const newCurrentSwipers = [ + unref(normalizeData)[sIndex], + unref(normalizeData)[cIndex], + unref(normalizeData)[eIndex], + ]; + swiperCurrent.value = 1; // 每次都重置到 1 + currentSwipers.value = newCurrentSwipers; + } else if (cIndex === 0) { + swiperCurrent.value = unref(dataCurrent); + currentSwipers.value = unref(normalizeData).slice(0, 3); + } else if (cIndex === unref(swiperCounts) - 1) { + swiperCurrent.value = unref(dataCurrent); + currentSwipers.value = unref(normalizeData).slice(-3); + } } - - onMounted(() => { - scrollIntoSwiper(defaultCurrent); - }) - - return { - finalyKeyField, - finalyDuration, - finalyCircular, - currentKey, - dataCurrent, - swiperCurrent, - currentSwipers, - onSwiperChange, - scrollIntoSwiper + + // console.log('>>>swiper', dataCurrent.value, swiperCurrent.value, currentSwipers.value) + } + + function updateDataCurrent(index: number, trigger = true) { + dataCurrent.value = getCurrentIndex(index, unref(swiperCounts)); + currentKey.value = + unref(normalizeData)[unref(dataCurrent)][unref(finalyKeyField)]; + if (unref(finalyCircular)) { + updateCurrentSwiperByCircle(); + } else { + updateCurrentSwiperByDefault(); } + + trigger && + emits && + emits( + 'swiper-change', + unref(currentKey), + unref(dataCurrent), + unref(swiperCurrent) + ); + } + + function scrollIntoSwiper(index: number, trigger = false) { + updateDataCurrent(index, trigger); + } + onMounted(() => { + scrollIntoSwiper( + getCurrentIndex(defaultCurrent, unref(swiperCounts)), + unref(props).defaultEmit + ); + }); + + return { + finalyKeyField, + finalyDuration, + finalyCircular, + currentKey, + dataCurrent, + swiperCurrent, + currentSwipers, + onSwiperChange, + scrollIntoSwiper, + }; } diff --git a/uni-components/wsd-virtual-swiper/package.json b/uni-components/wsd-virtual-swiper/package.json new file mode 100644 index 0000000..3041738 --- /dev/null +++ b/uni-components/wsd-virtual-swiper/package.json @@ -0,0 +1,17 @@ +{ + "id": "wsd-virtual-swiper", + "displayName": "虚拟Swiper", + "version": "1.0.1", + "description": "提供组件和hook方法,可用于解决渲染大量swiper-item引起的性能问题,swiper滑动切换丝滑,避免swiepr手动调整current引起的重回问题。", + "keywords": [ + "Swiper", + "Swiper优化", + "虚拟Swiper", + "动态Swiper" + ], + "repository": "", + "engines": { + "HBuilderX": "^3.2.6" + }, + "dependencies": {} +}