feat: add clicked effect on Photo viewer nav buttons + fade on name

This commit is contained in:
2024-08-03 14:42:53 +02:00
parent 0c8076097e
commit 8186e07335
2 changed files with 471 additions and 450 deletions

View File

@@ -224,14 +224,14 @@
*/ */
const animation = timeline([ const animation = timeline([
// First photo // First photo
['.photo-page__picture.is-1', { ['.picture.is-1', {
y: [24, 0], y: [24, 0],
opacity: [0, 1], opacity: [0, 1],
}, { }, {
duration: 0.9, duration: 0.9,
}], }],
// Other photos // Other photos
['.photo-page__picture:not(.is-1)', { ['.picture:not(.is-1)', {
x: ['-15%', '0%'], x: ['-15%', '0%'],
opacity: [0, 1], opacity: [0, 1],
}, { }, {
@@ -241,13 +241,13 @@
}], }],
// Prev/Next buttons // Prev/Next buttons
['.photo-page__controls .prev', { ['.controls .prev button', {
x: [-16, 0], x: [-16, 0],
opacity: [0, 1], opacity: [0, 1],
}, { }, {
at: 0.45, at: 0.45,
}], }],
['.photo-page__controls .next', { ['.controls .next button', {
x: [16, 0], x: [16, 0],
opacity: [0, 1], opacity: [0, 1],
}, { }, {
@@ -255,7 +255,7 @@
}], }],
// Infos // Infos
['.photo-page__info > *', { ['.info > *', {
y: [24, 0], y: [24, 0],
opacity: [0, 1], opacity: [0, 1],
}, { }, {
@@ -264,7 +264,7 @@
}], }],
// Index // Index
['.photo-page__index', { ['.index', {
opacity: [0, 1], opacity: [0, 1],
}, { }, {
at: 0.6, at: 0.6,
@@ -272,7 +272,7 @@
duration: 0.9, duration: 0.9,
}], }],
// Fly each number // Fly each number
['.photo-page__index .char', { ['.index .char', {
y: ['300%', 0], y: ['300%', 0],
}, { }, {
at: 1.1, at: 1.1,
@@ -306,7 +306,7 @@
<main class="photo-page"> <main class="photo-page">
<div class="container grid"> <div class="container grid">
<p class="photo-page__notice text-label">Tap for fullscreen</p> <p class="notice text-label">Tap for fullscreen</p>
<ButtonCircle <ButtonCircle
tag="a" tag="a"
@@ -320,15 +320,15 @@
</svg> </svg>
</ButtonCircle> </ButtonCircle>
<div class="photo-page__carousel"> <div class="carousel">
<div <div
class="photo-page__images" class="images"
use:swipe use:swipe
onswipe={handleSwipe} onswipe={handleSwipe}
ontap={toggleFullscreen} ontap={toggleFullscreen}
> >
{#each visiblePhotos as { id, image, title }, index (id)} {#each visiblePhotos as { id, image, title }, index (id)}
<div class="photo-page__picture is-{currentIndex === 0 ? index + 1 : index}"> <div class="picture is-{currentIndex === 0 ? index + 1 : index}">
<Image <Image
class="photo {image.width / image.height < 1.475 ? 'not-landscape' : ''}" class="photo {image.width / image.height < 1.475 ? 'not-landscape' : ''}"
id={image.id} id={image.id}
@@ -344,24 +344,32 @@
</div> </div>
{/each} {/each}
<div class="photo-page__controls"> <nav class="controls">
<ButtonCircle class="prev shadow-box-dark" label="Previous" disabled={!canGoNext} clone={true} onclick={goToPrevious}> <div class="prev" class:is-disabled={!canGoNext}>
<IconArrow color="pink" flip={true} /> <ButtonCircle class="shadow-box-dark" label="Previous" disabled={!canGoNext} clone={true} onclick={goToPrevious}>
</ButtonCircle> <IconArrow color="pink" flip={true} />
<ButtonCircle class="next shadow-box-dark" label="Next" disabled={!canGoPrev} clone={true} onclick={goToNext}> </ButtonCircle>
<IconArrow color="pink" /> </div>
</ButtonCircle> <div class="next" class:is-disabled={!canGoPrev}>
</div> <ButtonCircle class="shadow-box-dark" label="Next" disabled={!canGoPrev} clone={true} onclick={goToNext}>
<IconArrow color="pink" />
</ButtonCircle>
</div>
</nav>
<div class="photo-page__index title-index">
<div class="index title-index"> <div class="index title-index">
<SplitText text={padZero(currentPhotoIndex)} mode="chars" /> <SplitText text={padZero(currentPhotoIndex)} mode="chars" />
</div> </div>
</div> </div>
<div class="photo-page__info"> <div class="info">
<h1 class="title-medium">{currentPhoto.title}</h1> <div class="name">
{#key currentPhoto}
<h1 class="title-medium" in:fade={{ duration: 250, delay: 250 }} out:fade={{ duration: 250 }}>
{currentPhoto.title}
</h1>
{/key}
</div>
<div class="detail text-info"> <div class="detail text-info">
<a href="/{data.location.country.slug}/{data.location.slug}" data-sveltekit-noscroll> <a href="/{data.location.country.slug}/{data.location.slug}" data-sveltekit-noscroll>
@@ -386,7 +394,7 @@
{#if isFullscreen} {#if isFullscreen}
<div <div
bind:this={fullscreenEl} bind:this={fullscreenEl}
class="photo-page__fullscreen" class="fullscreen"
onclick={toggleFullscreen} onclick={toggleFullscreen}
in:fade={{ easing: quartOut, duration: 1000 }} in:fade={{ easing: quartOut, duration: 1000 }}
out:fade={{ easing: quartOut, duration: 1000, delay: 300 }} out:fade={{ easing: quartOut, duration: 1000, delay: 300 }}

View File

@@ -6,431 +6,6 @@
align-items: center; align-items: center;
overflow: hidden; overflow: hidden;
.container {
position: relative;
width: 100%;
height: 100%;
@include bp (md, max) {
padding: 0 8px;
}
}
// Carousel
&__carousel {
position: absolute;
top: 0;
left: 50%;
transform: translate3d(-50%, 0, 0);
grid-column: 1 / -1;
display: grid;
grid-row-gap: 20px;
width: calc(100% - 40px);
height: 100%;
max-width: 720px;
position: relative;
@include bp (md) {
position: relative;
max-width: none;
margin: auto 0;
grid-column: 2 / span 17;
grid-row-gap: 40px;
transform: translate3d(-50%, 2.5%, 0);
}
@include bp (sd) {
grid-column: 3 / span 16;
}
}
// Images
&__images {
position: relative;
width: 100%;
margin: auto auto 0;
padding-top: 66.66%;
touch-action: none;
}
&__picture {
--opacity: 1;
--scale: 0.6;
--rotate: 0deg;
--offset-x: 0%;
--offset-y: 0%;
position: absolute;
z-index: 8;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translateZ(0);
will-change: transform, opacity;
@include bp (md) {
--scale: 0.6;
--rotate: 5deg;
--offset-x: 28.5%;
--offset-y: 0%;
top: 0;
left: 0;
transform-origin: bottom right;
}
:global(.photo) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translate3d(var(--offset-x), var(--offset-y), 0) scale(var(--scale)) rotate(var(--rotate));
transition: opacity 1s var(--ease-quart), transform 1s var(--ease-quart);
will-change: transform;
box-shadow:
0 12px 12px rgba(#000, 0.15),
0 20px 20px rgba(#000, 0.15),
0 48px 48px rgba(#000, 0.15);
:global(picture) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: $color-primary;
cursor: default;
}
:global(img) {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
opacity: var(--opacity);
transform: translateZ(0);
pointer-events: none;
user-select: none;
transition: opacity 1s var(--ease-quart);
}
}
// Ratio is not landscape
:global(.not-landscape) {
:global(img) {
object-fit: contain;
}
}
// Hidden photo over
&.is-0 {
--scale: 1.03;
--rotate: 0deg;
--offset-x: 0%;
--offset-y: -7%;
z-index: 9;
pointer-events: none;
:global(.photo) {
opacity: 0;
}
@include bp (md) {
--scale: 1.075;
--rotate: -1deg;
--offset-x: -9%;
--offset-y: 0%;
}
}
// First visible photo
&.is-1 {
--scale: 1;
--rotate: 0deg;
--offset-y: 0%;
@include bp (md) {
--offset-x: 0%;
--offset-y: 0%;
}
}
&.is-2 {
--scale: 0.9;
--opacity: 0.75;
--offset-y: 12%;
z-index: 7;
@include bp (md) {
--scale: 0.9;
--rotate: 1deg;
--offset-x: 9.5%;
--offset-y: 0%;
}
}
&.is-3 {
--scale: 0.83;
--opacity: 0.55;
--offset-y: 20%;
z-index: 6;
@include bp (md) {
--scale: 0.83;
--rotate: 2deg;
--offset-x: 16.25%;
--offset-y: 0%;
}
}
&.is-4 {
--scale: 0.75;
--opacity: 0.45;
--offset-y: 27.5%;
z-index: 5;
@include bp (md) {
--scale: 0.75;
--rotate: 3deg;
--offset-x: 22%;
--offset-y: 0%;
}
}
&.is-5 {
--scale: 0.68;
--opacity: 0.25;
--offset-y: 33%;
z-index: 4;
@include bp (md) {
--scale: 0.68;
--rotate: 4deg;
--offset-x: 27%;
--offset-y: 0%;
}
}
&.is-6 {
--opacity: 0.25;
z-index: 3;
:global(.photo) {
opacity: 0;
}
}
&.is-7 {
:global(.photo) {
opacity: 0;
}
}
}
// Infos
&__info {
bottom: 0;
margin-top: auto;
margin-bottom: 40px;
padding: 0 20px;
text-align: center;
@include bp (md) {
position: static;
margin-top: 0;
padding: 0;
text-align: left;
}
@include bp (lg) {
display: grid;
grid-template-columns: 50% 50%;
align-items: baseline;
}
h1 {
color: $color-secondary;
font-size: clamp(#{rem(18px)}, 5.5vw, #{rem(28px)});
line-height: 1.1;
@include bp (md) {
font-size: rem(32px);
}
}
// Details
.detail {
display: inline-block;
align-items: center;
color: rgba($color-tertiary, 0.7);
line-height: 1.5;
@include bp (lg) {
margin-left: auto;
text-align: right;
padding-left: 12px;
}
a {
color: inherit;
text-decoration: none;
transition: color 0.3s;
&:hover {
color: $color-tertiary;
}
}
// Icon
:global(.icon) {
display: inline-block;
width: 17px;
height: 17px;
margin-top: -5px;
margin-right: 4px;
}
// Separator
.sep {
display: inline-block;
margin: 0 4px;
line-height: 1;
}
}
}
// Index
&__index {
position: absolute;
z-index: 1;
left: 50%;
bottom: calc(91% + 1vw);
display: block;
line-height: 1;
color: rgba($color-tertiary, 0.4);
transform: translate3d(-50%, 0, 0);
white-space: nowrap;
pointer-events: none;
user-select: none;
overflow: hidden;
@include bp (md, max) {
font-size: clamp(#{rem(80px)}, 24vw, #{rem(120px)});
}
@include bp (md) {
top: 50%;
left: auto;
right: calc(-1 * min(30vw, 400px));
width: 350px;
text-align: center;
bottom: auto;
transform: translate3d(0, -50%, 0);
}
@include bp (lg) {
right: calc(-1 * min(25vw, 460px));
}
}
// Controls
&__controls {
display: none;
@include bp (md) {
position: absolute;
z-index: 20;
display: flex;
left: -28px;
right: -28px;
top: 50%;
transform: translateY(-50%);
justify-content: space-between;
pointer-events: none;
}
:global(button) {
pointer-events: auto;
// Prev button
&:first-child {
& > :global(*:nth-child(2)) {
transform: translate3d(100%, -50%, 0) rotate(180deg);
}
// Hover
&:not([disabled]):hover {
& > :global(*:nth-child(1)) {
transform: translate3d(-20%, 0, 0) rotate(180deg);
}
& > :global(*:nth-child(2)) {
transform: translate3d(-50%, -50%, 0) rotate(180deg);
}
}
}
// Hover
&:not([disabled]):hover {
background-color: $color-secondary;
color: #fff;
:global(svg:nth-child(2)) {
color: #fff;
}
}
}
}
// Fullscreen viewer
&__fullscreen {
position: absolute;
z-index: 102;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: $color-primary-darker;
.inner {
width: 100%;
height: 100%;
}
// Photo
:global(picture) {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
overflow: auto;
cursor: pointer;
:global(img) {
display: block;
width: auto;
height: 100%;
object-fit: contain;
pointer-events: none;
user-select: none;
}
}
// Close
:global(.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;
}
}
// Close button // Close button
:global(.close) { :global(.close) {
--offset: 16px; --offset: 16px;
@@ -462,3 +37,441 @@
} }
} }
} }
.container {
position: relative;
width: 100%;
height: 100%;
@include bp (md, max) {
padding: 0 8px;
}
}
// Carousel
.carousel {
position: absolute;
top: 0;
left: 50%;
transform: translate3d(-50%, 0, 0);
grid-column: 1 / -1;
display: grid;
grid-row-gap: 20px;
width: calc(100% - 40px);
height: 100%;
max-width: 720px;
position: relative;
@include bp (md) {
position: relative;
max-width: none;
margin: auto 0;
grid-column: 2 / span 17;
grid-row-gap: 40px;
transform: translate3d(-50%, 2.5%, 0);
}
@include bp (sd) {
grid-column: 3 / span 16;
}
}
// Images
.images {
position: relative;
width: 100%;
margin: auto auto 0;
padding-top: 66.66%;
touch-action: none;
}
.picture {
--opacity: 1;
--scale: 0.6;
--rotate: 0deg;
--offset-x: 0%;
--offset-y: 0%;
position: absolute;
z-index: 8;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translateZ(0);
will-change: transform, opacity;
@include bp (md) {
--scale: 0.6;
--rotate: 5deg;
--offset-x: 28.5%;
--offset-y: 0%;
top: 0;
left: 0;
transform-origin: bottom right;
}
:global(.photo) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
transform: translate3d(var(--offset-x), var(--offset-y), 0) scale(var(--scale)) rotate(var(--rotate));
transition: opacity 1s var(--ease-quart), transform 1s var(--ease-quart);
will-change: transform;
box-shadow:
0 12px 12px rgba(#000, 0.15),
0 20px 20px rgba(#000, 0.15),
0 48px 48px rgba(#000, 0.15);
:global(picture) {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
background: $color-primary;
cursor: default;
}
:global(img) {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
opacity: var(--opacity);
transform: translateZ(0);
pointer-events: none;
user-select: none;
transition: opacity 1s var(--ease-quart);
}
}
// Ratio is not landscape
:global(.not-landscape) {
:global(img) {
object-fit: contain;
}
}
// Hidden photo over
&.is-0 {
--scale: 1.03;
--rotate: 0deg;
--offset-x: 0%;
--offset-y: -7%;
z-index: 9;
pointer-events: none;
:global(.photo) {
opacity: 0;
}
@include bp (md) {
--scale: 1.075;
--rotate: -1deg;
--offset-x: -9%;
--offset-y: 0%;
}
}
// First visible photo
&.is-1 {
--scale: 1;
--rotate: 0deg;
--offset-y: 0%;
@include bp (md) {
--offset-x: 0%;
--offset-y: 0%;
}
}
&.is-2 {
--scale: 0.9;
--opacity: 0.75;
--offset-y: 12%;
z-index: 7;
@include bp (md) {
--scale: 0.9;
--rotate: 1deg;
--offset-x: 9.5%;
--offset-y: 0%;
}
}
&.is-3 {
--scale: 0.83;
--opacity: 0.55;
--offset-y: 20%;
z-index: 6;
@include bp (md) {
--scale: 0.83;
--rotate: 2deg;
--offset-x: 16.25%;
--offset-y: 0%;
}
}
&.is-4 {
--scale: 0.75;
--opacity: 0.45;
--offset-y: 27.5%;
z-index: 5;
@include bp (md) {
--scale: 0.75;
--rotate: 3deg;
--offset-x: 22%;
--offset-y: 0%;
}
}
&.is-5 {
--scale: 0.68;
--opacity: 0.25;
--offset-y: 33%;
z-index: 4;
@include bp (md) {
--scale: 0.68;
--rotate: 4deg;
--offset-x: 27%;
--offset-y: 0%;
}
}
&.is-6 {
--opacity: 0.25;
z-index: 3;
:global(.photo) {
opacity: 0;
}
}
&.is-7 {
:global(.photo) {
opacity: 0;
}
}
}
// Infos
.info {
bottom: 0;
margin-top: auto;
margin-bottom: 40px;
padding: 0 20px;
text-align: center;
@include bp (md) {
position: static;
margin-top: 0;
padding: 0;
text-align: left;
}
@include bp (lg) {
display: grid;
grid-template-columns: 50% 50%;
align-items: baseline;
}
.name {
display: grid;
}
h1 {
grid-area: 1 / -1 / 1 / -1;
color: $color-secondary;
font-size: clamp(#{rem(18px)}, 5.5vw, #{rem(28px)});
line-height: 1.1;
@include bp (md) {
font-size: rem(32px);
}
}
// Details
.detail {
display: inline-block;
align-items: center;
color: rgba($color-tertiary, 0.7);
line-height: 1.5;
@include bp (lg) {
margin-left: auto;
text-align: right;
padding-left: 12px;
}
a {
color: inherit;
text-decoration: none;
transition: color 0.3s;
&:hover {
color: $color-tertiary;
}
}
// Icon
:global(.icon) {
display: inline-block;
width: 17px;
height: 17px;
margin-top: -5px;
margin-right: 4px;
}
// Separator
.sep {
display: inline-block;
margin: 0 4px;
line-height: 1;
}
}
}
// Index
.index {
position: absolute;
z-index: 1;
left: 50%;
bottom: calc(91% + 1vw);
display: block;
line-height: 1;
color: rgba($color-tertiary, 0.4);
transform: translate3d(-50%, 0, 0);
white-space: nowrap;
pointer-events: none;
user-select: none;
overflow: hidden;
@include bp (md, max) {
font-size: clamp(#{rem(80px)}, 24vw, #{rem(120px)});
}
@include bp (md) {
top: 50%;
left: auto;
right: calc(-1 * min(30vw, 400px));
width: 350px;
text-align: center;
bottom: auto;
transform: translate3d(0, -50%, 0);
}
@include bp (lg) {
right: calc(-1 * min(25vw, 460px));
}
}
// Controls
.controls {
display: none;
@include bp (md) {
position: absolute;
z-index: 20;
display: flex;
left: -28px;
right: -28px;
top: 50%;
transform: translateY(-50%);
justify-content: space-between;
pointer-events: none;
}
div {
transition: transform 0.3s var(--ease-quart);
pointer-events: auto;
// State: Shrink down when clicking
&:not(.is-disabled):active {
transform: scale(0.9);
}
}
// Prev button
.prev :global(button) {
:global(> *:nth-child(2)) {
transform: translate3d(100%, -50%, 0) rotate(180deg);
}
// Hover
&:not([disabled]):hover {
& > :global(*:nth-child(1)) {
transform: translate3d(-20%, 0, 0) rotate(180deg);
}
& > :global(*:nth-child(2)) {
transform: translate3d(-50%, -50%, 0) rotate(180deg);
}
}
}
// State: Hover
:global(button) {
// Common button styles
&:not([disabled]):hover {
background-color: $color-secondary;
color: #fff;
:global(svg:nth-child(2)) {
color: #fff;
}
}
}
}
// Fullscreen viewer
.fullscreen {
position: absolute;
z-index: 102;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: $color-primary-darker;
.inner {
width: 100%;
height: 100%;
}
// Photo
:global(picture) {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
overflow: auto;
cursor: pointer;
:global(img) {
display: block;
width: auto;
height: 100%;
object-fit: contain;
pointer-events: none;
user-select: none;
}
}
// Close
:global(.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;
}
}