|
|
@@ -0,0 +1,210 @@
|
|
|
+<template>
|
|
|
+ <div
|
|
|
+ :style="boxStyle"
|
|
|
+ class="w-full h-full flex items-center justify-center overflow-hidden relative"
|
|
|
+ >
|
|
|
+ <ImageBg
|
|
|
+ v-if="styleMap?.mainStyle?.imageSrc"
|
|
|
+ :src="styleMap?.mainStyle?.imageSrc"
|
|
|
+ :image-style="styleMap?.mainStyle?.imageStyle"
|
|
|
+ />
|
|
|
+ <ImageBg
|
|
|
+ :src="currentSrc || defaultImg"
|
|
|
+ :image-style="styleMap?.mainStyle?.image"
|
|
|
+ :image-props="imageProps"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { computed, ImgHTMLAttributes, onBeforeUnmount, ref, watch } from 'vue'
|
|
|
+import { useProjectStore } from '@/store/modules/project'
|
|
|
+import defaultImg from '@/assets/default.png'
|
|
|
+import ImageBg from '../ImageBg.vue'
|
|
|
+import { useWidgetStyle } from '../hooks/useWidgetStyle'
|
|
|
+
|
|
|
+const props = defineProps<{
|
|
|
+ width: number
|
|
|
+ height: number
|
|
|
+ styles: any
|
|
|
+ part?: string
|
|
|
+ state?: string
|
|
|
+ images: string[]
|
|
|
+ time: number
|
|
|
+ repeatCount: number
|
|
|
+ playback: boolean
|
|
|
+ playbackTime: number
|
|
|
+ playbackDelay: number
|
|
|
+ autoPlay: boolean
|
|
|
+ reverse: boolean
|
|
|
+}>()
|
|
|
+
|
|
|
+const projectStore = useProjectStore()
|
|
|
+const currentIndex = ref(0)
|
|
|
+let timer: ReturnType<typeof setTimeout> | null = null
|
|
|
+
|
|
|
+const styleMap = useWidgetStyle({
|
|
|
+ widget: 'lv_animimg',
|
|
|
+ props
|
|
|
+})
|
|
|
+
|
|
|
+const frameSources = computed(() => {
|
|
|
+ return (props.images || [])
|
|
|
+ .map((id) => {
|
|
|
+ const imagePath = projectStore.project?.resources.images.find((item) => item.id === id)?.path
|
|
|
+ if (!imagePath) return ''
|
|
|
+ return `local:///${(projectStore.projectPath + imagePath).replaceAll('\\', '/')}`
|
|
|
+ })
|
|
|
+ .filter((item) => item)
|
|
|
+})
|
|
|
+
|
|
|
+const currentSrc = computed(() => frameSources.value[currentIndex.value] || '')
|
|
|
+
|
|
|
+const imageProps = computed((): ImgHTMLAttributes => {
|
|
|
+ return {
|
|
|
+ width: '100%',
|
|
|
+ height: '100%',
|
|
|
+ style: {
|
|
|
+ objectFit: 'fill'
|
|
|
+ }
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const boxStyle = computed(() => {
|
|
|
+ return {
|
|
|
+ ...(styleMap.value?.mainStyle || {}),
|
|
|
+ width: `${props.width}px`,
|
|
|
+ height: `${props.height}px`,
|
|
|
+ display: 'flex',
|
|
|
+ justifyContent: 'center',
|
|
|
+ alignItems: 'center'
|
|
|
+ }
|
|
|
+})
|
|
|
+
|
|
|
+const clearTimer = () => {
|
|
|
+ if (timer) {
|
|
|
+ clearTimeout(timer)
|
|
|
+ timer = null
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const setInitialFrame = () => {
|
|
|
+ const count = frameSources.value.length
|
|
|
+ currentIndex.value = count ? (props.reverse ? count - 1 : 0) : 0
|
|
|
+}
|
|
|
+
|
|
|
+const buildCycleSteps = () => {
|
|
|
+ const count = frameSources.value.length
|
|
|
+ if (!count) return [] as { index: number; delay: number }[]
|
|
|
+
|
|
|
+ const all = Array.from({ length: count }, (_, index) => index)
|
|
|
+ const primary = props.reverse ? [...all].reverse() : all
|
|
|
+ const secondary = props.playback
|
|
|
+ ? props.reverse
|
|
|
+ ? all.slice(1)
|
|
|
+ : [...all].reverse().slice(1)
|
|
|
+ : []
|
|
|
+
|
|
|
+ const primaryDelay =
|
|
|
+ primary.length > 1 ? Math.max(0, props.time / Math.max(primary.length - 1, 1)) : props.time
|
|
|
+ const secondaryDelay =
|
|
|
+ secondary.length > 0
|
|
|
+ ? Math.max(0, props.playbackTime / Math.max(secondary.length, 1))
|
|
|
+ : props.playbackTime
|
|
|
+
|
|
|
+ const steps: { index: number; delay: number }[] = []
|
|
|
+
|
|
|
+ primary.forEach((index, stepIndex) => {
|
|
|
+ steps.push({
|
|
|
+ index,
|
|
|
+ delay:
|
|
|
+ stepIndex === primary.length - 1
|
|
|
+ ? secondary.length
|
|
|
+ ? props.playbackDelay
|
|
|
+ : 0
|
|
|
+ : primaryDelay
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ secondary.forEach((index) => {
|
|
|
+ steps.push({
|
|
|
+ index,
|
|
|
+ delay: secondaryDelay
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ return steps
|
|
|
+}
|
|
|
+
|
|
|
+const startPlayback = () => {
|
|
|
+ clearTimer()
|
|
|
+ const steps = buildCycleSteps()
|
|
|
+ if (!steps.length) {
|
|
|
+ currentIndex.value = 0
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ currentIndex.value = steps[0].index
|
|
|
+
|
|
|
+ if (!props.autoPlay || steps.length === 1) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ const totalCycles = props.repeatCount === -1 ? Number.POSITIVE_INFINITY : props.repeatCount + 1
|
|
|
+ let cycleIndex = 0
|
|
|
+ let stepIndex = 0
|
|
|
+
|
|
|
+ const schedule = () => {
|
|
|
+ if (!props.autoPlay) return
|
|
|
+
|
|
|
+ const currentStep = steps[stepIndex]
|
|
|
+ timer = setTimeout(
|
|
|
+ () => {
|
|
|
+ if (stepIndex < steps.length - 1) {
|
|
|
+ stepIndex += 1
|
|
|
+ currentIndex.value = steps[stepIndex].index
|
|
|
+ schedule()
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ cycleIndex += 1
|
|
|
+ if (cycleIndex >= totalCycles) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ stepIndex = 0
|
|
|
+ currentIndex.value = steps[0].index
|
|
|
+ schedule()
|
|
|
+ },
|
|
|
+ Math.max(0, currentStep.delay)
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ schedule()
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => [
|
|
|
+ props.images,
|
|
|
+ props.time,
|
|
|
+ props.repeatCount,
|
|
|
+ props.playback,
|
|
|
+ props.playbackTime,
|
|
|
+ props.playbackDelay,
|
|
|
+ props.autoPlay,
|
|
|
+ props.reverse
|
|
|
+ ],
|
|
|
+ () => {
|
|
|
+ setInitialFrame()
|
|
|
+ startPlayback()
|
|
|
+ },
|
|
|
+ {
|
|
|
+ deep: true,
|
|
|
+ immediate: true
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ clearTimer()
|
|
|
+})
|
|
|
+</script>
|