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:active={index === currentIndex}
|
||||||
class:small={index < currentIndex - 2 || index > currentIndex + 2}
|
class:small={index < currentIndex - 2 || index > currentIndex + 2}
|
||||||
class:hidden={index < currentIndex - 3 || index > currentIndex + 3}
|
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>
|
<button aria-label="Go to photo #{index + 1}"></button>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -18,13 +18,12 @@
|
|||||||
// Props
|
// Props
|
||||||
export let photos
|
export let photos
|
||||||
export let viewer = false
|
export let viewer = false
|
||||||
export let init = undefined
|
|
||||||
|
|
||||||
// Variables
|
// Variables
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const { page } = stores()
|
const { page } = stores()
|
||||||
let scope
|
let scope
|
||||||
let hover
|
let swiped
|
||||||
let currentIndex = 0
|
let currentIndex = 0
|
||||||
$: currentPhoto = photos[currentIndex] || null
|
$: currentPhoto = photos[currentIndex] || null
|
||||||
$: prevPhoto = photos[currentIndex - 1] || photos[photos.length - 1]
|
$: prevPhoto = photos[currentIndex - 1] || photos[photos.length - 1]
|
||||||
@@ -34,11 +33,6 @@
|
|||||||
/*
|
/*
|
||||||
** Navigate to a photo
|
** 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
|
// Change current index from param
|
||||||
const goToPhoto = to => {
|
const goToPhoto = to => {
|
||||||
if (to === 'prev') {
|
if (to === 'prev') {
|
||||||
@@ -52,25 +46,50 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dispatch current photo
|
// Dispatch current photo
|
||||||
sendCurrentPhoto()
|
dispatch('photoChange', photos[currentIndex])
|
||||||
|
|
||||||
// Reset fullscreen value if open
|
// Reset fullscreen value if open
|
||||||
fullscreen.set()
|
if ($fullscreen) fullscreen.set()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send current photo to event
|
// Hover on controls
|
||||||
const sendCurrentPhoto = (init = false) => {
|
const hoverPhoto = event => {
|
||||||
dispatch('photoChange', {
|
const button = event.currentTarget.querySelector('button')
|
||||||
currentPhoto: photos[currentIndex],
|
const photoActive = scope.querySelector('.gallery__photo--active')
|
||||||
init
|
|
||||||
})
|
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 => {
|
const swipe = directions => {
|
||||||
|
swiped = true
|
||||||
|
|
||||||
if (directions.right) {
|
if (directions.right) {
|
||||||
goToPhoto('prev')
|
goToPhoto('prev')
|
||||||
} else if (directions.left) {
|
} else if (directions.left) {
|
||||||
@@ -78,10 +97,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keyboard navigation
|
||||||
/*
|
|
||||||
** Keyboard navigation
|
|
||||||
*/
|
|
||||||
const keyboardNav = event => {
|
const keyboardNav = event => {
|
||||||
if ([37,80,74].includes(event.keyCode)) {
|
if ([37,80,74].includes(event.keyCode)) {
|
||||||
goToPhoto('prev')
|
goToPhoto('prev')
|
||||||
@@ -103,30 +119,10 @@
|
|||||||
// Enable gestures
|
// Enable gestures
|
||||||
const touch = SwipeListener(scope)
|
const touch = SwipeListener(scope)
|
||||||
|
|
||||||
// Controls hover
|
// Viewer: Navigate to photo on init and URL change
|
||||||
hover = event => {
|
if (viewer) {
|
||||||
const button = event.currentTarget.querySelector('button')
|
page.subscribe(page => goToPhoto(photos.findIndex(photo => photo.slug === page.params.photo)))
|
||||||
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')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch current photo for init
|
|
||||||
sendCurrentPhoto({ init: true })
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -145,7 +141,7 @@
|
|||||||
class:gallery__photo--prev={photo === prevPhoto}
|
class:gallery__photo--prev={photo === prevPhoto}
|
||||||
class:gallery__photo--active={photo === currentPhoto}
|
class:gallery__photo--active={photo === currentPhoto}
|
||||||
class:gallery__photo--next={photo === nextPhoto}
|
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: 968px)" srcset={getThumbnail(photo.image.private_hash, 1400)}>
|
||||||
<source media="(min-width: 800px)" srcset={getThumbnail(photo.image.private_hash, 900)}>
|
<source media="(min-width: 800px)" srcset={getThumbnail(photo.image.private_hash, 900)}>
|
||||||
@@ -158,7 +154,7 @@
|
|||||||
|
|
||||||
<div class="carousel__controls">
|
<div class="carousel__controls">
|
||||||
<div class="carousel__area carousel__area--prev" data-to="prev"
|
<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')}
|
on:click={() => goToPhoto('prev')}
|
||||||
>
|
>
|
||||||
<button class="button-control button-control--white dir-left" aria-label="Previous">
|
<button class="button-control button-control--white dir-left" aria-label="Previous">
|
||||||
@@ -168,7 +164,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="carousel__area carousel__area--next" data-to="next"
|
<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')}
|
on:click={() => goToPhoto('next')}
|
||||||
>
|
>
|
||||||
<button class="button-control button-control--white dir-right" aria-label="Next">
|
<button class="button-control button-control--white dir-right" aria-label="Next">
|
||||||
@@ -205,7 +201,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PaginationDots className="carousel__dots" {photos} {currentIndex}
|
<PaginationDots className="carousel__dots" {photos} {currentIndex}
|
||||||
on:goToIndex={event => currentIndex = event.detail.index}
|
on:goToIndex={event => currentIndex = event.detail}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -22,9 +22,7 @@
|
|||||||
photosToAppend.forEach(photo => photo.hidden = false)
|
photosToAppend.forEach(photo => photo.hidden = false)
|
||||||
|
|
||||||
// Merge new photos to show to the existing ones
|
// Merge new photos to show to the existing ones
|
||||||
dispatch('updatePagination', {
|
dispatch('updatePagination', [...paginatedPhotos, ...photosToAppend])
|
||||||
paginatedPhotos: [...paginatedPhotos, ...photosToAppend]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Increment current index
|
// Increment current index
|
||||||
currentIndex = currentIndex + photosPerPage
|
currentIndex = currentIndex + photosPerPage
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
let paginatedPhotos = photos.filter(photo => photo.hidden === false)
|
let paginatedPhotos = photos.filter(photo => photo.hidden === false)
|
||||||
|
|
||||||
// Update pagination event from Pagination component
|
// Update pagination event from Pagination component
|
||||||
const updatePagination = event => paginatedPhotos = event.detail.paginatedPhotos
|
const updatePagination = event => paginatedPhotos = event.detail
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -39,7 +39,7 @@
|
|||||||
pageReady,
|
pageReady,
|
||||||
pageTransition
|
pageTransition
|
||||||
} from '../../../../utils/store'
|
} from '../../../../utils/store'
|
||||||
import { getThumbnail, analyticsUpdate } from '../../../../utils/functions'
|
import { getThumbnail } from '../../../../utils/functions'
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import IconGlobe from '../../../../atoms/IconGlobe'
|
import IconGlobe from '../../../../atoms/IconGlobe'
|
||||||
@@ -59,31 +59,21 @@
|
|||||||
const { page } = stores()
|
const { page } = stores()
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
let windowWidth
|
let windowWidth
|
||||||
|
let gotoLink
|
||||||
let currentPhoto = photos.find(photo => photo.slug === $page.params.photo)
|
let currentPhoto = photos.find(photo => photo.slug === $page.params.photo)
|
||||||
|
|
||||||
// Update store current location
|
// Update store current location
|
||||||
if (!$currentLocation) currentLocation.set($locations.find(loc => loc.slug === $page.params.place))
|
if (!$currentLocation) currentLocation.set($locations.find(loc => loc.slug === $page.params.place))
|
||||||
if (!$currentPhotos) currentPhotos.set(photos)
|
if (!$currentPhotos) currentPhotos.set(photos)
|
||||||
|
|
||||||
// Photo has changed from the Carousel component
|
// Photo has changed (from Carousel)
|
||||||
const photoChanged = event => {
|
const photoChanged = event => {
|
||||||
currentPhoto = event.detail.currentPhoto
|
|
||||||
|
|
||||||
// Change the URL to the current photo
|
|
||||||
if (!event.detail.init) {
|
|
||||||
const windowPathname = window.location.pathname
|
const windowPathname = window.location.pathname
|
||||||
const newUrl = windowPathname.substring(0, windowPathname.lastIndexOf('/') + 1) + currentPhoto.slug
|
const newUrl = windowPathname.substring(0, windowPathname.lastIndexOf('/') + 1) + event.detail.slug
|
||||||
history.pushState('', document.title, newUrl)
|
// Go to page via a sapper-noscroll link to avoid scroll jump
|
||||||
analyticsUpdate(newUrl)
|
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(() => {
|
onMount(() => {
|
||||||
// Page is loaded
|
// Page is loaded
|
||||||
pageReady.set(true)
|
pageReady.set(true)
|
||||||
|
|
||||||
dispatch('changeUrl', { page: $page })
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -129,12 +117,12 @@
|
|||||||
<IconCross color="#fff" width="18" class="icon" hidden="true" />
|
<IconCross color="#fff" width="18" class="icon" hidden="true" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
<a href="/" bind:this={gotoLink} aria-hidden="true" hidden class="hidden" sapper-noscroll> </a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Carousel
|
<Carousel
|
||||||
viewer="true"
|
viewer="true"
|
||||||
photos={photos}
|
photos={photos}
|
||||||
init={$page}
|
|
||||||
on:photoChange={photoChanged}
|
on:photoChange={photoChanged}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -22,9 +22,15 @@
|
|||||||
*/
|
*/
|
||||||
let scope
|
let scope
|
||||||
let firstLoad = true
|
let firstLoad = true
|
||||||
|
let previousPage = ''
|
||||||
|
|
||||||
|
// Check if viewer
|
||||||
|
const isExcluded = path => path.includes(['/viewer/'])
|
||||||
|
|
||||||
// 1. Watch page change
|
// 1. Watch page change
|
||||||
page.subscribe(page => {
|
page.subscribe(page => {
|
||||||
|
// Run transition if page is not excluded
|
||||||
|
if (!isExcluded(previousPage) || !isExcluded(page.path)) {
|
||||||
// Run the loader animation (only after first load)
|
// Run the loader animation (only after first load)
|
||||||
if (!firstLoad) {
|
if (!firstLoad) {
|
||||||
animateIn(scope)
|
animateIn(scope)
|
||||||
@@ -34,6 +40,10 @@
|
|||||||
|
|
||||||
// Set pageReady to false (?)
|
// Set pageReady to false (?)
|
||||||
pageReady.set(false)
|
pageReady.set(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update page for viewer navigation checking
|
||||||
|
previousPage = page.path
|
||||||
})
|
})
|
||||||
|
|
||||||
// 2. Watch when loaded changes
|
// 2. Watch when loaded changes
|
||||||
|
|||||||
Reference in New Issue
Block a user