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": {}
+}