🎉
This commit is contained in:
148
src/modules/map/MapScene.vue
Normal file
148
src/modules/map/MapScene.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<video
|
||||
v-if="type === 'video'"
|
||||
:src="src"
|
||||
:style="dynamicStyle"
|
||||
class="map-scene--video map-scene"
|
||||
ref="video"
|
||||
@loadedmetadata="onVideoLoaded"
|
||||
autoplay
|
||||
loop
|
||||
muted
|
||||
></video>
|
||||
<canvas
|
||||
v-else
|
||||
:style="dynamicStyle"
|
||||
ref="canvas"
|
||||
class="map-scene--image map-scene"
|
||||
/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, defineProps, inject, ref, watch } from 'vue';
|
||||
import {ReceiverSymbol} from '../../model/injectionSymbols';
|
||||
|
||||
const video = ref<HTMLVideoElement>();
|
||||
const image = ref<HTMLImageElement>();
|
||||
const canvas = ref<HTMLCanvasElement>();
|
||||
|
||||
const props = defineProps<{
|
||||
src?: string;
|
||||
width: number;
|
||||
dpi: number;
|
||||
position: {x: number, y: number};
|
||||
flipped: boolean;
|
||||
}>();
|
||||
|
||||
const isImage = computed(() => props.src?.endsWith('.png') || props.src?.endsWith('.jpg') || props.src?.endsWith('.jpeg') || props.src?.endsWith('.webp'));
|
||||
const isVideo = computed(() => props.src?.endsWith('.mp4') || props.src?.endsWith('.webm'));
|
||||
|
||||
const mediaDimensions = ref({width: 0, height: 0});
|
||||
const virtualMediaDimensions = ref({width: 0, height: 0});
|
||||
|
||||
const windowSize = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
};
|
||||
|
||||
watch(() => props.src, (src) => {
|
||||
if (isImage.value) {
|
||||
const img = new Image();
|
||||
img.src = src!;
|
||||
img.onload = () => {
|
||||
mediaDimensions.value = {
|
||||
width: img.naturalWidth,
|
||||
height: img.naturalHeight,
|
||||
};
|
||||
canvas.value!.width = img.naturalWidth;
|
||||
canvas.value!.height = img.naturalHeight;
|
||||
const ctx = canvas.value!.getContext('2d')!;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
};
|
||||
} else if (isVideo.value) {
|
||||
video.value!.src = src!;
|
||||
}
|
||||
});
|
||||
|
||||
const receiver = inject(ReceiverSymbol);
|
||||
receiver?.addEventListener('streamImage', ({detail}) => {
|
||||
if (canvas.value) {
|
||||
if (canvas.value.width !== detail.canvas.width || canvas.value.height !== detail.canvas.height) {
|
||||
const backup = document.createElement('canvas');
|
||||
backup.width = canvas.value.width;
|
||||
backup.height = canvas.value.height;
|
||||
const backupCtx = backup.getContext('2d')!;
|
||||
backupCtx.drawImage(canvas.value, 0, 0);
|
||||
canvas.value.width = detail.canvas.width;
|
||||
canvas.value.height = detail.canvas.height;
|
||||
const ctx = canvas.value.getContext('2d')!;
|
||||
ctx.drawImage(backup, 0, 0, detail.canvas.width, detail.canvas.height);
|
||||
}
|
||||
|
||||
const imageData = new ImageData(
|
||||
new Uint8ClampedArray(new Uint32Array(detail.imageData.data).buffer),
|
||||
detail.imageData.width,
|
||||
detail.imageData.height
|
||||
);
|
||||
const ctx = canvas.value.getContext('2d')!;
|
||||
ctx.putImageData(imageData, detail.x, detail.y);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const type = computed(() => {
|
||||
if (isImage.value) {
|
||||
return 'image';
|
||||
} else if (isVideo.value) {
|
||||
return 'video';
|
||||
} else {
|
||||
return 'unknown';
|
||||
}
|
||||
});
|
||||
|
||||
const dynamicStyle = computed(() => {
|
||||
const width = props.dpi * props.width;
|
||||
return {
|
||||
'--width': `${width}px`,
|
||||
'--height': `${(width) * (mediaDimensions.value.height / mediaDimensions.value.width)}px`,
|
||||
'--x': `${props.position.x/100}`,
|
||||
'--y': `${props.position.y/100}`,
|
||||
'--rotate': mediaDimensions.value.width > mediaDimensions.value.height ? (props.flipped ? '180deg' : '0deg') :
|
||||
(props.flipped ? '90deg' : '-90deg'),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
function onImageLoaded(event: Event) {
|
||||
console.log('image loaded', event.target);
|
||||
mediaDimensions.value = {
|
||||
width: image.value!.naturalWidth,
|
||||
height: image.value!.naturalHeight,
|
||||
};
|
||||
}
|
||||
|
||||
function onVideoLoaded(event: Event) {
|
||||
console.log('video loaded', event.target);
|
||||
mediaDimensions.value = {
|
||||
width: video.value!.videoWidth,
|
||||
height: video.value!.videoHeight,
|
||||
};
|
||||
}
|
||||
|
||||
function updateImageSize() {
|
||||
if (image.value) {
|
||||
image.value.width = props.width;
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style>
|
||||
.map-scene {
|
||||
position: absolute;
|
||||
left: calc(var(--x) * 100vw - var(--width) / 2);
|
||||
top: calc(var(--y) * 100vh - var(--height) / 2);
|
||||
width: var(--width);
|
||||
transform: rotate(var(--rotate));
|
||||
transform-origin: center;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user