douyin/src/components/slide/SlideVerticalInfinite.vue
2024-03-26 17:57:07 +08:00

313 lines
10 KiB
Vue
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.

<script setup lang="jsx">
import {computed, createApp, h, onMounted, reactive, ref, render, watch} from "vue";
import GM from '../../utils'
import {getSlideDistance, slideInit, slideReset, slideTouchEnd, slideTouchMove, slideTouchStart} from "./common";
import {SlideType} from "@/utils/const_var";
import SlideItem from '@/components/slide/SlideItem.vue'
import bus, {EVENT_KEY} from "../../utils/bus";
import {useStore} from 'vuex'
import Loading from "@/components/Loading.vue";
import {cloneDeep} from "lodash-es";
const props = defineProps({
index: {
type: Number,
default: () => {
return -1
}
},
render: {
type: Function,
default: () => {
return null
}
},
list: {
type: Array,
default: () => {
return []
}
},
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 wrapperEl = ref(null)
const state = reactive({
name: props.name,
localIndex: props.index,
needCheck: true,
next: false,
start: {x: 0, y: 0, time: 0},
move: {x: 0, y: 0},
wrapper: {width: 0, height: 0, childrenLength: 0}
})
const store = useStore()
const homeRefresh = computed(() => store.state.homeRefresh)
const judgeValue = computed(() => store.state.judgeValue)
watch(
() => props.list,
(newVal, oldVal) => {
console.log('watch-list', newVal.length, oldVal.length,)
//新数据比老数据小,是刷新
if (newVal.length < oldVal.length) {
insertContent()
} else {
if (oldVal.length === 0) {
insertContent()
} else {
let lastSlideItem = $(wrapperEl.value).find(`.${itemClassName}:last`)
let top = lastSlideItem.css('top')
let lastIndex = Number(lastSlideItem.attr('data-index')) + 1
console.log('lastIndex', lastIndex)
newVal.slice(lastIndex, lastIndex + 3).map((item, index) => {
let el = getInsEl(item, lastIndex + index)
//这里必须要设置个top值不然会把前面的条目给覆盖掉
//2022-3-27这里不用计算直接用已用slide-item最后一条的top值
//因为有一条情况当滑动最后一条和二条的时候top值不会继续加。此时新增的数据如果还
// 计算top值的会和前面的对不上
$(el).css('top', top)
wrapperEl.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, oldVal) => {
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(wrapperEl.value, state, SlideType.VERTICAL)
insertContent()
})
function insertContent(list = props.list) {
if (!list.length) return
$(wrapperEl.value).empty()
let half = (props.virtualTotal - 1) / 2
let start = 0
if (state.localIndex >= half) {
start = state.localIndex - half
}
let end = start + props.virtualTotal
if (end >= list.length) {
end = 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事件要慢点样
let el = getInsEl(item, start + index, start + index === state.localIndex)
wrapperEl.value.appendChild(el)
}
)
GM.$setCss(wrapperEl.value, 'transform', `translate3d(0px,${getSlideDistance(state, SlideType.VERTICAL)}px, 0px)`)
if (state.localIndex > 2 && list.length > 5) {
$(wrapperEl.value).find(`.${itemClassName}`).each(function () {
if ((list.length - state.localIndex) > 2) {
$(this).css('top', (state.localIndex - 2) * state.wrapper.height)
} else {
$(this).css('top', start * state.wrapper.height)
}
})
}
state.wrapper.childrenLength = wrapperEl.value.children.length
bus.emit(EVENT_KEY.CURRENT_ITEM, list[state.localIndex])
}
function dislike(item) {
let currentItem = $(wrapperEl.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})
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)
render(slideVNode, parent)
appInsMap.set(index, {
unmount: () => {
render(null, parent)
parent.remove()
}
})
return parent
}
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, wrapperEl.value, state)
}
//TODO 2022-3-28:在最顶部,反复滑动会抖动一下,初步猜测是因为方向变了,导致的加判断距离变成了减
function touchMove(e) {
slideTouchMove(e, wrapperEl.value, state, judgeValue.value, canNext, null, SlideType.VERTICAL)
}
function touchEnd(e) {
let isNext = state.move.y < 0
if (state.localIndex === 0 && !isNext && state.move.y > (homeRefresh.value + judgeValue.value)) {
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
let res = $(wrapperEl.value).find(`.${itemClassName}[data-index=${addItemIndex}]`)
if (state.wrapper.childrenLength < props.virtualTotal) {
if (res.length === 0) {
wrapperEl.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.length === 0) {
wrapperEl.value.appendChild(getInsEl(props.list[addItemIndex], addItemIndex))
appInsMap.get($(wrapperEl.value).find(`.${itemClassName}:first`).data('index')).unmount()
// $(wrapperEl.value).find(".base-slide-item:first").remove()
$(wrapperEl.value).find(`.${itemClassName}`).each(function () {
$(this).css('top', (state.localIndex - 2) * state.wrapper.height)
})
}
}
if (state.wrapper.childrenLength > props.virtualTotal) {
$(wrapperEl.value).find(`.${itemClassName}`).each(function () {
let index = $(this).data('index')
if (index < (state.localIndex - 2)) {
appInsMap.get(index).unmount()
}
$(this).css('top', (state.localIndex - 2) * state.wrapper.height)
})
}
} else {
let addItemIndex = state.localIndex - 2
let res = $(wrapperEl.value).find(`.${itemClassName}[data-index=${addItemIndex}]`)
if (state.localIndex > 1 && state.localIndex <= props.list.length - 4) {
if (res.length === 0) {
wrapperEl.value.prepend(getInsEl(props.list[addItemIndex], addItemIndex))
appInsMap.get($(wrapperEl.value).find(`.${itemClassName}:last`).data('index')).unmount()
// $(wrapperEl.value).find(".base-slide-item:last").remove()
$(wrapperEl.value).find(`.${itemClassName}`).each(function () {
$(this).css('top', (state.localIndex - 2) * state.wrapper.height)
})
}
}
if (state.wrapper.childrenLength > props.virtualTotal) {
appInsMap.get($(wrapperEl.value).find(`.${itemClassName}:last`).data('index')).unmount()
}
}
state.wrapper.childrenLength = wrapperEl.value.children.length
}
}, null, SlideType.VERTICAL)
slideReset(wrapperEl.value, state, SlideType.VERTICAL, emit)
}
function canNext(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="wrapperEl"
@click="null"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
>
<slot></slot>
</div>
</div>
</template>