feat: 文章图片点击缩放
点击图片全屏遮罩放大,Esc 或点击遮罩关闭,纯自定义实现无额外依赖。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,17 +5,91 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const isZoomed = ref(false)
|
||||||
|
|
||||||
const resolvedSrc = computed(() => {
|
const resolvedSrc = computed(() => {
|
||||||
if (!props.src) return ''
|
if (!props.src) return ''
|
||||||
// 绝对路径直接用
|
|
||||||
if (props.src.startsWith('/') || props.src.startsWith('http')) return props.src
|
if (props.src.startsWith('/') || props.src.startsWith('http')) return props.src
|
||||||
// 相对路径:基于当前路由拼接
|
|
||||||
const base = route.path.replace(/\/$/, '')
|
const base = route.path.replace(/\/$/, '')
|
||||||
return `${base}/${props.src}`
|
return `${base}/${props.src}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
isZoomed.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
isZoomed.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeydown(e: KeyboardEvent) {
|
||||||
|
if (e.key === 'Escape') close()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => document.addEventListener('keydown', onKeydown))
|
||||||
|
onUnmounted(() => document.removeEventListener('keydown', onKeydown))
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<img :src="resolvedSrc" :alt="alt" loading="lazy" decoding="async" />
|
<img
|
||||||
|
:src="resolvedSrc"
|
||||||
|
:alt="alt"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
class="zoomable-img"
|
||||||
|
@click="open"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ClientOnly>
|
||||||
|
<Teleport to="body">
|
||||||
|
<Transition name="zoom-fade">
|
||||||
|
<div
|
||||||
|
v-if="isZoomed"
|
||||||
|
class="zoom-overlay"
|
||||||
|
@click="close"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="resolvedSrc"
|
||||||
|
:alt="alt"
|
||||||
|
class="zoom-img"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Teleport>
|
||||||
|
</ClientOnly>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.zoomable-img {
|
||||||
|
cursor: zoom-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-overlay {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.8);
|
||||||
|
cursor: zoom-out;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-fade-enter-active,
|
||||||
|
.zoom-fade-leave-active {
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-fade-enter-from,
|
||||||
|
.zoom-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user