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

View File

@@ -3,6 +3,6 @@
export let flip: boolean = false
</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">
<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"/>
<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"/>
</svg>

View File

@@ -2,80 +2,126 @@
import { page } from '$app/stores'
import dayjs from 'dayjs'
import advancedFormat from 'dayjs/plugin/advancedFormat.js'
import { getAssetUrlKey } from '$utils/helpers'
// Components
import Metas from '$components/Metas.svelte'
import Image from '$components/atoms/Image.svelte'
import IconArrow from '$components/atoms/IconArrow.svelte'
import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
import IconArrow from '$components/atoms/IconArrow.svelte'
import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
export let photos: any[]
export let location: any
export let currentIndex: number
export let totalPhotos: number
dayjs.extend(advancedFormat)
// let currentIndex: number = 0
// Find current photo from the slug
$: currentPhoto = photos.find((photo: any) => photo.slug === $page.params.photo)
$: currentPhotoIndex = photos.findIndex((photo: any) => photo.slug === $page.params.photo)
// Define reactive last and first photos
$: isLast = currentIndex === totalPhotos - 1
$: isFirst = currentIndex === 0
// Define current 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 = () => {
currentIndex++
if (!isLast) {
currentIndex++
}
// TODO: Fetch new photos
}
/**
* Go to previous photo
*/
// Fo to previous photo
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
*/
const loadPhotos = (index: number) => {
}
</script>
<svelte:window on:keydown={handleKeydown} />
<main class="viewer-photo grid">
<div class="viewer-photo__carousel">
<div class="viewer-photo__images">
<Image
class="photo"
id={currentPhoto.image.id}
alt={currentPhoto.title}
sizeKey="photo-list"
sizes={{
small: { width: 500 },
medium: { width: 850 },
large: { width: 1280 },
}}
ratio={1.5}
/>
{#if currentPhoto}
<Metas
title="{currentPhoto.title} - Houses Of {location.name}"
description=""
image={getAssetUrlKey(currentPhoto.image.id, 'share')}
/>
{/if}
<div class="viewer-photo__controls">
<ButtonCircle on:click={goToPrevious}>
<IconArrow color="pink" flip={true} />
</ButtonCircle>
<ButtonCircle on:click={goToPrevious}>
<IconArrow color="pink" />
</ButtonCircle>
<main class="viewer-photo">
<div class="container grid">
<div class="viewer-photo__carousel">
<div class="viewer-photo__images">
{#each photos as photo}
<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>
<p class="viewer-photo__index title-index">{currentIndex + 1}</p>
</div>
<div class="viewer-photo__info">
<h1 class="title-medium">{currentPhoto.title}</h1>
<div class="viewer-photo__info">
<h1 class="title-medium">{currentPhoto.title}</h1>
<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 class="detail text-date">
<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>
</div>
</div>
</div>
</div>
@@ -91,8 +137,9 @@ import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
photos: photo (
filter: { location: { slug: { _eq: "${page.params.location}" }}},
sort: "-date_created",
limit: 9,
limit: 5,
) {
id
title
slug
date_taken
@@ -111,15 +158,19 @@ import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
`)
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
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 {
props: {
photos: data.photos,
location: data.location[0],
currentIndex,
totalPhotos,
}
}
}

View File

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

View File

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

View File

@@ -1,37 +1,140 @@
.viewer-photo {
height: 100vh;
display: flex;
align-items: center;
@include bp (md, max) {
display: block;
.container {
height: 100%;
@include bp (md, max) {
padding: 0 8px;
}
}
// Carousel
&__carousel {
display: grid;
grid-column: span var(--columns);
grid-row-gap: 20px;
margin: auto 8px;
max-width: 720px;
margin: auto 0;
height: 100%;
position: relative;
left: 50%;
transform: translateX(-50%);
@include bp (md) {
max-width: none;
margin: auto 0;
grid-column: 3 / span 16;
grid-row-gap: 40px;
}
}
// Images
&__images {
position: relative;
margin: auto 0 0;
width: 100%;
margin: auto auto 0;
padding-top: 66.66%;
.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;
border-radius: 6px;
overflow: hidden;
box-shadow:
0 12px 12px rgba(#000, 0.15),
0 20px 20px rgba(#000, 0.15),
0 48px 48px rgba(#000, 0.15);
transform: translate3d(var(--offset-x), var(--offset-y), 0) scale(var(--scale)) rotate(var(--rotate));
transform-origin: top center;
cursor: default;
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;
@include bp (md) {
margin-top: 26px;
margin-top: 0;
padding: 0;
text-align: left;
}
@@ -55,8 +158,12 @@
h1 {
color: $color-secondary;
font-size: rem(32px);
font-size: clamp(#{rem(20px)}, 6.5vw, #{rem(28px)});
line-height: 1.1;
@include bp (md) {
font-size: rem(32px);
}
}
// Details
@@ -67,6 +174,9 @@
color: $color-tertiary;
line-height: 1.5;
@include bp (md) {
margin-top: 16px;
}
@include bp (lg) {
margin-top: 0;
margin-left: auto;
@@ -95,16 +205,17 @@
z-index: 1;
left: 50%;
bottom: calc(92% + 1vw);
display: block;
line-height: 1;
transform: translate3d(-50%, 0, 0);
color: rgba($color-tertiary, 0.4);
transform: translate3d(-50%, 0, 0);
@include bp (md, max) {
font-size: clamp(#{rem(80px)}, 24vw, #{rem(120px)});
}
@include bp (md) {
top: 50%;
left: 95%;
left: min(68vw, 1296px);
width: 350px;
text-align: center;
bottom: auto;
@@ -125,17 +236,11 @@
top: 50%;
transform: translateY(-50%);
justify-content: space-between;
pointer-events: none;
}
}
// Image
.photo {
grid-column: 3 / span 16;
img {
width: 100%;
height: auto;
display: block;
button {
pointer-events: auto;
}
}
}