layout + non looping video
This commit is contained in:
parent
dbb93ba802
commit
1894e37605
10 changed files with 111 additions and 21 deletions
2
src/components.d.ts
vendored
2
src/components.d.ts
vendored
|
|
@ -24,8 +24,10 @@ declare module 'vue' {
|
||||||
SiteHeader: typeof import('./components/site/Header.vue')['default']
|
SiteHeader: typeof import('./components/site/Header.vue')['default']
|
||||||
StrapiContent: typeof import('./components/strapi/Content.vue')['default']
|
StrapiContent: typeof import('./components/strapi/Content.vue')['default']
|
||||||
StrapiCopy: typeof import('./components/strapi/Copy.vue')['default']
|
StrapiCopy: typeof import('./components/strapi/Copy.vue')['default']
|
||||||
|
StrapiDiptych: typeof import('./components/strapi/Diptych.vue')['default']
|
||||||
StrapiLink: typeof import('./components/strapi/Link.vue')['default']
|
StrapiLink: typeof import('./components/strapi/Link.vue')['default']
|
||||||
StrapiMap: typeof import('./components/strapi/Map.vue')['default']
|
StrapiMap: typeof import('./components/strapi/Map.vue')['default']
|
||||||
StrapiMedia: typeof import('./components/strapi/Media.vue')['default']
|
StrapiMedia: typeof import('./components/strapi/Media.vue')['default']
|
||||||
|
SvgPlay: typeof import('./components/svg/Play.vue')['default']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,13 @@ const props = defineProps({ row: Object })
|
||||||
.dl-row {
|
.dl-row {
|
||||||
grid-template-columns: desktop-vw(40px) 1fr;
|
grid-template-columns: desktop-vw(40px) 1fr;
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
padding-top: desktop-vw(8px);
|
padding-top: desktop-vw(8px);
|
||||||
border-top: 1px dashed currentColor;
|
border-top: 1px dashed currentColor;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
padding-top: 0.4em;
|
||||||
|
}
|
||||||
.content {
|
.content {
|
||||||
p,
|
p,
|
||||||
a {
|
a {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="global" class="layout">
|
<div v-if="global" class="layout" :style="{ '--header-height': headerHeight + 'px' }">
|
||||||
<site-header v-show="showHeader" :copy="headerCopy" :dl="headerDl" />
|
<site-header v-show="showHeader" :copy="headerCopy" :dl="headerDl" ref="header" />
|
||||||
|
|
||||||
<client-only>
|
<client-only>
|
||||||
<mosaic-scroll :items="mosaicItems" />
|
<mosaic-scroll :items="mosaicItems" />
|
||||||
|
|
@ -17,20 +17,26 @@
|
||||||
</template async>
|
</template async>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed} from 'vue';
|
import { computed, ref} from 'vue';
|
||||||
import { useSeoMeta } from '@unhead/vue'
|
import { useSeoMeta } from '@unhead/vue'
|
||||||
import { useStrapiGlobal } from '@/composables/useStrapi'
|
import { useStrapiGlobal } from '@/composables/useStrapi'
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import useBreakpoints from '@/composables/useBreakpoints';
|
import useBreakpoints from '@/composables/useBreakpoints';
|
||||||
|
import { useElementSize } from '@vueuse/core';
|
||||||
|
|
||||||
|
const header = ref()
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const { data: global } = await useStrapiGlobal()
|
const { data: global } = await useStrapiGlobal()
|
||||||
const {isMobile} = useBreakpoints()
|
const { isMobile } = useBreakpoints()
|
||||||
|
const { height: headerHeight } = useElementSize(header)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const headerCopy = computed(() => global.value?.header_copy)
|
const headerCopy = computed(() => global.value?.header_copy)
|
||||||
const headerDl = computed(() => global.value?.header_dl)
|
const headerDl = computed(() => global.value?.header_dl)
|
||||||
const mosaicItems = computed(() => global.value?.mosaic_items || [])
|
const mosaicItems = computed(() => global.value?.mosaic_items || [])
|
||||||
const showHeader = computed(() => !(isMobile && route.name === 'mosaic-item'))
|
const showHeader = computed(() => !(route.name === 'mosaic-item' && isMobile.value))
|
||||||
|
|
||||||
// SEO
|
// SEO
|
||||||
const seo = computed(() => global.value?.seo)
|
const seo = computed(() => global.value?.seo)
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,9 @@ const onClose = () => router.push('/')
|
||||||
padding-top: desktop-vw(27px);
|
padding-top: desktop-vw(27px);
|
||||||
|
|
||||||
.meta {
|
.meta {
|
||||||
grid-column: 1 / span 2;
|
grid-column: 2 / span 2;
|
||||||
|
margin-top: var(--header-height);
|
||||||
|
padding-top: var(--layout-column-gap);
|
||||||
}
|
}
|
||||||
|
|
||||||
.window {
|
.window {
|
||||||
|
|
@ -78,7 +80,7 @@ const onClose = () => router.push('/')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.content {
|
&.content {
|
||||||
grid-column: 3 / -2;
|
grid-column: 4 / -2;
|
||||||
|
|
||||||
.lenis {
|
.lenis {
|
||||||
max-height: desktop-vh(950px);
|
max-height: desktop-vh(950px);
|
||||||
|
|
@ -118,6 +120,8 @@ const onClose = () => router.push('/')
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--layout-column-gap);
|
gap: var(--layout-column-gap);
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
.window {
|
.window {
|
||||||
border-radius: mobile-vw(10px);
|
border-radius: mobile-vw(10px);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch } from 'vue'
|
import { nextTick, ref, watch } from 'vue'
|
||||||
import _shuffle from 'lodash/shuffle'
|
import _shuffle from 'lodash/shuffle'
|
||||||
import { useMosaicLayout } from '@/composables/useMosaicLayout'
|
import { useMosaicLayout } from '@/composables/useMosaicLayout'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
|
|
@ -69,8 +69,11 @@ const generateMosaicContent = async (index) => {
|
||||||
// Start/stop scroll when page is open
|
// Start/stop scroll when page is open
|
||||||
watch(
|
watch(
|
||||||
() => route.name,
|
() => route.name,
|
||||||
() => {
|
async () => {
|
||||||
if (route.name === 'mosaic-item') {
|
if (route.name === 'mosaic-item') {
|
||||||
|
if (!scrollContainer.value) {
|
||||||
|
await nextTick()
|
||||||
|
}
|
||||||
scrollContainer.value?.stop?.()
|
scrollContainer.value?.stop?.()
|
||||||
} else {
|
} else {
|
||||||
scrollContainer.value?.start?.()
|
scrollContainer.value?.start?.()
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ const props = defineProps({
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
padding: desktop-vw(27px) var(--layout-margin);
|
padding: desktop-vw(27px) var(--layout-margin) 0;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
|
|
|
||||||
21
src/components/strapi/Diptych.vue
Normal file
21
src/components/strapi/Diptych.vue
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<section class="strapi-diptych">
|
||||||
|
<strapi-media v-bind="props.left_media" />
|
||||||
|
<strapi-media v-bind="props.right_media" />
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const props = defineProps({
|
||||||
|
left_media: Object,
|
||||||
|
right_media: Object,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.strapi-diptych {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
display: grid;
|
||||||
|
gap: var(--layout-column-gap);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -12,11 +12,13 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import Copy from './Copy.vue'
|
import Copy from './Copy.vue'
|
||||||
|
import Diptych from './Diptych.vue'
|
||||||
import Media from './Media.vue'
|
import Media from './Media.vue'
|
||||||
|
|
||||||
const MAP = {
|
const MAP = {
|
||||||
'global.copy': Copy,
|
'global.copy': Copy,
|
||||||
'global.media': Media,
|
'global.media': Media,
|
||||||
|
'global.dipytch': Diptych,
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="image"
|
v-if="image"
|
||||||
:class="['strapi-media', { 'fill-space': fillSpace }, `fit-${fit}`]"
|
:class="[
|
||||||
|
'strapi-media',
|
||||||
|
{ 'fill-space': fillSpace, 'can-play': canPlay },
|
||||||
|
`fit-${fit}`,
|
||||||
|
]"
|
||||||
:style="styles"
|
:style="styles"
|
||||||
>
|
>
|
||||||
<div class="image-sizer">
|
<div class="image-sizer">
|
||||||
|
|
@ -16,13 +20,19 @@
|
||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<button v-if="!shouldLoop" class="play-button" @click="onPlayClick">
|
||||||
|
<svg-play />
|
||||||
|
</button>
|
||||||
|
|
||||||
<video
|
<video
|
||||||
v-if="video?.url"
|
v-if="video?.url"
|
||||||
:src="video.url"
|
:src="video.url"
|
||||||
muted
|
:muted="shouldLoop"
|
||||||
autoplay
|
:autoplay="shouldLoop"
|
||||||
playsinline
|
:playsinline="shouldLoop"
|
||||||
loop
|
:loop="shouldLoop"
|
||||||
|
:controls="!shouldLoop"
|
||||||
|
ref="videoRef"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -30,11 +40,15 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { breakpoints } from '@/libs/theme'
|
import { breakpoints } from '@/libs/theme'
|
||||||
import { computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
image: Object,
|
image: Object,
|
||||||
video: Object,
|
video: Object,
|
||||||
|
looping: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => true,
|
||||||
|
},
|
||||||
aspect: {
|
aspect: {
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
default: () => -1,
|
default: () => -1,
|
||||||
|
|
@ -57,6 +71,10 @@ const props = defineProps({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const videoRef = ref()
|
||||||
|
const canPlay = ref(props.looping !== false)
|
||||||
|
|
||||||
|
const shouldLoop = computed(() => props.looping !== false)
|
||||||
const formats = computed(() => props.image?.formats || {})
|
const formats = computed(() => props.image?.formats || {})
|
||||||
const ratio = computed(() => {
|
const ratio = computed(() => {
|
||||||
if (!props.image) return 1
|
if (!props.image) return 1
|
||||||
|
|
@ -82,6 +100,11 @@ const sizes = computed(
|
||||||
() =>
|
() =>
|
||||||
`(max-width: ${breakpoints.mobile}) ${props.mobileSize}, ${props.desktopSize}`,
|
`(max-width: ${breakpoints.mobile}) ${props.mobileSize}, ${props.desktopSize}`,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const onPlayClick = () => {
|
||||||
|
canPlay.value = true
|
||||||
|
videoRef.value?.play()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
|
@ -94,7 +117,8 @@ const sizes = computed(
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding-bottom: var(--aspect);
|
padding-bottom: var(--aspect);
|
||||||
|
|
||||||
& > * {
|
img,
|
||||||
|
video {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
@ -102,9 +126,17 @@ const sizes = computed(
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
.svg {
|
.play-button {
|
||||||
background-size: cover;
|
position: absolute;
|
||||||
background-repeat: no-repeat;
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 4rem;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,6 +158,11 @@ const sizes = computed(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:not(.can-play) video {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
// fits
|
// fits
|
||||||
&.fit-cover .image-sizer > * {
|
&.fit-cover .image-sizer > * {
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
|
|
||||||
12
src/components/svg/Play.vue
Normal file
12
src/components/svg/Play.vue
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
class="svg-play"
|
||||||
|
width="15"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 15 16"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M0 16V0L14.607 8L0 16Z" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
Loading…
Reference in a new issue