139 lines
3.1 KiB
Vue
139 lines
3.1 KiB
Vue
<template>
|
|
<div
|
|
v-if="image"
|
|
:class="['strapi-media', { 'fill-space': fillSpace }, `fit-${fit}`]"
|
|
:style="styles"
|
|
>
|
|
<div class="image-sizer">
|
|
<img
|
|
:src="addURLBase(image.url)"
|
|
:srcset="srcset"
|
|
:sizes="sizes"
|
|
:alt="image.alternativeText || ''"
|
|
:width="image.width"
|
|
:height="image.height"
|
|
loading="lazy"
|
|
decoding="async"
|
|
/>
|
|
|
|
<video
|
|
v-if="video?.url"
|
|
:src="addURLBase(video.url)"
|
|
muted
|
|
autoplay
|
|
playsinline
|
|
loop
|
|
/>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { breakpoints } from '@/libs/theme'
|
|
import { computed } from 'vue'
|
|
|
|
const props = defineProps({
|
|
image: Object,
|
|
video: Object,
|
|
aspect: {
|
|
type: [String, Number],
|
|
default: () => -1,
|
|
},
|
|
fillSpace: {
|
|
type: Boolean,
|
|
default: () => false,
|
|
},
|
|
fit: {
|
|
type: String,
|
|
default: () => 'cover',
|
|
},
|
|
mobileSize: {
|
|
type: String,
|
|
default: () => '100vw',
|
|
},
|
|
desktopSize: {
|
|
type: String,
|
|
default: () => '100vw',
|
|
},
|
|
})
|
|
|
|
const addURLBase = (path) => `${import.meta.env.VITE_STRAPI_URL}${path}`
|
|
|
|
const formats = computed(() => props.image?.formats || {})
|
|
const ratio = computed(() => {
|
|
if (!props.image) return 1
|
|
return props.image.height / props.image.width
|
|
})
|
|
const aspect = computed(() => {
|
|
// calculate if no aspect provided
|
|
if (props.aspect === -1) {
|
|
return ratio.value * 100
|
|
}
|
|
|
|
// otherwise, parse provided aspect, handling both 56.25 and 0.5625 style
|
|
const toParse = parseFloat(props.aspect)
|
|
return toParse <= 1 ? toParse * 100 : toParse
|
|
})
|
|
const styles = computed(() => ({ '--aspect': aspect.value + '%' }))
|
|
const srcset = computed(() =>
|
|
Object.values(formats.value)
|
|
.map((f) => `${addURLBase(f.url)} ${f.width}w`)
|
|
.join(', '),
|
|
)
|
|
const sizes = computed(
|
|
() =>
|
|
`(max-width: ${breakpoints.mobile}) ${props.mobileSize}, ${props.desktopSize}`,
|
|
)
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.strapi-media {
|
|
position: relative;
|
|
width: 100%;
|
|
overflow: hidden;
|
|
|
|
.image-sizer {
|
|
overflow: hidden;
|
|
padding-bottom: var(--aspect);
|
|
|
|
& > * {
|
|
display: block;
|
|
position: absolute;
|
|
height: 100%;
|
|
width: 100%;
|
|
left: 0;
|
|
top: 0;
|
|
}
|
|
.svg {
|
|
background-size: cover;
|
|
background-repeat: no-repeat;
|
|
}
|
|
}
|
|
|
|
// fill space
|
|
&.fill-space {
|
|
position: absolute;
|
|
bottom: 0;
|
|
right: 0;
|
|
left: 0;
|
|
top: 0;
|
|
|
|
.image-sizer {
|
|
padding-bottom: 0;
|
|
position: absolute;
|
|
top: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
left: 0;
|
|
}
|
|
}
|
|
|
|
// fits
|
|
&.fit-cover .image-sizer > * {
|
|
object-fit: cover;
|
|
}
|
|
&.fit-contain .image-sizer > * {
|
|
object-fit: contain;
|
|
}
|
|
}
|
|
</style>
|