feat: switch hotspot coordinates from container percentage to absolute content pixels
This commit is contained in:
@@ -218,6 +218,7 @@ init()
|
||||
:hotspots="store.hotspots"
|
||||
:is-image-scene="store.isImageScene"
|
||||
:image-url="store.currentScene?.imageUrl"
|
||||
:content-size="store.currentScene?.contentSize ?? null"
|
||||
@click-hotspot="clickHotspot"
|
||||
/>
|
||||
<Subtitles
|
||||
|
||||
@@ -1,35 +1,91 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import type { Hotspot } from '@engine/types'
|
||||
|
||||
const props = defineProps<{
|
||||
hotspots: Hotspot[]
|
||||
isImageScene: boolean
|
||||
imageUrl?: string | null
|
||||
contentSize?: { w: number; h: number } | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
clickHotspot: [hotspotId: string]
|
||||
}>()
|
||||
|
||||
function hsStyle(hs: Hotspot) {
|
||||
return {
|
||||
left: `${hs.x * 100}%`,
|
||||
top: `${hs.y * 100}%`,
|
||||
width: `${hs.width * 100}%`,
|
||||
height: `${hs.height * 100}%`,
|
||||
const layerRef = ref<HTMLDivElement | null>(null)
|
||||
const layerW = ref(window.innerWidth)
|
||||
const layerH = ref(window.innerHeight)
|
||||
|
||||
function measure() {
|
||||
if (layerRef.value) {
|
||||
layerW.value = layerRef.value.clientWidth
|
||||
layerH.value = layerRef.value.clientHeight
|
||||
} else {
|
||||
layerW.value = window.innerWidth
|
||||
layerH.value = window.innerHeight
|
||||
}
|
||||
}
|
||||
|
||||
let resizeObs: ResizeObserver | null = null
|
||||
|
||||
onMounted(() => {
|
||||
measure()
|
||||
resizeObs = new ResizeObserver(() => measure())
|
||||
if (layerRef.value) resizeObs.observe(layerRef.value)
|
||||
window.addEventListener('resize', measure)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
resizeObs?.disconnect()
|
||||
window.removeEventListener('resize', measure)
|
||||
})
|
||||
|
||||
interface Rect {
|
||||
left: string
|
||||
top: string
|
||||
width: string
|
||||
height: string
|
||||
}
|
||||
|
||||
const hotspotRects = computed<Rect[]>(() => {
|
||||
const cw = layerW.value
|
||||
const ch = layerH.value
|
||||
const cs = props.contentSize
|
||||
|
||||
if (!cs || !cs.w || !cs.h) {
|
||||
return props.hotspots.map((hs) => ({
|
||||
left: `${(hs.x / (cs?.w || 1)) * 100}%`,
|
||||
top: `${(hs.y / (cs?.h || 1)) * 100}%`,
|
||||
width: `${(hs.width / (cs?.w || 1)) * 100}%`,
|
||||
height: `${(hs.height / (cs?.h || 1)) * 100}%`,
|
||||
}))
|
||||
}
|
||||
|
||||
const scale = Math.min(cw / cs.w, ch / cs.h)
|
||||
const renderW = cs.w * scale
|
||||
const renderH = cs.h * scale
|
||||
const ox = (cw - renderW) / 2
|
||||
const oy = (ch - renderH) / 2
|
||||
|
||||
return props.hotspots.map((hs) => ({
|
||||
left: `${ox + hs.x * scale}px`,
|
||||
top: `${oy + hs.y * scale}px`,
|
||||
width: `${hs.width * scale}px`,
|
||||
height: `${hs.height * scale}px`,
|
||||
}))
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="hotspot-layer" v-if="(hotspots.length > 0) || (isImageScene && imageUrl)">
|
||||
<div ref="layerRef" class="hotspot-layer" v-if="(hotspots.length > 0) || (isImageScene && imageUrl)">
|
||||
<img v-if="isImageScene && imageUrl" :src="imageUrl" class="hotspot-image" />
|
||||
|
||||
<div
|
||||
v-for="hs in hotspots"
|
||||
v-for="(hs, i) in hotspots"
|
||||
:key="hs.id"
|
||||
class="hotspot-rect"
|
||||
:style="hsStyle(hs)"
|
||||
:style="hotspotRects[i] as any"
|
||||
@click.stop="emit('clickHotspot', hs.id)"
|
||||
:title="hs.label"
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user