refactor: optimize the code
This commit is contained in:
parent
15b89986b2
commit
6107b19d03
@ -3,7 +3,7 @@
|
||||
<div class="img-slide-wrapper">
|
||||
<div
|
||||
class="img-slide-list"
|
||||
ref="wrapperEl"
|
||||
ref="slideListEl"
|
||||
@touchstart.passive="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
@ -233,7 +233,7 @@ const props = defineProps({
|
||||
}
|
||||
}
|
||||
})
|
||||
const wrapperEl = ref(null)
|
||||
const slideListEl = ref(null)
|
||||
|
||||
//用于解决,touch事件触发startPlay,然后click事件又触发stopLoop的问题
|
||||
let lockDatetime = 0
|
||||
@ -301,7 +301,7 @@ function startLoop() {
|
||||
|
||||
onMounted(async () => {
|
||||
await nextTick()
|
||||
slideInit(wrapperEl.value, state, SlideType.HORIZONTAL)
|
||||
slideInit(slideListEl.value, state, SlideType.HORIZONTAL)
|
||||
startPlay()
|
||||
// setTimeout(() => {
|
||||
// state.operationStatus = SlideAlbumOperationStatus.Zooming
|
||||
@ -348,11 +348,11 @@ onBeforeUpdate(() => {
|
||||
watch(
|
||||
() => state.localIndex,
|
||||
() => {
|
||||
GM.$setCss(wrapperEl.value, 'transition-duration', `300ms`)
|
||||
GM.$setCss(slideListEl.value, 'transition-duration', `300ms`)
|
||||
GM.$setCss(
|
||||
wrapperEl.value,
|
||||
slideListEl.value,
|
||||
'transform',
|
||||
`translate3d(${getSlideOffset(state, wrapperEl.value)}px, 0, 0)`
|
||||
`translate3d(${getSlideOffset(state, slideListEl.value)}px, 0, 0)`
|
||||
)
|
||||
}
|
||||
)
|
||||
@ -406,7 +406,7 @@ function touchStart(e) {
|
||||
// Utils.$showNoticeDialog('start'+e.touches.length)
|
||||
console.log('start', e.touches.length)
|
||||
if (e.touches.length === 1) {
|
||||
slideTouchStart(e, wrapperEl.value, state)
|
||||
slideTouchStart(e, slideListEl.value, state)
|
||||
} else {
|
||||
if (state.operationStatus === SlideAlbumOperationStatus.Zooming) {
|
||||
// state.start.center = Utils.getCenter(state.start.point1, state.start.point2)
|
||||
@ -454,7 +454,7 @@ function touchMove(e) {
|
||||
state.isAutoPlay = false
|
||||
slideTouchMove(
|
||||
e,
|
||||
wrapperEl.value,
|
||||
slideListEl.value,
|
||||
state,
|
||||
canNext,
|
||||
() => {
|
||||
@ -580,7 +580,7 @@ function touchEnd(e) {
|
||||
startLoop()
|
||||
}
|
||||
)
|
||||
slideReset(e, wrapperEl.value, state)
|
||||
slideReset(e, slideListEl.value, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -40,7 +40,7 @@ const emit = defineEmits(['update:index'])
|
||||
|
||||
let ob = null
|
||||
//slide-list的ref引用
|
||||
const wrapperEl = ref(null)
|
||||
const slideListEl = ref(null)
|
||||
|
||||
const state = reactive({
|
||||
judgeValue: 20, //一个用于判断滑动朝向的固定值
|
||||
@ -67,19 +67,19 @@ watch(
|
||||
if (state.localIndex !== newVal) {
|
||||
state.localIndex = newVal
|
||||
if (props.changeActiveIndexUseAnim) {
|
||||
_css(wrapperEl.value, 'transition-duration', `300ms`)
|
||||
_css(slideListEl.value, 'transition-duration', `300ms`)
|
||||
}
|
||||
_css(
|
||||
wrapperEl.value,
|
||||
slideListEl.value,
|
||||
'transform',
|
||||
`translate3d(${getSlideOffset(state, wrapperEl.value)}px, 0, 0)`
|
||||
`translate3d(${getSlideOffset(state, slideListEl.value)}px, 0, 0)`
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
slideInit(wrapperEl.value, state)
|
||||
slideInit(slideListEl.value, state)
|
||||
|
||||
if (props.autoplay) {
|
||||
setInterval(() => {
|
||||
@ -94,9 +94,9 @@ onMounted(() => {
|
||||
//观察子元素数量变动,获取最新数量
|
||||
//childrenLength用于canNext方法判断当前页是否是最后一页,是则不能滑动,不捕获事件
|
||||
ob = new MutationObserver(() => {
|
||||
state.wrapper.childrenLength = wrapperEl.value.children.length
|
||||
state.wrapper.childrenLength = slideListEl.value.children.length
|
||||
})
|
||||
ob.observe(wrapperEl.value, { childList: true })
|
||||
ob.observe(slideListEl.value, { childList: true })
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@ -104,16 +104,16 @@ onUnmounted(() => {
|
||||
})
|
||||
|
||||
function touchStart(e) {
|
||||
slideTouchStart(e, wrapperEl.value, state)
|
||||
slideTouchStart(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
function touchMove(e) {
|
||||
slideTouchMove(e, wrapperEl.value, state)
|
||||
slideTouchMove(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
function touchEnd(e) {
|
||||
slideTouchEnd(e, state)
|
||||
slideReset(e, wrapperEl.value, state, emit)
|
||||
slideReset(e, slideListEl.value, state, emit)
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -130,7 +130,7 @@ function touchEnd(e) {
|
||||
|
||||
<div
|
||||
class="slide-list"
|
||||
ref="wrapperEl"
|
||||
ref="slideListEl"
|
||||
@pointerdown="touchStart"
|
||||
@pointermove="touchMove"
|
||||
@pointerup="touchEnd"
|
||||
|
||||
@ -31,7 +31,7 @@ const props = defineProps({
|
||||
const emit = defineEmits(['update:index'])
|
||||
|
||||
//slide-list的ref引用
|
||||
const wrapperEl = ref(null)
|
||||
const slideListEl = ref(null)
|
||||
|
||||
const state = reactive({
|
||||
judgeValue: 20, //一个用于判断滑动朝向的固定值
|
||||
@ -58,32 +58,32 @@ watch(
|
||||
if (state.localIndex !== newVal) {
|
||||
state.localIndex = newVal
|
||||
if (props.changeActiveIndexUseAnim) {
|
||||
_css(wrapperEl.value, 'transition-duration', `300ms`)
|
||||
_css(slideListEl.value, 'transition-duration', `300ms`)
|
||||
}
|
||||
_css(
|
||||
wrapperEl.value,
|
||||
slideListEl.value,
|
||||
'transform',
|
||||
`translate3d(0,${getSlideOffset(state, wrapperEl.value)}px, 0)`
|
||||
`translate3d(0,${getSlideOffset(state, slideListEl.value)}px, 0)`
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
slideInit(wrapperEl.value, state)
|
||||
slideInit(slideListEl.value, state)
|
||||
})
|
||||
|
||||
function touchStart(e) {
|
||||
slideTouchStart(e, wrapperEl.value, state)
|
||||
slideTouchStart(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
function touchMove(e) {
|
||||
slideTouchMove(e, wrapperEl.value, state)
|
||||
slideTouchMove(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
function touchEnd(e) {
|
||||
slideTouchEnd(e, state)
|
||||
slideReset(e, wrapperEl.value, state, emit)
|
||||
slideReset(e, slideListEl.value, state, emit)
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -91,7 +91,7 @@ function touchEnd(e) {
|
||||
<div class="slide v">
|
||||
<div
|
||||
class="slide-list flex-direction-column"
|
||||
ref="wrapperEl"
|
||||
ref="slideListEl"
|
||||
@pointerdown="touchStart"
|
||||
@pointermove="touchMove"
|
||||
@pointerup="touchEnd"
|
||||
|
||||
@ -34,6 +34,7 @@ const props = defineProps({
|
||||
return []
|
||||
}
|
||||
},
|
||||
//页面中同时存在多少个SlideItem
|
||||
virtualTotal: {
|
||||
type: Number,
|
||||
default: () => 5
|
||||
@ -59,7 +60,7 @@ const emit = defineEmits(['update:index', 'loadMore', 'refresh'])
|
||||
|
||||
const appInsMap = new Map()
|
||||
const itemClassName = 'slide-item'
|
||||
const wrapperEl = ref<HTMLDivElement>(null)
|
||||
const slideListEl = ref<HTMLDivElement>(null)
|
||||
const state = reactive({
|
||||
judgeValue: 20,
|
||||
type: SlideType.VERTICAL_INFINITE,
|
||||
@ -90,7 +91,7 @@ watch(
|
||||
// 为了在用户快速滑动时,无需频繁等待请求接口加载数据,给用户更好的使用体验
|
||||
// 这里额外加载3条数据。所以此刻,html里面有原本的5个加新增的3个,一共8个dom
|
||||
// 用户往下滑动时只删除前面多余的dom,等滑动到临界值(virtualTotal/2+1)时,再去执行新增逻辑
|
||||
// let lastSlideItem = wrapperEl.value.querySelector(`.${itemClassName}:last-child`)
|
||||
// let lastSlideItem = slideListEl.value.querySelector(`.${itemClassName}:last-child`)
|
||||
// let top = _css(lastSlideItem, 'top')
|
||||
// let newListStartIndex = Number(lastSlideItem.getAttribute('data-index')) + 1
|
||||
// // console.log('newListStartIndex', newListStartIndex)
|
||||
@ -101,7 +102,7 @@ watch(
|
||||
// //因为有一条情况:当滑动最后一条和二条的时候top值不会继续加。此时新增的数据如果还
|
||||
// // 计算top值的,会和前面的对不上
|
||||
// _css(el, 'top', top)
|
||||
// wrapperEl.value.appendChild(el)
|
||||
// slideListEl.value.appendChild(el)
|
||||
// state.wrapper.childrenLength++
|
||||
// })
|
||||
}
|
||||
@ -152,37 +153,46 @@ watch(
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
slideInit(wrapperEl.value, state)
|
||||
slideInit(slideListEl.value, state)
|
||||
})
|
||||
|
||||
function insertContent(list = props.list) {
|
||||
if (!list.length) return
|
||||
wrapperEl.value.innerHTML = ''
|
||||
/**
|
||||
* 插入SlideItem
|
||||
*/
|
||||
function insertContent() {
|
||||
if (!props.list.length) return
|
||||
//清空SlideList
|
||||
slideListEl.value.innerHTML = ''
|
||||
let half = (props.virtualTotal - 1) / 2
|
||||
//因为我们只渲染 props.virtualTotal 条数据到dom中,并且当前index有可能不是0,所以需要计算出起始下标和结束下标
|
||||
let start = 0
|
||||
if (state.localIndex >= half) {
|
||||
if (state.localIndex > half) {
|
||||
start = state.localIndex - half
|
||||
}
|
||||
let end = start + props.virtualTotal
|
||||
if (end >= list.length) {
|
||||
end = list.length
|
||||
if (end >= props.list.length) {
|
||||
end = props.list.length
|
||||
start = end - props.virtualTotal
|
||||
}
|
||||
if (start < 0) start = 0
|
||||
|
||||
// console.log('start', start, end)
|
||||
list.slice(start, end).map((item, index) => {
|
||||
//自动播放,当前条(可能是0,可能是其他),试了下用jq来找元素,然后trigger play事件,要慢点样
|
||||
//插入start到end范围内的数据到dom中
|
||||
props.list.slice(start, end).map((item, index) => {
|
||||
let el = getInsEl(item, start + index, start + index === state.localIndex)
|
||||
wrapperEl.value.appendChild(el)
|
||||
slideListEl.value.appendChild(el)
|
||||
})
|
||||
|
||||
//设置SlideList的偏移量
|
||||
_css(
|
||||
wrapperEl.value,
|
||||
slideListEl.value,
|
||||
'transform',
|
||||
`translate3d(0px,${getSlideOffset(state, wrapperEl.value)}px, 0px)`
|
||||
`translate3d(0px,${getSlideOffset(state, slideListEl.value)}px, 0px)`
|
||||
)
|
||||
|
||||
if (state.localIndex > 2 && list.length > 5) {
|
||||
let list = wrapperEl.value.querySelectorAll(`.${itemClassName}`)
|
||||
//因为index有可能不是0,所以要设置Item的top偏移量
|
||||
if (state.localIndex > 2 && props.list.length > 5) {
|
||||
let list = slideListEl.value.querySelectorAll(`.${itemClassName}`)
|
||||
list.forEach((item) => {
|
||||
if (list.length - state.localIndex > 2) {
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
@ -191,13 +201,13 @@ function insertContent(list = props.list) {
|
||||
}
|
||||
})
|
||||
}
|
||||
state.wrapper.childrenLength = wrapperEl.value.children.length
|
||||
state.wrapper.childrenLength = slideListEl.value.children.length
|
||||
// console.log('list[state.localIndex]',list[state.localIndex])
|
||||
bus.emit(EVENT_KEY.CURRENT_ITEM, list[state.localIndex])
|
||||
bus.emit(EVENT_KEY.CURRENT_ITEM, props.list[state.localIndex])
|
||||
}
|
||||
|
||||
function dislike() {
|
||||
// let currentItem = $(wrapperEl.value).find(`.${itemClassName}[data-index=${state.localIndex}]`)
|
||||
// let currentItem = $(slideListEl.value).find(`.${itemClassName}[data-index=${state.localIndex}]`)
|
||||
// let replaceItem = getInsEl(item, state.localIndex, true)
|
||||
// $(replaceItem).css('top', currentItem.css('top'))
|
||||
// currentItem.replaceWith(replaceItem)
|
||||
@ -205,6 +215,12 @@ function dislike() {
|
||||
|
||||
defineExpose({ dislike })
|
||||
|
||||
/**
|
||||
* 获取Vue组件渲染之后的dom元素
|
||||
* @param item
|
||||
* @param index
|
||||
* @param play
|
||||
*/
|
||||
function getInsEl(item, index, play = false) {
|
||||
// console.log('index', cloneDeep(item), index, play)
|
||||
let slideVNode = props.render(item, index, play, props.uniqueId)
|
||||
@ -213,6 +229,7 @@ function getInsEl(item, index, play = false) {
|
||||
if (import.meta.env.PROD) {
|
||||
parent.classList.add('slide-item')
|
||||
parent.setAttribute('data-index', index)
|
||||
//将Vue组件渲染到一个div上
|
||||
vueRender(slideVNode, parent)
|
||||
appInsMap.set(index, {
|
||||
unmount: () => {
|
||||
@ -222,6 +239,7 @@ function getInsEl(item, index, play = false) {
|
||||
})
|
||||
return parent
|
||||
} else {
|
||||
//创建一个新的Vue实例,并挂载到一个div上
|
||||
const app = createApp({
|
||||
render() {
|
||||
return <SlideItem data-index={index}>{slideVNode}</SlideItem>
|
||||
@ -234,12 +252,11 @@ function getInsEl(item, index, play = false) {
|
||||
}
|
||||
|
||||
function touchStart(e) {
|
||||
slideTouchStart(e, wrapperEl.value, state)
|
||||
slideTouchStart(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
//TODO 2022-3-28:在最顶部,反复滑动会抖动一下,初步猜测是因为方向变了,导致的加判断距离变成了减
|
||||
function touchMove(e) {
|
||||
slideTouchMove(e, wrapperEl.value, state, canNext)
|
||||
slideTouchMove(e, slideListEl.value, state, canNext)
|
||||
}
|
||||
|
||||
function touchEnd(e) {
|
||||
@ -252,73 +269,73 @@ function touchEnd(e) {
|
||||
emit('refresh')
|
||||
}
|
||||
slideTouchEnd(e, state, canNext, (isNext) => {
|
||||
let half = (props.virtualTotal + 1) / 2
|
||||
let half = (props.virtualTotal - 1) / 2
|
||||
if (props.list.length > props.virtualTotal) {
|
||||
//往下滑
|
||||
if (isNext) {
|
||||
if (state.localIndex > props.list.length - props.virtualTotal && state.localIndex >= half) {
|
||||
if (state.localIndex > props.list.length - props.virtualTotal && state.localIndex > half) {
|
||||
emit('loadMore')
|
||||
}
|
||||
let addItemIndex = state.localIndex + 2
|
||||
let res = wrapperEl.value.querySelector(`.${itemClassName}[data-index='${addItemIndex}']`)
|
||||
if (state.wrapper.childrenLength < props.virtualTotal) {
|
||||
if (!res) {
|
||||
wrapperEl.value.appendChild(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
}
|
||||
let addItemIndex = state.localIndex + half
|
||||
let res = slideListEl.value.querySelector(`.${itemClassName}[data-index='${addItemIndex}']`)
|
||||
if (!res) {
|
||||
slideListEl.value.appendChild(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
}
|
||||
if (
|
||||
state.wrapper.childrenLength === props.virtualTotal &&
|
||||
state.localIndex >= (props.virtualTotal + 1) / 2 &&
|
||||
state.localIndex <= props.list.length - 3
|
||||
) {
|
||||
if (!res) {
|
||||
wrapperEl.value.appendChild(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
let index = wrapperEl.value
|
||||
.querySelector(`.${itemClassName}:first-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
wrapperEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
}
|
||||
if (state.wrapper.childrenLength > props.virtualTotal) {
|
||||
wrapperEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
let index = Number(item.getAttribute('data-index'))
|
||||
if (index < state.localIndex - 2) {
|
||||
appInsMap.get(index).unmount()
|
||||
}
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
if (state.localIndex > half) {
|
||||
let index = slideListEl.value
|
||||
.querySelector(`.${itemClassName}:first-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
|
||||
slideListEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
_css(item, 'top', (state.localIndex - half) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let addItemIndex = state.localIndex - 2
|
||||
let res = wrapperEl.value.querySelector(`.${itemClassName}[data-index='${addItemIndex}']`)
|
||||
|
||||
if (state.localIndex > 1 && state.localIndex <= props.list.length - 4) {
|
||||
let addIndex = state.localIndex - half
|
||||
if (addIndex >= 0) {
|
||||
let res = slideListEl.value.querySelector(`.${itemClassName}[data-index='${addIndex}']`)
|
||||
if (!res) {
|
||||
wrapperEl.value.prepend(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
let index = wrapperEl.value
|
||||
.querySelector(`.${itemClassName}:last-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
// $(wrapperEl.value).find(".base-slide-item:last").remove()
|
||||
wrapperEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
})
|
||||
slideListEl.value.prepend(getInsEl(props.list[addIndex], addIndex))
|
||||
}
|
||||
}
|
||||
|
||||
if (state.wrapper.childrenLength > props.virtualTotal) {
|
||||
let index = wrapperEl.value
|
||||
if (state.localIndex >= half) {
|
||||
let index = slideListEl.value
|
||||
.querySelector(`.${itemClassName}:last-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
|
||||
slideListEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
_css(item, 'top', (state.localIndex - half) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
|
||||
// if (state.localIndex > 1 && state.localIndex <= props.list.length - 4) {
|
||||
// if (!res) {
|
||||
// slideListEl.value.prepend(getInsEl(props.list[addIndex], addIndex))
|
||||
// let index = slideListEl.value
|
||||
// .querySelector(`.${itemClassName}:last-child`)
|
||||
// .getAttribute('data-index')
|
||||
// appInsMap.get(Number(index)).unmount()
|
||||
// // $(slideListEl.value).find(".base-slide-item:last").remove()
|
||||
// slideListEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
// _css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (state.wrapper.childrenLength > props.virtualTotal) {
|
||||
// let index = slideListEl.value
|
||||
// .querySelector(`.${itemClassName}:last-child`)
|
||||
// .getAttribute('data-index')
|
||||
// appInsMap.get(Number(index)).unmount()
|
||||
// }
|
||||
}
|
||||
state.wrapper.childrenLength = wrapperEl.value.children.length
|
||||
state.wrapper.childrenLength = slideListEl.value.children.length
|
||||
}
|
||||
})
|
||||
slideReset(e, wrapperEl.value, state, emit)
|
||||
slideReset(e, slideListEl.value, state, emit)
|
||||
}
|
||||
|
||||
function canNext(state, isNext) {
|
||||
@ -334,7 +351,7 @@ function canNext(state, isNext) {
|
||||
<Loading v-if="props.loading && props.list.length === 0" />
|
||||
<div
|
||||
class="slide-list flex-direction-column"
|
||||
ref="wrapperEl"
|
||||
ref="slideListEl"
|
||||
@click="null"
|
||||
@pointerdown="touchStart"
|
||||
@pointermove="touchMove"
|
||||
|
||||
365
src/components/slide/SlideVerticalInfinite2.vue
Normal file
365
src/components/slide/SlideVerticalInfinite2.vue
Normal file
@ -0,0 +1,365 @@
|
||||
<script setup lang="tsx">
|
||||
import { createApp, onMounted, reactive, ref, render as vueRender, watch } from 'vue'
|
||||
import {
|
||||
getSlideOffset,
|
||||
slideInit,
|
||||
slideReset,
|
||||
slideTouchEnd,
|
||||
slideTouchMove,
|
||||
slideTouchStart
|
||||
} from '@/utils/slide'
|
||||
import { SlideType } from '@/utils/const_var'
|
||||
import SlideItem from '@/components/slide/SlideItem.vue'
|
||||
import bus, { EVENT_KEY } from '../../utils/bus'
|
||||
import Loading from '@/components/Loading.vue'
|
||||
import { useBaseStore } from '@/store/pinia'
|
||||
import { _css } from '@/utils/dom'
|
||||
|
||||
const props = defineProps({
|
||||
index: {
|
||||
type: Number,
|
||||
default: () => {
|
||||
return -1
|
||||
}
|
||||
},
|
||||
render: {
|
||||
type: Function,
|
||||
default: () => {
|
||||
return null
|
||||
}
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
//页面中同时存在多少个SlideItem
|
||||
virtualTotal: {
|
||||
type: Number,
|
||||
default: () => 5
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
uniqueId: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: () => false
|
||||
}
|
||||
})
|
||||
const emit = defineEmits(['update:index', 'loadMore', 'refresh'])
|
||||
|
||||
const appInsMap = new Map()
|
||||
const itemClassName = 'slide-item'
|
||||
const slideListEl = ref<HTMLDivElement>(null)
|
||||
const state = reactive({
|
||||
judgeValue: 20,
|
||||
type: SlideType.VERTICAL_INFINITE,
|
||||
name: props.name,
|
||||
localIndex: props.index,
|
||||
needCheck: true,
|
||||
next: false,
|
||||
isDown: false,
|
||||
start: { x: 0, y: 0, time: 0 },
|
||||
move: { x: 0, y: 0 },
|
||||
wrapper: { width: 0, height: 0, childrenLength: 0 }
|
||||
})
|
||||
const baseStore = useBaseStore()
|
||||
|
||||
watch(
|
||||
() => props.list,
|
||||
(newVal, oldVal) => {
|
||||
// console.log('watch-list', newVal.length, oldVal.length, newVal)
|
||||
//新数据长度比老数据长度小,说明是刷新
|
||||
if (newVal.length < oldVal.length) {
|
||||
insertContent()
|
||||
} else {
|
||||
//没数据就直接插入
|
||||
if (oldVal.length === 0) {
|
||||
insertContent()
|
||||
} else {
|
||||
// 走到这里,说明是通过接口加载了下一页的数据,
|
||||
// 为了在用户快速滑动时,无需频繁等待请求接口加载数据,给用户更好的使用体验
|
||||
// 这里额外加载3条数据。所以此刻,html里面有原本的5个加新增的3个,一共8个dom
|
||||
// 用户往下滑动时只删除前面多余的dom,等滑动到临界值(virtualTotal/2+1)时,再去执行新增逻辑
|
||||
// let lastSlideItem = slideListEl.value.querySelector(`.${itemClassName}:last-child`)
|
||||
// let top = _css(lastSlideItem, 'top')
|
||||
// let newListStartIndex = Number(lastSlideItem.getAttribute('data-index')) + 1
|
||||
// // console.log('newListStartIndex', newListStartIndex)
|
||||
// newVal.slice(newListStartIndex, newListStartIndex + 3).map((item, index) => {
|
||||
// let el = getInsEl(item, newListStartIndex + index)
|
||||
// //这里必须要设置个top值,不然会把前面的条目给覆盖掉
|
||||
// //2022-3-27,这里不用计算,直接用已用slide-item最后一条的top值,
|
||||
// //因为有一条情况:当滑动最后一条和二条的时候top值不会继续加。此时新增的数据如果还
|
||||
// // 计算top值的,会和前面的对不上
|
||||
// _css(el, 'top', top)
|
||||
// slideListEl.value.appendChild(el)
|
||||
// state.wrapper.childrenLength++
|
||||
// })
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.index,
|
||||
(newVal, oldVal) => {
|
||||
state.localIndex = newVal
|
||||
// console.log('watch-index', newVal, oldVal)
|
||||
if (!props.list.length) return
|
||||
bus.emit(EVENT_KEY.CURRENT_ITEM, props.list[newVal])
|
||||
bus.emit(EVENT_KEY.SINGLE_CLICK_BROADCAST, {
|
||||
uniqueId: props.uniqueId,
|
||||
index: newVal,
|
||||
type: EVENT_KEY.ITEM_PLAY
|
||||
})
|
||||
setTimeout(() => {
|
||||
bus.emit(EVENT_KEY.SINGLE_CLICK_BROADCAST, {
|
||||
uniqueId: props.uniqueId,
|
||||
index: oldVal,
|
||||
type: EVENT_KEY.ITEM_STOP
|
||||
})
|
||||
}, 200)
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.active,
|
||||
(newVal) => {
|
||||
//当激活此页时,如果list为空,那么向上发射事件通知父组件请求数据
|
||||
if (newVal && !props.list.length) {
|
||||
return emit('refresh')
|
||||
}
|
||||
// console.log('active', 'newVal', newVal, 'oldVal', oldVal)
|
||||
if (newVal) {
|
||||
bus.emit(EVENT_KEY.CURRENT_ITEM, props.list[state.localIndex])
|
||||
}
|
||||
bus.emit(EVENT_KEY.SINGLE_CLICK_BROADCAST, {
|
||||
uniqueId: props.uniqueId,
|
||||
index: state.localIndex,
|
||||
type: newVal === false ? EVENT_KEY.ITEM_STOP : EVENT_KEY.ITEM_PLAY
|
||||
})
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
slideInit(slideListEl.value, state)
|
||||
})
|
||||
|
||||
/**
|
||||
* 插入SlideItem
|
||||
*/
|
||||
function insertContent() {
|
||||
if (!props.list.length) return
|
||||
//清空SlideList
|
||||
slideListEl.value.innerHTML = ''
|
||||
let half = (props.virtualTotal - 1) / 2
|
||||
//因为我们只渲染 props.virtualTotal 条数据到dom中,并且当前index有可能不是0,所以需要计算出起始下标和结束下标
|
||||
let start = 0
|
||||
if (state.localIndex > half) {
|
||||
start = state.localIndex - half
|
||||
}
|
||||
let end = start + props.virtualTotal
|
||||
if (end >= props.list.length) {
|
||||
end = props.list.length
|
||||
start = end - props.virtualTotal
|
||||
}
|
||||
if (start < 0) start = 0
|
||||
|
||||
// console.log('start', start, end)
|
||||
//插入start到end范围内的数据到dom中
|
||||
props.list.slice(start, end).map((item, index) => {
|
||||
let el = getInsEl(item, start + index, start + index === state.localIndex)
|
||||
slideListEl.value.appendChild(el)
|
||||
})
|
||||
|
||||
//设置SlideList的偏移量
|
||||
_css(
|
||||
slideListEl.value,
|
||||
'transform',
|
||||
`translate3d(0px,${getSlideOffset(state, slideListEl.value)}px, 0px)`
|
||||
)
|
||||
|
||||
//因为index有可能不是0,所以要设置Item的top偏移量
|
||||
if (state.localIndex > 2 && props.list.length > 5) {
|
||||
let list = slideListEl.value.querySelectorAll(`.${itemClassName}`)
|
||||
list.forEach((item) => {
|
||||
if (list.length - state.localIndex > 2) {
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
} else {
|
||||
_css(item, 'top', start * state.wrapper.height)
|
||||
}
|
||||
})
|
||||
}
|
||||
state.wrapper.childrenLength = slideListEl.value.children.length
|
||||
// console.log('list[state.localIndex]',list[state.localIndex])
|
||||
bus.emit(EVENT_KEY.CURRENT_ITEM, props.list[state.localIndex])
|
||||
}
|
||||
|
||||
function dislike() {
|
||||
// let currentItem = $(slideListEl.value).find(`.${itemClassName}[data-index=${state.localIndex}]`)
|
||||
// let replaceItem = getInsEl(item, state.localIndex, true)
|
||||
// $(replaceItem).css('top', currentItem.css('top'))
|
||||
// currentItem.replaceWith(replaceItem)
|
||||
}
|
||||
|
||||
defineExpose({ dislike })
|
||||
|
||||
/**
|
||||
* 获取Vue组件渲染之后的dom元素
|
||||
* @param item
|
||||
* @param index
|
||||
* @param play
|
||||
*/
|
||||
function getInsEl(item, index, play = false) {
|
||||
// console.log('index', cloneDeep(item), index, play)
|
||||
let slideVNode = props.render(item, index, play, props.uniqueId)
|
||||
const parent = document.createElement('div')
|
||||
//TODO 打包到线上时用这个,这个在开发时任何修改都会刷新页面
|
||||
if (import.meta.env.PROD) {
|
||||
parent.classList.add('slide-item')
|
||||
parent.setAttribute('data-index', index)
|
||||
//将Vue组件渲染到一个div上
|
||||
vueRender(slideVNode, parent)
|
||||
appInsMap.set(index, {
|
||||
unmount: () => {
|
||||
vueRender(null, parent)
|
||||
parent.remove()
|
||||
}
|
||||
})
|
||||
return parent
|
||||
} else {
|
||||
//创建一个新的Vue实例,并挂载到一个div上
|
||||
const app = createApp({
|
||||
render() {
|
||||
return <SlideItem data-index={index}>{slideVNode}</SlideItem>
|
||||
}
|
||||
})
|
||||
const ins = app.mount(parent)
|
||||
appInsMap.set(index, app)
|
||||
return ins.$el
|
||||
}
|
||||
}
|
||||
|
||||
function touchStart(e) {
|
||||
slideTouchStart(e, slideListEl.value, state)
|
||||
}
|
||||
|
||||
function touchMove(e) {
|
||||
slideTouchMove(e, slideListEl.value, state, canNext)
|
||||
}
|
||||
|
||||
function touchEnd(e) {
|
||||
let isNext = state.move.y < 0
|
||||
if (
|
||||
state.localIndex === 0 &&
|
||||
!isNext &&
|
||||
state.move.y > baseStore.homeRefresh + baseStore.judgeValue
|
||||
) {
|
||||
emit('refresh')
|
||||
}
|
||||
slideTouchEnd(e, state, canNext, (isNext) => {
|
||||
let half = (props.virtualTotal - 1) / 2
|
||||
if (props.list.length > props.virtualTotal) {
|
||||
//往下滑
|
||||
if (isNext) {
|
||||
if (state.localIndex > props.list.length - props.virtualTotal && state.localIndex > half) {
|
||||
emit('loadMore')
|
||||
}
|
||||
let addItemIndex = state.localIndex + 2
|
||||
console.log('addItemIndex', addItemIndex)
|
||||
let res = slideListEl.value.querySelector(`.${itemClassName}[data-index='${addItemIndex}']`)
|
||||
if (state.wrapper.childrenLength < props.virtualTotal) {
|
||||
if (!res) {
|
||||
slideListEl.value.appendChild(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
}
|
||||
}
|
||||
if (
|
||||
state.wrapper.childrenLength === props.virtualTotal &&
|
||||
state.localIndex > half &&
|
||||
state.localIndex <= props.list.length - 3
|
||||
) {
|
||||
if (!res) {
|
||||
slideListEl.value.appendChild(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
let index = slideListEl.value
|
||||
.querySelector(`.${itemClassName}:first-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
slideListEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
}
|
||||
if (state.wrapper.childrenLength > props.virtualTotal) {
|
||||
slideListEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
let index = Number(item.getAttribute('data-index'))
|
||||
if (index < state.localIndex - 2) {
|
||||
appInsMap.get(index).unmount()
|
||||
}
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
let addItemIndex = state.localIndex - 2
|
||||
let res = slideListEl.value.querySelector(`.${itemClassName}[data-index='${addItemIndex}']`)
|
||||
|
||||
if (state.localIndex > 1 && state.localIndex <= props.list.length - 4) {
|
||||
if (!res) {
|
||||
slideListEl.value.prepend(getInsEl(props.list[addItemIndex], addItemIndex))
|
||||
let index = slideListEl.value
|
||||
.querySelector(`.${itemClassName}:last-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
// $(slideListEl.value).find(".base-slide-item:last").remove()
|
||||
slideListEl.value.querySelectorAll(`.${itemClassName}`).forEach((item) => {
|
||||
_css(item, 'top', (state.localIndex - 2) * state.wrapper.height)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (state.wrapper.childrenLength > props.virtualTotal) {
|
||||
let index = slideListEl.value
|
||||
.querySelector(`.${itemClassName}:last-child`)
|
||||
.getAttribute('data-index')
|
||||
appInsMap.get(Number(index)).unmount()
|
||||
}
|
||||
}
|
||||
state.wrapper.childrenLength = slideListEl.value.children.length
|
||||
}
|
||||
})
|
||||
slideReset(e, slideListEl.value, state, emit)
|
||||
}
|
||||
|
||||
function canNext(state, isNext) {
|
||||
return !(
|
||||
(state.localIndex === 0 && !isNext) ||
|
||||
(state.localIndex === props.list.length - 1 && isNext)
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="slide slide-infinite">
|
||||
<Loading v-if="props.loading && props.list.length === 0" />
|
||||
<div
|
||||
class="slide-list flex-direction-column"
|
||||
ref="slideListEl"
|
||||
@click="null"
|
||||
@pointerdown="touchStart"
|
||||
@pointermove="touchMove"
|
||||
@pointerup="touchEnd"
|
||||
>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Loading…
Reference in New Issue
Block a user