Viewer: Finally fix Carousel navigation depending on URL

- Instead of detecting every case using popState event or whatnot, used a Sapper page stores subscribe to go to the current photo from the URL param (findIndex in photos)
- No page transition when navigating between photos in Viewer, but runs In and Out
- Also shortened dispatches to direct value over an object
This commit is contained in:
2020-03-29 18:13:27 +02:00
parent 25fa9e1c07
commit ca07fe364e
6 changed files with 73 additions and 81 deletions

View File

@@ -14,7 +14,7 @@
class:active={index === currentIndex}
class:small={index < currentIndex - 2 || index > currentIndex + 2}
class:hidden={index < currentIndex - 3 || index > currentIndex + 3}
on:click={() => dispatch('goToIndex', { index })}
on:click={() => dispatch('goToIndex', index)}
>
<button aria-label="Go to photo #{index + 1}"></button>
</li>

View File

@@ -18,13 +18,12 @@
// Props
export let photos
export let viewer = false
export let init = undefined
// Variables
const dispatch = createEventDispatcher()
const { page } = stores()
let scope
let hover
let swiped
let currentIndex = 0
$: currentPhoto = photos[currentIndex] || null
$: prevPhoto = photos[currentIndex - 1] || photos[photos.length - 1]
@@ -34,11 +33,6 @@
/*
** Navigate to a photo
*/
// Go to specific photo on Init
if (init) {
currentIndex = photos.findIndex(photo => photo.slug === init.params.photo)
}
// Change current index from param
const goToPhoto = to => {
if (to === 'prev') {
@@ -52,25 +46,50 @@
}
// Dispatch current photo
sendCurrentPhoto()
dispatch('photoChange', photos[currentIndex])
// Reset fullscreen value if open
fullscreen.set()
if ($fullscreen) fullscreen.set()
}
// Send current photo to event
const sendCurrentPhoto = (init = false) => {
dispatch('photoChange', {
currentPhoto: photos[currentIndex],
init
})
// Hover on controls
const hoverPhoto = event => {
const button = event.currentTarget.querySelector('button')
const photoActive = scope.querySelector('.gallery__photo--active')
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
}
// Toggle class and focus of the button
if (event.type === 'mouseenter') {
photoToHover.classList.add('hover')
button.classList.add('hover')
} else if (event.type === 'mouseleave') {
photoToHover.classList.remove('hover')
button.classList.remove('hover')
}
}
// Open fullscreen
const openFullscreen = event => {
if (!swiped) {
fullscreen.set(currentPhoto)
}
// Reset swiped event if fired
swiped = false
}
/*
** Drag/touch navigation
** Navigation
*/
// Drag and swipe
const swipe = directions => {
swiped = true
if (directions.right) {
goToPhoto('prev')
} else if (directions.left) {
@@ -78,10 +97,7 @@
}
}
/*
** Keyboard navigation
*/
// Keyboard navigation
const keyboardNav = event => {
if ([37,80,74].includes(event.keyCode)) {
goToPhoto('prev')
@@ -103,30 +119,10 @@
// Enable gestures
const touch = SwipeListener(scope)
// Controls hover
hover = event => {
const button = event.currentTarget.querySelector('button')
const photoActive = document.querySelector('.gallery__photo--active')
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
// Viewer: Navigate to photo on init and URL change
if (viewer) {
page.subscribe(page => goToPhoto(photos.findIndex(photo => photo.slug === page.params.photo)))
}
// Toggle class and focus of the button
if (event.type === 'mouseenter') {
photoToHover.classList.add('hover')
button.classList.add('hover')
} else if (event.type === 'mouseleave') {
photoToHover.classList.remove('hover')
button.classList.remove('hover')
}
}
// Dispatch current photo for init
sendCurrentPhoto({ init: true })
})
</script>
@@ -145,7 +141,7 @@
class:gallery__photo--prev={photo === prevPhoto}
class:gallery__photo--active={photo === currentPhoto}
class:gallery__photo--next={photo === nextPhoto}
on:click={event => fullscreen.set(currentPhoto)}
on:click={openFullscreen}
>
<source media="(min-width: 968px)" srcset={getThumbnail(photo.image.private_hash, 1400)}>
<source media="(min-width: 800px)" srcset={getThumbnail(photo.image.private_hash, 900)}>
@@ -158,7 +154,7 @@
<div class="carousel__controls">
<div class="carousel__area carousel__area--prev" data-to="prev"
on:mouseenter={hover} on:mouseleave={hover}
on:mouseenter={hoverPhoto} on:mouseleave={hoverPhoto}
on:click={() => goToPhoto('prev')}
>
<button class="button-control button-control--white dir-left" aria-label="Previous">
@@ -168,7 +164,7 @@
</div>
<div class="carousel__area carousel__area--next" data-to="next"
on:mouseenter={hover} on:mouseleave={hover}
on:mouseenter={hoverPhoto} on:mouseleave={hoverPhoto}
on:click={() => goToPhoto('next')}
>
<button class="button-control button-control--white dir-right" aria-label="Next">
@@ -205,7 +201,7 @@
</div>
<PaginationDots className="carousel__dots" {photos} {currentIndex}
on:goToIndex={event => currentIndex = event.detail.index}
on:goToIndex={event => currentIndex = event.detail}
/>
</div>
{/if}

