175 lines
3.1 KiB
Vue
175 lines
3.1 KiB
Vue
<!-- src/components/itemCenter.vue -->
|
||
<template>
|
||
<div style="position: relative; width: 100%" ref="containerRef">
|
||
<!-- 背景图 -->
|
||
<img
|
||
v-if="shouldShowImage"
|
||
v-smart-img.noFade
|
||
:src="imgUrl"
|
||
:class="flip ? 'flipImg' : ''"
|
||
alt=""
|
||
style="width: 100%; height: 100%; display: block"
|
||
/>
|
||
<div
|
||
v-else
|
||
class="placeholder"
|
||
:style="{
|
||
width: '100%',
|
||
height: '100%',
|
||
backgroundColor: 'transparent',
|
||
}"
|
||
></div>
|
||
|
||
<!-- 内容 -->
|
||
<div
|
||
class="scrollY"
|
||
style="
|
||
position: absolute;
|
||
inset: 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
"
|
||
:style="contentStyle"
|
||
>
|
||
<slot></slot>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, computed, onUnmounted, watch } from 'vue'
|
||
|
||
const props = defineProps({
|
||
imgUrl: {
|
||
type: String,
|
||
default: '',
|
||
required: true,
|
||
},
|
||
|
||
contentStyle: {
|
||
type: String,
|
||
default: '',
|
||
},
|
||
|
||
// 懒加载预加载范围
|
||
rootMargin: {
|
||
type: String,
|
||
default: '100px',
|
||
},
|
||
|
||
// 是否翻转图片
|
||
flip: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
|
||
// 是否启用懒加载
|
||
lazy: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
|
||
// 是否立即显示(优先级高于lazy)
|
||
immediate: {
|
||
type: Boolean,
|
||
default: false,
|
||
},
|
||
|
||
// 新增:父容器显示状态
|
||
show: {
|
||
type: Boolean,
|
||
default: true,
|
||
},
|
||
})
|
||
|
||
const containerRef = ref(null)
|
||
const isVisible = ref(false)
|
||
let observer = null
|
||
|
||
// 是否应该显示图片
|
||
const shouldShowImage = computed(() => {
|
||
// 如果标记为立即显示或禁用懒加载,则直接显示
|
||
if (props.immediate || !props.lazy) {
|
||
return true
|
||
}
|
||
|
||
// 否则根据是否可见决定
|
||
return isVisible.value
|
||
})
|
||
|
||
// 判断是否为OSS图片
|
||
// const isOSSImage = computed(() => {
|
||
// return (
|
||
// props.imgUrl.startsWith('/oss/h5/Azizi/') ||
|
||
// props.imgUrl.startsWith('https://cdn.azizichat.com/h5/Azizi/')
|
||
// )
|
||
// })
|
||
|
||
// 创建交叉观察器
|
||
const createObserver = () => {
|
||
if (!props.lazy || props.immediate) return
|
||
|
||
if (!containerRef.value) return
|
||
|
||
// 清理旧观察者
|
||
if (observer) {
|
||
observer.disconnect()
|
||
}
|
||
|
||
observer = new IntersectionObserver(
|
||
(entries) => {
|
||
entries.forEach((entry) => {
|
||
if (entry.isIntersecting) {
|
||
isVisible.value = true
|
||
observer.unobserve(entry.target)
|
||
}
|
||
})
|
||
},
|
||
{
|
||
rootMargin: props.rootMargin, // 提前100px开始加载
|
||
threshold: 0,
|
||
},
|
||
)
|
||
|
||
observer.observe(containerRef.value)
|
||
}
|
||
|
||
onMounted(() => {
|
||
createObserver()
|
||
})
|
||
|
||
// 监听 show 属性变化,重新创建观察者
|
||
watch(
|
||
() => props.show,
|
||
(newVal) => {
|
||
if (newVal && props.lazy && !props.immediate) {
|
||
// 延迟执行,确保 DOM 已渲染
|
||
setTimeout(() => {
|
||
createObserver()
|
||
}, 100)
|
||
}
|
||
},
|
||
)
|
||
|
||
onUnmounted(() => {
|
||
if (observer) {
|
||
observer.disconnect()
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.placeholder {
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.scrollY::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
[dir='rtl'] .flipImg {
|
||
transform: scaleX(-1);
|
||
}
|
||
</style>
|