Implement Fullscreen for mobile on photo Viewer

This commit is contained in:
2021-11-23 12:33:46 +01:00
parent 26634720d2
commit 35e8ea2d89
3 changed files with 150 additions and 24 deletions

View File

@@ -1,6 +1,9 @@
<script lang="ts"> <script lang="ts">
import { browser } from '$app/env' import { browser } from '$app/env'
import { page } from '$app/stores' import { page } from '$app/stores'
import { tick } from 'svelte'
import { scale } from 'svelte/transition'
import { quartOut } from 'svelte/easing'
import { getAssetUrlKey } from '$utils/helpers' import { getAssetUrlKey } from '$utils/helpers'
import { throttle } from '$utils/functions' import { throttle } from '$utils/functions'
import { swipe } from '$utils/interactions/swipe' import { swipe } from '$utils/interactions/swipe'
@@ -25,8 +28,10 @@
enum directions { PREV, NEXT } enum directions { PREV, NEXT }
let innerWidth: number let innerWidth: number
let fullscreenEl: HTMLElement
let globalOffset = offset let globalOffset = offset
let isLoading = false let isLoading = false
let isFullscreen = false
let hasNext = offset + limit < countPhotos let hasNext = offset + limit < countPhotos
let hasPrev = offset > 0 let hasPrev = offset > 0
@@ -61,10 +66,14 @@
* Photo navigation * Photo navigation
*/ */
// Go to next photo // Go to next photo
const goToNext = throttle(() => canGoPrev && currentIndex++, 200) const goToNext = throttle(() => {
canGoPrev && currentIndex++
}, 200)
// Fo to previous photo // Fo to previous photo
const goToPrevious = throttle(() => canGoNext && (currentIndex = Math.max(currentIndex - 1, 0)), 200) const goToPrevious = throttle(() => {
canGoNext && (currentIndex = Math.max(currentIndex - 1, 0))
}, 200)
// Enable navigation with keyboard // Enable navigation with keyboard
const handleKeydown = ({ key, defaultPrevented }: KeyboardEvent) => { const handleKeydown = ({ key, defaultPrevented }: KeyboardEvent) => {
@@ -95,6 +104,25 @@
} }
/**
* Fullscreen for mobile
*/
const toggleFullscreen = async () => {
if (innerWidth < 992) {
isFullscreen = !isFullscreen
// Scroll at middle of photo
if (isFullscreen) {
// Wait for fullscreen children to be mounted
await tick()
const picture = fullscreenEl.querySelector('picture')
const image = fullscreenEl.querySelector('img')
picture.scrollTo((image.offsetWidth - innerWidth) / 2, 0)
}
}
}
/** /**
* Load photos * Load photos
*/ */
@@ -188,8 +216,10 @@
<main class="viewer-photo"> <main class="viewer-photo">
<div class="container grid"> <div class="container grid">
<p class="viewer-photo__notice text-label">Tap for fullscreen</p>
<div class="viewer-photo__carousel"> <div class="viewer-photo__carousel">
<div class="viewer-photo__images" use:swipe on:swipe={handleSwipe}> <div class="viewer-photo__images" use:swipe on:swipe={handleSwipe} on:tap={toggleFullscreen}>
{#each visiblePhotos as photo, index (photo.id)} {#each visiblePhotos as photo, index (photo.id)}
<Image <Image
class="photo photo--{currentIndex === 0 ? index + 1 : index}" class="photo photo--{currentIndex === 0 ? index + 1 : index}"
@@ -238,6 +268,25 @@
</div> </div>
</div> </div>
</div> </div>
{#if isFullscreen}
<div class="viewer-photo__fullscreen" bind:this={fullscreenEl} on:click={toggleFullscreen}>
<div class="inner" transition:scale={{ easing: quartOut, start: 1.1, duration: 1000 }}>
<Image
id={currentPhoto.image.id}
sizeKey="photo-grid-large"
width={1266}
height={844}
alt={currentPhoto.title}
/>
<ButtonCircle color="gray-medium" class="close">
<svg width="18" height="18" viewBox="0 0 18 18" fill="#fff" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.751 0c4.274 0 7.752 3.477 7.752 7.751 0 1.846-.65 3.543-1.73 4.875l3.99 3.991a.81.81 0 1 1-1.146 1.146l-3.99-3.991a7.714 7.714 0 0 1-4.876 1.73C3.477 15.503 0 12.027 0 7.753 0 3.476 3.477 0 7.751 0Zm0 1.62a6.138 6.138 0 0 0-6.13 6.131 6.138 6.138 0 0 0 6.13 6.132 6.138 6.138 0 0 0 6.131-6.132c0-3.38-2.75-6.13-6.13-6.13Zm2.38 5.321a.81.81 0 1 1 0 1.62h-4.76a.81.81 0 1 1 0-1.62h4.76Z" />
</svg>
</ButtonCircle>
</div>
</div>
{/if}
</main> </main>

View File

@@ -4,12 +4,17 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 56px; width: 44px;
height: 56px; height: 44px;
background: #fff; background: #fff;
border-radius: 100vh; border-radius: 100vh;
transition: background-color 0.8s var(--ease-quart); transition: background-color 0.8s var(--ease-quart);
@include bp (md) {
width: 56px;
height: 56px;
}
& > * { & > * {
width: 22px; width: 22px;
height: 22px; height: 22px;
@@ -88,28 +93,33 @@
// Pink color // Pink color
&--pink { &--pink {
background-color: $color-secondary; background: $color-secondary;
&:hover { &:hover {
background-color: darken($color-secondary, 7); background: darken($color-secondary, 7);
} }
} }
// Purple color // Purple color
&--purple { &--purple {
background-color: $color-primary-tertiary20; background: $color-primary-tertiary20;
&:hover { &:hover {
background-color: $color-lightpurple; background: $color-lightpurple;
} }
} }
// Gray color // Gray color
&--gray { &--gray {
background-color: #F2F2F2; background: #F2F2F2;
&:hover { &:hover {
background-color: #D2D2D2; background: #D2D2D2;
} }
} }
// Gray color
&--gray-medium {
background: $color-gray;
}
} }

View File

@@ -1,10 +1,12 @@
.viewer-photo { .viewer-photo {
position: relative;
height: 100vh; height: 100vh;
display: flex; display: flex;
align-items: center; align-items: center;
overflow: hidden; overflow: hidden;
.container { .container {
position: relative;
height: 100%; height: 100%;
@include bp (md, max) { @include bp (md, max) {
@@ -14,21 +16,25 @@
// Carousel // Carousel
&__carousel { &__carousel {
display: grid; position: absolute;
grid-column: span var(--columns); top: 0;
grid-row-gap: 20px;
max-width: 720px;
margin: auto 0;
height: 100%;
position: relative;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translate3d(-50%, 0, 0);
grid-column: span var(--columns);
display: grid;
grid-row-gap: 20px;
width: calc(100% - 40px);
height: 100%;
max-width: 720px;
position: relative;
@include bp (md) { @include bp (md) {
position: relative;
max-width: none; max-width: none;
margin: auto 0; margin: auto 0;
grid-column: 3 / span 16; grid-column: 3 / span 16;
grid-row-gap: 40px; grid-row-gap: 40px;
transform: translate3d(-50%, 2.5%, 0);
} }
} }
@@ -181,12 +187,14 @@
// Infos // Infos
&__info { &__info {
bottom: 0;
margin-top: auto; margin-top: auto;
margin-bottom: 40px; margin-bottom: 40px;
padding: 0 20px; padding: 0 20px;
text-align: center; text-align: center;
@include bp (md) { @include bp (md) {
position: static;
margin-top: 0; margin-top: 0;
padding: 0; padding: 0;
text-align: left; text-align: left;
@@ -212,7 +220,7 @@
display: inline-block; display: inline-block;
align-items: center; align-items: center;
margin-top: 24px; margin-top: 24px;
color: $color-tertiary; color: rgba($color-tertiary, 0.7);
line-height: 1.5; line-height: 1.5;
@include bp (md) { @include bp (md) {
@@ -226,12 +234,12 @@
} }
a { a {
color: $color-tertiary; color: inherit;
text-decoration: none; text-decoration: none;
transition: color 0.3s; transition: color 0.3s;
&:hover { &:hover {
color: $color-secondary-light; color: $color-tertiary;
} }
} }
@@ -257,7 +265,7 @@
position: absolute; position: absolute;
z-index: 1; z-index: 1;
left: 50%; left: 50%;
bottom: calc(92% + 1vw); bottom: calc(91% + 1vw);
display: block; display: block;
line-height: 1; line-height: 1;
color: rgba($color-tertiary, 0.4); color: rgba($color-tertiary, 0.4);
@@ -271,12 +279,16 @@
} }
@include bp (md) { @include bp (md) {
top: 50%; top: 50%;
left: min(68vw, 1296px); left: auto;
right: calc(-1 * min(30vw, 400px));
width: 350px; width: 350px;
text-align: center; text-align: center;
bottom: auto; bottom: auto;
transform: translate3d(0, -50%, 0); transform: translate3d(0, -50%, 0);
} }
@include bp (lg) {
right: calc(-1 * min(25vw, 460px));
}
} }
// Controls // Controls
@@ -326,4 +338,59 @@
} }
} }
} }
// Fullscreen viewer
&__fullscreen {
position: absolute;
z-index: 101;
top: 0;
left: 0;
width: 100%;
height: 100%;
.inner {
width: 100%;
height: 100%;
}
picture {
width: 100%;
height: 100%;
overflow: auto;
cursor: pointer;
}
img {
display: block;
width: auto;
height: 100%;
object-fit: contain;
pointer-events: none;
user-select: none;
}
.close {
$color-shadow: rgba(#000, 0.15);
position: absolute;
z-index: 2;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
box-shadow:
0 6px 6px $color-shadow,
0 12px 12px $color-shadow,
0 24px 24px $color-shadow;
}
}
// Notice
&__notice {
position: absolute;
top: 16px;
left: 20px;
line-height: 44px;
color: rgba($color-tertiary, 0.5);
@include bp (md) {
display: none;
}
}
} }