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:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
// 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')
|
||||
}
|
||||
// Viewer: Navigate to photo on init and URL change
|
||||
if (viewer) {
|
||||
page.subscribe(page => goToPhoto(photos.findIndex(photo => photo.slug === page.params.photo)))
|
||||
}
|
||||
|
||||
// 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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
/*
|
||||
|
||||
@@ -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,32 +59,22 @@
|
||||
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 windowPathname = window.location.pathname
|
||||
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 })
|
||||
|
||||
|
||||
/*
|
||||
** Run code when mounted
|
||||
@@ -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> </a>
|
||||
</div>
|
||||
|
||||
<Carousel
|
||||
viewer="true"
|
||||
photos={photos}
|
||||
init={$page}
|
||||
on:photoChange={photoChanged}
|
||||
/>
|
||||
|
||||
|
||||
@@ -22,18 +22,28 @@
|
||||
*/
|
||||
let scope
|
||||
let firstLoad = true
|
||||
let previousPage = ''
|
||||
|
||||
// Check if viewer
|
||||
const isExcluded = path => path.includes(['/viewer/'])
|
||||
|
||||
// 1. Watch page change
|
||||
page.subscribe(page => {
|
||||
// Run the loader animation (only after first load)
|
||||
if (!firstLoad) {
|
||||
animateIn(scope)
|
||||
// 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)
|
||||
}
|
||||
|
||||
// TODO: Figure out how to delay the page rendering a little bit before the end of the transition panel ending
|
||||
|
||||
// Set pageReady to false (?)
|
||||
pageReady.set(false)
|
||||
}
|
||||
|
||||
// TODO: Figure out how to delay the page rendering a little bit before the end of the transition panel ending
|
||||
|
||||
// Set pageReady to false (?)
|
||||
pageReady.set(false)
|
||||
// Update page for viewer navigation checking
|
||||
previousPage = page.path
|
||||
})
|
||||
|
||||
// 2. Watch when loaded changes
|
||||
|
||||
Reference in New Issue
Block a user