View File

@@ -22,9 +22,7 @@
photosToAppend.forEach(photo => photo.hidden = false)
// Merge new photos to show to the existing ones
dispatch('updatePagination', {
paginatedPhotos: [...paginatedPhotos, ...photosToAppend]
})
dispatch('updatePagination', [...paginatedPhotos, ...photosToAppend])
// Increment current index
currentIndex = currentIndex + photosPerPage

View File

@@ -80,7 +80,7 @@
let paginatedPhotos = photos.filter(photo => photo.hidden === false)
// Update pagination event from Pagination component
const updatePagination = event => paginatedPhotos = event.detail.paginatedPhotos
const updatePagination = event => paginatedPhotos = event.detail
/*

View File

@@ -39,7 +39,7 @@
pageReady,
pageTransition
} from '../../../../utils/store'
import { getThumbnail, analyticsUpdate } from '../../../../utils/functions'
import { getThumbnail } from '../../../../utils/functions'
// Components
import IconGlobe from '../../../../atoms/IconGlobe'
@@ -59,31 +59,21 @@
const { page } = stores()
const dispatch = createEventDispatcher()
let windowWidth
let gotoLink
let currentPhoto = photos.find(photo => photo.slug === $page.params.photo)
// Update store current location
if (!$currentLocation) currentLocation.set($locations.find(loc => loc.slug === $page.params.place))
if (!$currentPhotos) currentPhotos.set(photos)
// Photo has changed from the Carousel component
// Photo has changed (from Carousel)
const photoChanged = event => {
currentPhoto = event.detail.currentPhoto
// Change the URL to the current photo
if (!event.detail.init) {
const windowPathname = window.location.pathname
const newUrl = windowPathname.substring(0, windowPathname.lastIndexOf('/') + 1) + currentPhoto.slug
history.pushState('', document.title, newUrl)
analyticsUpdate(newUrl)
const newUrl = windowPathname.substring(0, windowPathname.lastIndexOf('/') + 1) + event.detail.slug
// Go to page via a sapper-noscroll link to avoid scroll jump
gotoLink.href = newUrl
gotoLink.click()
}
}
// Access a direct photo or navigate in history
const photoSlug = $page.params.photo
// On init, send an event to the Carousel component with the photoSlug to set currentIndex and then change the photo
// Pop event from browser (prev/next)
const changedUrl = event => dispatch('changedUrl', { currentPhoto: currentPhoto })
/*
@@ -92,8 +82,6 @@
onMount(() => {
// Page is loaded
pageReady.set(true)
dispatch('changeUrl', { page: $page })
})
</script>
@@ -129,12 +117,12 @@
<IconCross color="#fff" width="18" class="icon" hidden="true" />
</a>
</div>
<a href="/" bind:this={gotoLink} aria-hidden="true" hidden class="hidden" sapper-noscroll>&nbsp;</a>
</div>
<Carousel
viewer="true"
photos={photos}
init={$page}
on:photoChange={photoChanged}
/>

View File

@@ -22,9 +22,15 @@
*/
let scope
let firstLoad = true
let previousPage = ''
// Check if viewer
const isExcluded = path => path.includes(['/viewer/'])
// 1. Watch page change
page.subscribe(page => {
// Run transition if page is not excluded
if (!isExcluded(previousPage) || !isExcluded(page.path)) {
// Run the loader animation (only after first load)
if (!firstLoad) {
animateIn(scope)
@@ -34,6 +40,10 @@
// Set pageReady to false (?)
pageReady.set(false)
}
// Update page for viewer navigation checking
previousPage = page.path
})
// 2. Watch when loaded changes