Finish Photo viewer styling with responsive

This commit is contained in:
2021-10-25 15:45:12 +02:00
parent e830a18454
commit adc395b3f7
6 changed files with 249 additions and 80 deletions

View File

@@ -4,6 +4,7 @@
export let type: string = undefined export let type: string = undefined
export let form: string = undefined export let form: string = undefined
export let clone: boolean = false export let clone: boolean = false
export let disabled: boolean = undefined
const className = 'button-circle' const className = 'button-circle'
const classes = [ const classes = [
@@ -13,7 +14,7 @@
].join(' ').trim() ].join(' ').trim()
</script> </script>
<button {type} {form} class={classes} on:click> <button {type} {form} class={classes} on:click disabled={disabled}>
{#if clone} {#if clone}
{#each Array(2) as _} {#each Array(2) as _}
<span class="clone"> <span class="clone">

View File

@@ -3,6 +3,6 @@
export let flip: boolean = false export let flip: boolean = false
</script> </script>
<svg class="arrow arrow--{color}" class:arrow--flip={flip} width="12" height="14" viewBox="0 0 12 14" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg class="arrow arrow--{color}" class:arrow--flip={flip} width="12" height="14" viewBox="0 0 12 14" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.67961 11.8224C4.34487 12.1571 4.34487 12.6999 4.67961 13.0346C5.01434 13.3693 5.55705 13.3693 5.89179 13.0346L11.3204 7.60602C11.6551 7.27129 11.6551 6.72857 11.3204 6.39384L5.89179 0.965267C5.55705 0.630532 5.01434 0.630532 4.67961 0.965267C4.34487 1.3 4.34487 1.84271 4.67961 2.17745L8.64494 6.14279L1.2857 6.14279C0.812311 6.14279 0.428555 6.52654 0.428555 6.99993C0.428555 7.47332 0.812311 7.85707 1.2857 7.85707L8.64494 7.85707L4.67961 11.8224Z" fill="#000"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M4.67961 11.8224C4.34487 12.1571 4.34487 12.6999 4.67961 13.0346C5.01434 13.3693 5.55705 13.3693 5.89179 13.0346L11.3204 7.60602C11.6551 7.27129 11.6551 6.72857 11.3204 6.39384L5.89179 0.965267C5.55705 0.630532 5.01434 0.630532 4.67961 0.965267C4.34487 1.3 4.34487 1.84271 4.67961 2.17745L8.64494 6.14279L1.2857 6.14279C0.812311 6.14279 0.428555 6.52654 0.428555 6.99993C0.428555 7.47332 0.812311 7.85707 1.2857 7.85707L8.64494 7.85707L4.67961 11.8224Z"/>
</svg> </svg>

View File

@@ -2,80 +2,126 @@
import { page } from '$app/stores' import { page } from '$app/stores'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import advancedFormat from 'dayjs/plugin/advancedFormat.js' import advancedFormat from 'dayjs/plugin/advancedFormat.js'
import { getAssetUrlKey } from '$utils/helpers'
// Components // Components
import Metas from '$components/Metas.svelte'
import Image from '$components/atoms/Image.svelte' import Image from '$components/atoms/Image.svelte'
import IconArrow from '$components/atoms/IconArrow.svelte' import IconArrow from '$components/atoms/IconArrow.svelte'
import ButtonCircle from '$components/atoms/ButtonCircle.svelte' import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
export let photos: any[] export let photos: any[]
export let location: any export let location: any
export let currentIndex: number export let currentIndex: number
export let totalPhotos: number
dayjs.extend(advancedFormat) dayjs.extend(advancedFormat)
// let currentIndex: number = 0 // Define reactive last and first photos
$: isLast = currentIndex === totalPhotos - 1
// Find current photo from the slug $: isFirst = currentIndex === 0
$: currentPhoto = photos.find((photo: any) => photo.slug === $page.params.photo) // Define current photo
$: currentPhotoIndex = photos.findIndex((photo: any) => photo.slug === $page.params.photo) // $: currentPhoto = photos.find((photo: any) => photo.slug === $page.params.photo)
$: currentPhoto = photos[photos.findIndex((photo: any) => photo.slug === $page.params.photo)]
// Reactive photos showns
$: shownPhotos = [
...photos.filter((photo: any, index: number) => {
// Grab the 4 prev and next photos depending the index
console.log(index)
// console.log(index >= currentIndex - 4 && index < currentIndex + 4)
})
]
/** /**
* Go to next photo * Photo navigation
*/ */
// Go to next photo
const goToNext = () => { const goToNext = () => {
currentIndex++ if (!isLast) {
currentIndex++
}
// TODO: Fetch new photos
} }
// Fo to previous photo
/**
* Go to previous photo
*/
const goToPrevious = () => { const goToPrevious = () => {
currentIndex-- if (!isFirst) {
currentIndex--
}
// TODO: Fetch new photos
}
// Manage navigation with keyboard
const handleKeydown = ({ key, defaultPrevented }: KeyboardEvent) => {
if (defaultPrevented) return
switch (key) {
case 'ArrowLeft': goToNext(); break;
case 'ArrowRight': goToPrevious(); break;
default: return;
}
} }
/** /**
* Load photos * Load photos
*/ */
const loadPhotos = (index: number) => {
}
</script> </script>
<svelte:window on:keydown={handleKeydown} />
<main class="viewer-photo grid"> {#if currentPhoto}
<div class="viewer-photo__carousel"> <Metas
<div class="viewer-photo__images"> title="{currentPhoto.title} - Houses Of {location.name}"
<Image description=""
class="photo" image={getAssetUrlKey(currentPhoto.image.id, 'share')}
id={currentPhoto.image.id} />
alt={currentPhoto.title} {/if}
sizeKey="photo-list"
sizes={{
small: { width: 500 },
medium: { width: 850 },
large: { width: 1280 },
}}
ratio={1.5}
/>
<div class="viewer-photo__controls">
<ButtonCircle on:click={goToPrevious}> <main class="viewer-photo">
<IconArrow color="pink" flip={true} /> <div class="container grid">
</ButtonCircle> <div class="viewer-photo__carousel">
<ButtonCircle on:click={goToPrevious}> <div class="viewer-photo__images">
<IconArrow color="pink" /> {#each photos as photo}
</ButtonCircle> <Image
class="photo {photo.id === currentPhoto.id ? 'is-active' : ''}"
id={photo.image.id}
alt={photo.title}
sizeKey="photo-list"
sizes={{
small: { width: 500 },
medium: { width: 850 },
large: { width: 1280 },
}}
ratio={1.5}
/>
{/each}
<div class="viewer-photo__controls">
<ButtonCircle on:click={goToNext} disabled={isLast}>
<IconArrow color="pink" flip={true} />
</ButtonCircle>
<ButtonCircle on:click={goToPrevious} disabled={isFirst}>
<IconArrow color="pink" />
</ButtonCircle>
</div>
<span class="viewer-photo__index title-index">
{currentIndex + 1}
</span>
</div> </div>
<p class="viewer-photo__index title-index">{currentIndex + 1}</p> <div class="viewer-photo__info">
</div> <h1 class="title-medium">{currentPhoto.title}</h1>
<div class="detail text-date">
<div class="viewer-photo__info"> <img src="/images/icons/map-pin.svg" width={16} height={18} alt="Map icon"><span>{location.name}, {location.country.name}</span> <span class="sep">&middot;</span> <time datetime={dayjs(currentPhoto.date_taken).format('YYYY-MM-DD')}>{dayjs(currentPhoto.date_taken).format('MMMM, Do YYYY')}</time>
<h1 class="title-medium">{currentPhoto.title}</h1> </div>
<div class="detail text-date">
<img src="/images/icons/map-pin.svg" alt=""><span>{location.name}, {location.country.name}</span> <span class="sep">&middot;</span> <time datetime={dayjs(currentPhoto.date_taken).format('YYYY-MM-DD')}>{dayjs(currentPhoto.date_taken).format('MMMM, Do YYYY')}</time>
</div> </div>
</div> </div>
</div> </div>
@@ -91,8 +137,9 @@ import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
photos: photo ( photos: photo (
filter: { location: { slug: { _eq: "${page.params.location}" }}}, filter: { location: { slug: { _eq: "${page.params.location}" }}},
sort: "-date_created", sort: "-date_created",
limit: 9, limit: 5,
) { ) {
id
title title
slug slug
date_taken date_taken
@@ -111,15 +158,19 @@ import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
`) `)
const { data } = res const { data } = res
const location = stuff.locations.find((location: any) => location.slug === page.params.location)
const totalPhotos = stuff.countTotalPhotosByLocation.find((total: any) => total.group.location === Number(location.id)).count.id
// Find photo's index // Find photo's index
const currentIndex = data.photos.findIndex((photo: any) => photo.slug === page.params.photo) const currentPhotoIndex = data.photos.findIndex((photo: any) => photo.slug === page.params.photo)
const currentIndex = (totalPhotos - 1) - currentPhotoIndex
return { return {
props: { props: {
photos: data.photos, photos: data.photos,
location: data.location[0], location: data.location[0],
currentIndex, currentIndex,
totalPhotos,
} }
} }
} }

View File

@@ -1,18 +1,15 @@
.arrow { .arrow {
display: block; display: block;
// Colors
&--white { &--white {
path { color: #fff;
fill: #fff;
}
} }
&--pink { &--pink {
path { color: $color-secondary;
fill: $color-secondary;
}
} }
// Variants
&--flip { &--flip {
transform: rotate(180deg); transform: rotate(180deg);
} }

View File

@@ -14,6 +14,7 @@
width: 22px; width: 22px;
height: 22px; height: 22px;
object-fit: contain; object-fit: contain;
transition: opacity 0.4s var(--ease-quart), filter 0.4s var(--ease-quart);
} }
// Clones // Clones
@@ -35,6 +36,10 @@
} }
} }
/*
** States
*/
// Hover // Hover
&:hover { &:hover {
.clone { .clone {
@@ -49,6 +54,16 @@
} }
} }
// Disabled
&[disabled] {
background: $color-primary;
border: 3px solid #ffffff20;
svg {
fill: $color-primary-tertiary20;
}
}
/* /*
** Variants ** Variants

View File

@@ -1,37 +1,140 @@
.viewer-photo { .viewer-photo {
height: 100vh; height: 100vh;
display: flex;
align-items: center;
@include bp (md, max) { .container {
display: block; height: 100%;
@include bp (md, max) {
padding: 0 8px;
}
} }
// Carousel // Carousel
&__carousel { &__carousel {
display: grid; display: grid;
grid-column: span var(--columns);
grid-row-gap: 20px; grid-row-gap: 20px;
margin: auto 8px; max-width: 720px;
margin: auto 0;
height: 100%; height: 100%;
position: relative;
left: 50%;
transform: translateX(-50%);
@include bp (md) { @include bp (md) {
max-width: none;
margin: auto 0; margin: auto 0;
grid-column: 3 / span 16; grid-column: 3 / span 16;
grid-row-gap: 40px;
} }
} }
// Images // Images
&__images { &__images {
position: relative; position: relative;
margin: auto 0 0; width: 100%;
margin: auto auto 0;
padding-top: 66.66%;
.photo { .photo {
position: relative; --opacity: 1;
--scale: 1.0;
--rotate: 0deg;
--offset-x: -50%;
--offset-y: -50%;
position: absolute;
top: 50%;
left: 50%;
display: block;
width: 100%;
height: 100%;
z-index: 3; z-index: 3;
border-radius: 6px;
overflow: hidden; overflow: hidden;
box-shadow: transform: translate3d(var(--offset-x), var(--offset-y), 0) scale(var(--scale)) rotate(var(--rotate));
0 12px 12px rgba(#000, 0.15), transform-origin: top center;
0 20px 20px rgba(#000, 0.15), cursor: default;
0 48px 48px rgba(#000, 0.15); will-change: transform;
transition: opacity 1s var(--ease-quart), transform 1s var(--ease-quart);
@include bp (md) {
--offset-x: 0%;
--offset-y: -50%;
top: 50%;
left: 0;
transform-origin: bottom right;
}
img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
opacity: var(--opacity);
transform: translateZ(0);
}
&:nth-child(2) {
--scale: 0.9;
--opacity: 0.75;
--offset-y: -34%;
z-index: 7;
@include bp (md) {
--scale: 0.9;
--rotate: 1deg;
--offset-x: 4.5%;
--offset-y: -54%;
}
}
&:nth-child(3) {
--scale: 0.83;
--opacity: 0.55;
--offset-y: -22.5%;
z-index: 6;
@include bp (md) {
--scale: 0.83;
--offset-x: 7%;
--offset-y: -56.5%;
}
}
&:nth-child(4) {
--scale: 0.75;
--opacity: 0.45;
--offset-y: -11%;
z-index: 5;
@include bp (md) {
--scale: 0.75;
--rotate: 3deg;
--offset-x: 9%;
--offset-y: -59%;
}
}
&:nth-child(5) {
--scale: 0.68;
--opacity: 0.3;
--offset-y: -1.5%;
z-index: 4;
@include bp (md) {
--scale: 0.68;
--rotate: 4deg;
--offset-x: 10.5%;
--offset-y: -61.5%;
}
}
// Active state
&.is-active {
z-index: 8;
box-shadow:
0 12px 12px rgba(#000, 0.15),
0 20px 20px rgba(#000, 0.15),
0 48px 48px rgba(#000, 0.15);
}
} }
} }
@@ -43,7 +146,7 @@
text-align: center; text-align: center;
@include bp (md) { @include bp (md) {
margin-top: 26px; margin-top: 0;
padding: 0; padding: 0;
text-align: left; text-align: left;
} }
@@ -55,8 +158,12 @@
h1 { h1 {
color: $color-secondary; color: $color-secondary;
font-size: rem(32px); font-size: clamp(#{rem(20px)}, 6.5vw, #{rem(28px)});
line-height: 1.1; line-height: 1.1;
@include bp (md) {
font-size: rem(32px);
}
} }
// Details // Details
@@ -67,6 +174,9 @@
color: $color-tertiary; color: $color-tertiary;
line-height: 1.5; line-height: 1.5;
@include bp (md) {
margin-top: 16px;
}
@include bp (lg) { @include bp (lg) {
margin-top: 0; margin-top: 0;
margin-left: auto; margin-left: auto;
@@ -95,16 +205,17 @@
z-index: 1; z-index: 1;
left: 50%; left: 50%;
bottom: calc(92% + 1vw); bottom: calc(92% + 1vw);
display: block;
line-height: 1; line-height: 1;
transform: translate3d(-50%, 0, 0);
color: rgba($color-tertiary, 0.4); color: rgba($color-tertiary, 0.4);
transform: translate3d(-50%, 0, 0);
@include bp (md, max) { @include bp (md, max) {
font-size: clamp(#{rem(80px)}, 24vw, #{rem(120px)}); font-size: clamp(#{rem(80px)}, 24vw, #{rem(120px)});
} }
@include bp (md) { @include bp (md) {
top: 50%; top: 50%;
left: 95%; left: min(68vw, 1296px);
width: 350px; width: 350px;
text-align: center; text-align: center;
bottom: auto; bottom: auto;
@@ -125,17 +236,11 @@
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
justify-content: space-between; justify-content: space-between;
pointer-events: none;
} }
}
// Image button {
.photo { pointer-events: auto;
grid-column: 3 / span 16;
img {
width: 100%;
height: auto;
display: block;
} }
} }
} }