Create a functioning carousel

Thanks a million to Grafikart!
This commit is contained in:
2020-02-17 22:33:44 +01:00
parent 6551aa738a
commit 6fb6937c1a
4 changed files with 172 additions and 77 deletions

View File

@@ -2,15 +2,75 @@
import { onMount } from 'svelte' import { onMount } from 'svelte'
import * as fn from '../functions' import * as fn from '../functions'
// Dependencies
import dayjs from 'dayjs'
import advancedFormat from 'dayjs/plugin/advancedFormat'
dayjs.extend(advancedFormat)
// Components // Components
import IconArrow from '../atoms/IconArrow' import IconArrow from '../atoms/IconArrow'
// Props and variables // Props
export let viewer = false export let viewer = false
export let photos = [] export let photos = []
// Variables
let hover let hover
let photoPrev let currentIndex = 1
let photoNext $: currentPhoto = photos[currentIndex] || null
$: prevPhoto = photos[currentIndex - 1] || photos[photos.length - 1]
$: nextPhoto = photos[currentIndex + 1] || photos[0]
/*
** Go to a photo
*/
const goToPrev = () => {
currentIndex--
currentIndex = (currentIndex < 0) ? photos.length - 1 : currentIndex
}
const goToNext = () => {
currentIndex++
currentIndex = (currentIndex >= photos.length) ? 0 : currentIndex
}
/*
** Drag and touch navigation
*/
let touchStartX = 0
let touchStartY = 0
let touchEndX = 0
let touchEndY = 0
const swipeStart = event => {
touchStartX = event.changedTouches[0].screenX
touchStartY = event.changedTouches[0].screenY
}
const swipeEnd = event => {
touchEndX = event.changedTouches[0].screenX
touchEndY = event.changedTouches[0].screenY
if (touchEndX <= touchStartX) goToNext()
if (touchEndX >= touchStartX) goToPrev()
}
// Keyboard navigation
const keyboardNav = event => {
const keyCode = event.keyCode
switch (event.keyCode) {
case 37: case 80: case 74:
goToPrev()
break
case 39: case 78: case 75:
goToNext()
break
// case 27: case 67:
// document.getElementById('photo_close').click()
// break
default: break
}
}
/* /*
@@ -19,10 +79,15 @@
onMount(() => { onMount(() => {
// Hover function // Hover function
hover = event => { hover = event => {
const to = event.currentTarget.dataset.to
const button = event.currentTarget.querySelector('button') const button = event.currentTarget.querySelector('button')
const photoActive = document.querySelector('.gallery .active') const photoActive = document.querySelector('.gallery__images .active')
const photoToHover = (to === 'prev') ? photoActive.previousSibling : photoActive.nextSibling
let photoToHover
if (event.currentTarget.dataset.to === 'prev') {
photoToHover = (photoActive.previousSibling != null) ? photoActive.previousSibling : photoActive.parentNode.lastChild
} else {
photoToHover = (photoActive.nextSibling != null) ? photoActive.nextSibling : photoActive.parentNode.firstChild
}
switch (event.type) { switch (event.type) {
case 'mouseenter': case 'mouseenter':
@@ -36,29 +101,30 @@
default: break default: break
} }
} }
// Go to previous photo
photoPrev = () => {
console.log('previous photo')
}
// Go to next photo
photoNext = () => {
console.log('next photo')
}
}) })
/*
REVEAL ANIMATION ON CAROUSEL:
Fix the CSS priority? Cancel the transition delay and duration?
data-aos="{
(photo === prevPhoto) ? 'carousel-prev' :
(photo === currentPhoto) ? 'carousel-active' :
(photo === nextPhoto) ? 'carousel-next' : ''
}
*/
</script> </script>
<div class="carousel"> <svelte:window on:keydown={keyboardNav} />
<div class="carousel" on:touchstart={swipeStart} on:touchend={swipeEnd}>
<div class="wrap"> <div class="wrap">
<div class="gallery"> <div class="gallery">
<div class="gallery__images"> <div class="gallery__images">
{#each photos as photo, index} {#each photos as photo, index}
<picture class="gallery__images--photo" <picture class="gallery__images--photo"
class:prev={index === 0} class:prev={photo === prevPhoto}
class:active={index === 1} class:active={photo === currentPhoto}
class:next={index === 2} class:next={photo === nextPhoto}
> >
<source media="(min-width: 968px)" srcset={fn.getThumbnail(photo.image.private_hash, 1400)}> <source media="(min-width: 968px)" srcset={fn.getThumbnail(photo.image.private_hash, 1400)}>
<source media="(min-width: 800px)" srcset={fn.getThumbnail(photo.image.private_hash, 900)}> <source media="(min-width: 800px)" srcset={fn.getThumbnail(photo.image.private_hash, 900)}>
@@ -70,14 +136,14 @@
</div> </div>
<div class="carousel__controls"> <div class="carousel__controls">
<div class="carousel__controls--area prev" data-to="prev" on:mouseenter={hover} on:mouseleave={hover} on:click={photoPrev}> <div class="carousel__controls--area prev" data-to="prev" on:mouseenter={hover} on:mouseleave={hover} on:click={goToPrev}>
<button class="button-control button-control--white dir-left" aria-label="Previous"> <button class="button-control button-control--white dir-left" aria-label="Previous">
<IconArrow direction="left" color="#ff6c89" className="icon" /> <IconArrow direction="left" color="#ff6c89" className="icon" />
<IconArrow direction="left" color="#fff" className="icon" hidden="true" /> <IconArrow direction="left" color="#fff" className="icon" hidden="true" />
</button> </button>
</div> </div>
<div class="carousel__controls--area next" data-to="next" on:mouseenter={hover} on:mouseleave={hover} on:click={photoNext}> <div class="carousel__controls--area next" data-to="next" on:mouseenter={hover} on:mouseleave={hover} on:click={goToNext}>
<button class="button-control button-control--white dir-right" aria-label="Next"> <button class="button-control button-control--white dir-right" aria-label="Next">
<IconArrow direction="right" color="#ff6c89" className="icon" /> <IconArrow direction="right" color="#ff6c89" className="icon" />
<IconArrow direction="right" color="#fff" className="icon" hidden="true" /> <IconArrow direction="right" color="#fff" className="icon" hidden="true" />
@@ -86,18 +152,18 @@
</div> </div>
{#if viewer} {#if viewer}
<div class="carousel__number">05</div> <div class="carousel__number">{currentIndex < 10 ? 0 : ''}{currentIndex + 1}</div>
{/if} {/if}
</div> </div>
<div class="carousel__information"> <div class="carousel__information">
<div class="carousel__information--location style-location"> <div class="carousel__information--location style-location">
<p class="street">Street Name, Suburb</p> <p class="street">{currentPhoto.name}</p>
<p class="style-caps state">Region, Country</p> <p class="style-caps state">{currentPhoto.location.region}, {currentPhoto.location.country.name}</p>
</div> </div>
<!-- Duplicate for animation? --> <!-- Duplicate for animation? -->
{#if viewer} {#if viewer}
<p class="carousel__information--date">Apr 6th, 2019</p> <p class="carousel__information--date">{dayjs(currentPhoto.created_on).format('MMM Do, YYYY')}</p>
{/if} {/if}
</div> </div>
@@ -122,4 +188,3 @@
</ol> </ol>
{/if} {/if}
</div> </div>

View File

@@ -1,7 +1,6 @@
// Carousel // Carousel
.carousel { .carousel {
background-color: $color-primary; background-color: $color-primary;
transform: translateZ(0);
.wrap { .wrap {
max-width: 1280px; max-width: 1280px;
@@ -12,7 +11,10 @@
} }
} }
// Gallery
/*
** Gallery
*/
.gallery { .gallery {
position: relative; position: relative;
z-index: 2; z-index: 2;
@@ -25,96 +27,122 @@
max-width: 100%; max-width: 100%;
} }
// Variables
$scale: 0.9;
$angle: 1deg;
$angleHover: 1.25deg;
$distance: 9%;
$distanceHover: 10%;
$radius: 8px;
// Images // Images
&__images { &__images {
position: relative; position: relative;
height: 0; height: 0;
padding-bottom: calc(100% / 1.5); // Ratio 3.2 padding-bottom: calc(100% / 1.5); // Ratio 3.2
user-select: none;
// Photo // Photo
&--photo { &--photo {
opacity: 0;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
display: block; z-index: -1;
background-color: $color-primary; top: 0;
box-shadow: 0 15px 60px rgba(#000, 0.3); left: 0;
transition: transform 600ms $ease-quart; width: 100%;
height: 100%;
transform: scale($scale);
box-shadow: 0 pxVW(15) pxVW(60) rgba(#000, 0.3);
transition: transform 600ms $ease-quart, opacity 600ms $ease-quart;
will-change: transform, opacity;
@include breakpoint (sm) { @include breakpoint (sm) {
border-radius: 8px; border-radius: $radius;
} }
img { img {
opacity: 0;
position: relative;
z-index: 1;
display: block; display: block;
height: auto;
width: 100%; width: 100%;
opacity: 0.6; height: auto;
transition: opacity 600ms $ease-quart;
} }
// Temp hiding // Overlay
&:nth-of-type(4), &:nth-of-type(5) { &:before {
display: none; opacity: 0;
content: "";
display: block;
position: absolute;
z-index: 2;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: $color-primary;
} }
} }
// Active photo // Active photo
.active { .active {
opacity: 1;
z-index: 10; z-index: 10;
top: 50%; transform: scale(1);
left: 50%;
width: 100%;
transform: translate3d(-50%, -50%, 0);
img { img {
opacity: 1; opacity: 1;
} }
} }
// Variables // Previous & next photo
$hoverAngle: 1.25deg; .prev, .next {
$hoverDistance: 7%; opacity: 1;
img {
opacity: 1;
}
&:before {
opacity: 0.4;
}
}
// Previous photo // Previous photo
.prev { .prev {
z-index: 8;
@include breakpoint (sm) { @include breakpoint (sm) {
top: 50%; transform: translate(-$distance, -1%) rotate(-$angle) scale($scale);
transform: translate3d(-6%, -50%, 0) rotate(-1deg);
width: 85%;
} }
// Hover // Hover
&.hover { &.hover {
transform: translate3d(-$hoverDistance, -50%, 0) rotate(-$hoverAngle); transform: translate(-$distanceHover, -1%) rotate(-$angleHover) scale($scale);
img {
opacity: 0.8;
}
} }
} }
// Next photo // Next photo
.next { .next {
z-index: 9;
@include breakpoint (sm) { @include breakpoint (sm) {
top: 50%; transform: translate($distance, -1%) rotate($angle) scale($scale);
right: 0;
transform: translate3d(6%, -50%, 0) rotate(1deg);
width: 85%;
} }
// Hover // Hover
&.hover { &.hover {
transform: translate3d($hoverDistance, -50%, 0) rotate($hoverAngle); transform: translate($distanceHover, -1%) rotate($angleHover) scale($scale);
img {
opacity: 0.8;
}
} }
} }
} }
} }
// Controls (arrows)
/*
** Controls (arrows)
*/
&__controls { &__controls {
display: none; display: none;
@@ -237,8 +265,6 @@
&__image { &__image {
width: 100%; width: 100%;
height: 100%; height: 100%;
// display: flex;
// justify-content: center;
overflow: auto; overflow: auto;
img { img {

View File

@@ -48,15 +48,13 @@
} }
} }
// Gallery // Photos
.gallery { .gallery__images--photo {
&__images { &:before {
&--photo {
background-color: $color-secondary; background-color: $color-secondary;
} }
} }
} }
}
} }

View File

@@ -13,7 +13,10 @@
width: 100%; width: 100%;
} }
// Top part
/*
** Top part
*/
&__top { &__top {
position: absolute; position: absolute;
z-index: 10; z-index: 10;
@@ -57,13 +60,16 @@
} }
} }
// Carousel
/*
** Carousel
*/
.carousel { .carousel {
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 0; left: 0;
right: 0; right: 0;
transform: translate3d(0, -37%, 0); transform: translateY(-37%);
margin: 0; margin: 0;
@include breakpoint (sm) { @include breakpoint (sm) {
@@ -83,7 +89,7 @@
// Specific box shadow for images // Specific box shadow for images
&__images { &__images {
&--photo { &--photo {
box-shadow: 0 16px pxVW(40) rgba(#2E025C, 0.4); box-shadow: 0 pxVW(16px) pxVW(40) rgba(#2E025C, 0.4);
} }
} }
} }