Implement Fullscreen for mobile on photo Viewer
This commit is contained in:
@@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user