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']
|
||||
StrapiContent: typeof import('./components/strapi/Content.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']
|
||||
StrapiMap: typeof import('./components/strapi/Map.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 {
|
||||
grid-template-columns: desktop-vw(40px) 1fr;
|
||||
display: grid;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
padding-top: desktop-vw(8px);
|
||||
border-top: 1px dashed currentColor;
|
||||
|
||||
.label {
|
||||
padding-top: 0.4em;
|
||||
}
|
||||
.content {
|
||||
p,
|
||||
a {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-if="global" class="layout">
|
||||
<site-header v-show="showHeader" :copy="headerCopy" :dl="headerDl" />
|
||||
<div v-if="global" class="layout" :style="{ '--header-height': headerHeight + 'px' }">
|
||||
<site-header v-show="showHeader" :copy="headerCopy" :dl="headerDl" ref="header" />
|
||||
|
||||
<client-only>
|
||||
<mosaic-scroll :items="mosaicItems" />
|
||||
|
|
@ -17,20 +17,26 @@
|
|||
</template async>
|
||||
|
||||
<script setup>
|
||||
import { computed} from 'vue';
|
||||
import { computed, ref} from 'vue';
|
||||
import { useSeoMeta } from '@unhead/vue'
|
||||
import { useStrapiGlobal } from '@/composables/useStrapi'
|
||||
import { useRoute } from 'vue-router';
|
||||
import useBreakpoints from '@/composables/useBreakpoints';
|
||||
import { useElementSize } from '@vueuse/core';
|
||||
|
||||
const header = ref()
|
||||
|
||||
const route = useRoute()
|
||||
const { data: global } = await useStrapiGlobal()
|
||||
const {isMobile} = useBreakpoints()
|
||||
const { isMobile } = useBreakpoints()
|
||||
const { height: headerHeight } = useElementSize(header)
|
||||
|
||||
|
||||
|
||||
const headerCopy = computed(() => global.value?.header_copy)
|
||||
const headerDl = computed(() => global.value?.header_dl)
|
||||
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
|
||||
const seo = computed(() => global.value?.seo)
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ const onClose = () => router.push('/')
|
|||
padding-top: desktop-vw(27px);
|
||||
|
||||
.meta {
|
||||
grid-column: 1 / span 2;
|
||||
grid-column: 2 / span 2;
|
||||
margin-top: var(--header-height);
|
||||
padding-top: var(--layout-column-gap);
|
||||
}
|
||||
|
||||
.window {
|
||||
|
|
@ -78,7 +80,7 @@ const onClose = () => router.push('/')
|
|||
}
|
||||
}
|
||||
&.content {
|
||||
grid-column: 3 / -2;
|
||||
grid-column: 4 / -2;
|
||||
|
||||
.lenis {
|
||||
max-height: desktop-vh(950px);
|
||||
|
|
@ -118,6 +120,8 @@ const onClose = () => router.push('/')
|
|||
display: grid;
|
||||
gap: var(--layout-column-gap);
|
||||
align-items: flex-start;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.window {
|
||||
border-radius: mobile-vw(10px);
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import _shuffle from 'lodash/shuffle'
|
||||
import { useMosaicLayout } from '@/composables/useMosaicLayout'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
|
@ -69,8 +69,11 @@ const generateMosaicContent = async (index) => {
|
|||
// Start/stop scroll when page is open
|
||||
watch(
|
||||
() => route.name,
|
||||
() => {
|
||||
async () => {
|
||||
if (route.name === 'mosaic-item') {
|
||||
if (!scrollContainer.value) {
|
||||
await nextTick()
|
||||
}
|
||||
scrollContainer.value?.stop?.()
|
||||
} else {
|
||||
scrollContainer.value?.start?.()
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ const props = defineProps({
|
|||
left: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
padding: desktop-vw(27px) var(--layout-margin);
|
||||
padding: desktop-vw(27px) var(--layout-margin) 0;
|
||||
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>
|
||||
import Copy from './Copy.vue'
|
||||
import Diptych from './Diptych.vue'
|
||||
import Media from './Media.vue'
|
||||
|
||||
const MAP = {
|
||||
'global.copy': Copy,
|
||||
'global.media': Media,
|
||||
'global.dipytch': Diptych,
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="image"
|
||||
:class="['strapi-media', { 'fill-space': fillSpace }, `fit-${fit}`]"
|
||||
:class="[
|
||||
'strapi-media',
|
||||
{ 'fill-space': fillSpace, 'can-play': canPlay },
|
||||
`fit-${fit}`,
|
||||
]"
|
||||
:style="styles"
|
||||
>
|
||||
<div class="image-sizer">
|
||||
|
|
@ -16,13 +20,19 @@
|
|||
decoding="async"
|
||||
/>
|
||||
|
||||
<button v-if="!shouldLoop" class="play-button" @click="onPlayClick">
|
||||
<svg-play />
|
||||
</button>
|
||||
|
||||
<video
|
||||
v-if="video?.url"
|
||||
:src="video.url"
|
||||
muted
|
||||
autoplay
|
||||
playsinline
|
||||
loop
|
||||
:muted="shouldLoop"
|
||||
:autoplay="shouldLoop"
|
||||
:playsinline="shouldLoop"
|
||||
:loop="shouldLoop"
|
||||
:controls="!shouldLoop"
|
||||
ref="videoRef"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -30,11 +40,15 @@
|
|||
|
||||
<script setup>
|
||||
import { breakpoints } from '@/libs/theme'
|
||||
import { computed } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
image: Object,
|
||||
video: Object,
|
||||
looping: {
|
||||
type: Boolean,
|
||||
default: () => true,
|
||||
},
|
||||
aspect: {
|
||||
type: [String, Number],
|
||||
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 ratio = computed(() => {
|
||||
if (!props.image) return 1
|
||||
|
|
@ -82,6 +100,11 @@ const sizes = computed(
|
|||
() =>
|
||||
`(max-width: ${breakpoints.mobile}) ${props.mobileSize}, ${props.desktopSize}`,
|
||||
)
|
||||
|
||||
const onPlayClick = () => {
|
||||
canPlay.value = true
|
||||
videoRef.value?.play()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
@ -94,7 +117,8 @@ const sizes = computed(
|
|||
overflow: hidden;
|
||||
padding-bottom: var(--aspect);
|
||||
|
||||
& > * {
|
||||
img,
|
||||
video {
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
|
|
@ -102,9 +126,17 @@ const sizes = computed(
|
|||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.svg {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
.play-button {
|
||||
position: absolute;
|
||||
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
|
||||
&.fit-cover .image-sizer > * {
|
||||
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