Temporary browsable Carousel on photo page
Stuff to be fixed/work on: - Why is currentPhoto only triggered at the second photo and not the first? - popState events (prev/next on browser) not working - detect URL changes and change the currentIndex
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte'
|
import { onMount, createEventDispatcher } from 'svelte'
|
||||||
|
import { stores } from '@sapper/app'
|
||||||
import { currentLocation } from '../store'
|
import { currentLocation } from '../store'
|
||||||
import * as fn from '../functions'
|
import * as fn from '../functions'
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const { page } = stores()
|
||||||
|
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -12,27 +15,47 @@
|
|||||||
import IconArrow from '../atoms/IconArrow'
|
import IconArrow from '../atoms/IconArrow'
|
||||||
|
|
||||||
// Props
|
// Props
|
||||||
|
export let photos
|
||||||
export let viewer = false
|
export let viewer = false
|
||||||
export let photos = []
|
export let init = undefined
|
||||||
|
|
||||||
// Variables
|
// Variables
|
||||||
let hover
|
let hover
|
||||||
let currentIndex = 1
|
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]
|
||||||
$: nextPhoto = photos[currentIndex + 1] || photos[0]
|
$: nextPhoto = photos[currentIndex + 1] || photos[0]
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Go to a photo
|
** Navigate to a photo
|
||||||
*/
|
*/
|
||||||
const goToPrev = () => {
|
// Go to specific photo on Init
|
||||||
currentIndex--
|
if (init) {
|
||||||
currentIndex = (currentIndex < 0) ? photos.length - 1 : currentIndex
|
currentIndex = photos.findIndex(photo => photo.slug === init.params.photo)
|
||||||
}
|
}
|
||||||
const goToNext = () => {
|
|
||||||
currentIndex++
|
// Send current photo to event
|
||||||
currentIndex = (currentIndex >= photos.length) ? 0 : currentIndex
|
const sendCurrentPhoto = ({ init = false }) => {
|
||||||
|
console.log('sendCurrentPhoto event: ', currentPhoto.slug)
|
||||||
|
dispatch('photoChange', {
|
||||||
|
currentPhoto: currentPhoto,
|
||||||
|
init
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change the current index depending on direction
|
||||||
|
const goToPhoto = direction => {
|
||||||
|
if (direction === 'prev') {
|
||||||
|
currentIndex--
|
||||||
|
currentIndex = (currentIndex < 0) ? photos.length - 1 : currentIndex
|
||||||
|
} else if (direction === 'next') {
|
||||||
|
currentIndex++
|
||||||
|
currentIndex = (currentIndex >= photos.length) ? 0 : currentIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send current photo to event
|
||||||
|
sendCurrentPhoto({ init: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -51,25 +74,19 @@
|
|||||||
const swipeEnd = event => {
|
const swipeEnd = event => {
|
||||||
touchEndX = event.changedTouches[0].screenX
|
touchEndX = event.changedTouches[0].screenX
|
||||||
touchEndY = event.changedTouches[0].screenY
|
touchEndY = event.changedTouches[0].screenY
|
||||||
if (touchEndX <= touchStartX) goToNext()
|
if (touchEndX <= touchStartX) goToPhoto('prev')
|
||||||
if (touchEndX >= touchStartX) goToPrev()
|
if (touchEndX >= touchStartX) goToPhoto('next')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keyboard navigation
|
|
||||||
const keyboardNav = event => {
|
|
||||||
const keyCode = event.keyCode
|
|
||||||
|
|
||||||
switch (event.keyCode) {
|
/*
|
||||||
case 37: case 80: case 74:
|
** Keyboard navigation
|
||||||
goToPrev()
|
*/
|
||||||
break
|
const keyboardNav = event => {
|
||||||
case 39: case 78: case 75:
|
if ([37,80,74].includes(event.keyCode)) {
|
||||||
goToNext()
|
goToPhoto('prev')
|
||||||
break
|
} else if ([39,78,75].includes(event.keyCode)) {
|
||||||
// case 27: case 67:
|
goToPhoto('next')
|
||||||
// document.getElementById('photo_close').click()
|
|
||||||
// break
|
|
||||||
default: break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,22 +107,25 @@
|
|||||||
photoToHover = (photoActive.nextSibling != null) ? photoActive.nextSibling : photoActive.parentNode.firstChild
|
photoToHover = (photoActive.nextSibling != null) ? photoActive.nextSibling : photoActive.parentNode.firstChild
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (event.type) {
|
// Toggle class and focus of the button
|
||||||
case 'mouseenter':
|
if (event.type === 'mouseenter') {
|
||||||
photoToHover.classList.add('hover')
|
photoToHover.classList.add('hover')
|
||||||
button.focus()
|
button.focus()
|
||||||
break
|
} else if (event.type === 'mouseleave') {
|
||||||
case 'mouseleave':
|
photoToHover.classList.remove('hover')
|
||||||
photoToHover.classList.remove('hover')
|
button.blur()
|
||||||
button.blur()
|
|
||||||
break
|
|
||||||
default: break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send current photo to event for init
|
||||||
|
sendCurrentPhoto({ init: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
REVEAL ANIMATION ON CAROUSEL:
|
TODO: REVEAL ANIMATION ON CAROUSEL
|
||||||
Fix the CSS priority? Cancel the transition delay and duration?
|
Fix the CSS priority? Cancel the transition delay and duration?
|
||||||
data-aos="{
|
data-aos="{
|
||||||
(photo === prevPhoto) ? 'carousel-prev' :
|
(photo === prevPhoto) ? 'carousel-prev' :
|
||||||
@@ -117,7 +137,10 @@
|
|||||||
|
|
||||||
<svelte:window on:keydown={keyboardNav} />
|
<svelte:window on:keydown={keyboardNav} />
|
||||||
|
|
||||||
<div class="carousel" on:touchstart={swipeStart} on:touchend={swipeEnd}>
|
<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">
|
||||||
@@ -137,14 +160,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="carousel__controls">
|
<div class="carousel__controls">
|
||||||
<div class="carousel__area carousel__area--prev" data-to="prev" on:mouseenter={hover} on:mouseleave={hover} on:click={goToPrev}>
|
<div class="carousel__area carousel__area--prev" data-to="prev"
|
||||||
|
on:mouseenter={hover} on:mouseleave={hover}
|
||||||
|
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">
|
||||||
<IconArrow direction="left" color="#ff6c89" class="icon" />
|
<IconArrow direction="left" color="#ff6c89" class="icon" />
|
||||||
<IconArrow direction="left" color="#fff" class="icon" hidden="true" />
|
<IconArrow direction="left" color="#fff" class="icon" hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="carousel__area carousel__area--next" data-to="next" on:mouseenter={hover} on:mouseleave={hover} on:click={goToNext}>
|
<div class="carousel__area carousel__area--next" data-to="next"
|
||||||
|
on:mouseenter={hover} on:mouseleave={hover}
|
||||||
|
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">
|
||||||
<IconArrow direction="right" color="#ff6c89" class="icon" />
|
<IconArrow direction="right" color="#ff6c89" class="icon" />
|
||||||
<IconArrow direction="right" color="#fff" class="icon" hidden="true" />
|
<IconArrow direction="right" color="#fff" class="icon" hidden="true" />
|
||||||
|
|||||||
@@ -9,13 +9,14 @@
|
|||||||
|
|
||||||
// Define either to preload data or use the store
|
// Define either to preload data or use the store
|
||||||
let preloaded
|
let preloaded
|
||||||
currentLocation.subscribe(store => preloaded = store ? store : undefined)
|
currentPhotos.subscribe(store => preloaded = store ? store : undefined)
|
||||||
|
|
||||||
|
|
||||||
// Preload data
|
// Preload data
|
||||||
export async function preload (page, session) {
|
export async function preload (page, session) {
|
||||||
// Load the photos if not loaded
|
// Load the photos if not loaded
|
||||||
if (!preloaded) {
|
if (!preloaded) {
|
||||||
const req = await this.fetch(`${apiEndpoints.rest}/items/photos?fields=id,name,slug,image.*,image.data,location.*,location.country.*,created_on&filter[location.slug][rlike]=%${page.params.location}%`)
|
const req = await this.fetch(`${apiEndpoints.rest}/items/photos?fields=id,name,slug,image.*,location.*,location.country.*,created_on,modified_on&filter[location.slug][rlike]=%${page.params.location}%`)
|
||||||
const photos = await req.json()
|
const photos = await req.json()
|
||||||
return {
|
return {
|
||||||
photos: photos.data
|
photos: photos.data
|
||||||
@@ -23,14 +24,17 @@
|
|||||||
}
|
}
|
||||||
// Use the store otherwise
|
// Use the store otherwise
|
||||||
else return {
|
else return {
|
||||||
photos: preloaded.photos
|
photos: preloaded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte'
|
import { onMount, createEventDispatcher } from 'svelte'
|
||||||
|
import { goto } from '@sapper/app'
|
||||||
import * as fn from '../../../../functions'
|
import * as fn from '../../../../functions'
|
||||||
|
const { page } = stores()
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
// Dependencies
|
// Dependencies
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
@@ -41,38 +45,80 @@
|
|||||||
import IconGlobe from '../../../../atoms/IconGlobe'
|
import IconGlobe from '../../../../atoms/IconGlobe'
|
||||||
import IconCross from '../../../../atoms/IconCross'
|
import IconCross from '../../../../atoms/IconCross'
|
||||||
import Carousel from '../../../../organisms/Carousel'
|
import Carousel from '../../../../organisms/Carousel'
|
||||||
|
import SocialMetas from '../../../../utils/SocialMetas'
|
||||||
|
|
||||||
// Props and variables
|
// Props
|
||||||
const { page } = stores()
|
|
||||||
export let photos
|
export let photos
|
||||||
let currentIndex
|
|
||||||
let indexFormated
|
// Variables
|
||||||
let viewerPhotos
|
|
||||||
let windowWidth
|
let windowWidth
|
||||||
let changeWindowWidth
|
let currentPhoto
|
||||||
|
|
||||||
// Define current location
|
// Define current location
|
||||||
const location = (!$currentLocation) ? $locations.find(loc => loc.slug === $page.params.location) : $currentLocation
|
const location = (!$currentLocation) ? $locations.find(loc => loc.slug === $page.params.location) : $currentLocation
|
||||||
const locationFull = `${location.name}, ${location.country.name}`
|
|
||||||
|
|
||||||
// Update store current location informations
|
// Update store current location informations
|
||||||
if (!$currentLocation) currentLocation.set(location)
|
if (!$currentLocation) currentLocation.set(location)
|
||||||
if (!$currentPhotos) currentPhotos.set(photos)
|
if (!$currentPhotos) currentPhotos.set(photos)
|
||||||
|
|
||||||
// Define path
|
// The photo has changed from the carousel
|
||||||
const path = `/viewer/${location.country.slug}/${location.slug}/`
|
const photoChanged = event => {
|
||||||
|
currentPhoto = event.detail.currentPhoto
|
||||||
|
console.log('photoChanged [photo] event: ', currentPhoto.slug)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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', {
|
||||||
|
// location: event.target.location
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
!!! TODO:
|
||||||
|
- URL navigation on photo change (from Carousel)
|
||||||
|
- If coming to direct URL, show the proper photo (i.e currentIndex from photo.slug in URL)
|
||||||
|
- Get currentPhoto from Carousel to update title and metas
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
** Run code on browser only
|
** Run code on browser only
|
||||||
*/
|
*/
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
/*
|
||||||
|
!!! TODO:
|
||||||
|
- Change the title with the current photo name and update Metas (with window location url)
|
||||||
|
*/
|
||||||
|
// console.log(currentPhoto)
|
||||||
|
|
||||||
|
dispatch('changeUrl', {
|
||||||
|
page: $page
|
||||||
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>Houses Of - photoName photoCountryName</title>
|
<title>Houses Of – Photos of {location.name}, {location.country.name}</title>
|
||||||
|
<meta name="description" content={location.description}>
|
||||||
|
<SocialMetas
|
||||||
|
title="Houses Of - Beautiful houses of {location.name}, {location.country.name}"
|
||||||
|
description="Houses Of {location.name} {location.description}"
|
||||||
|
/>
|
||||||
|
<!-- url="//{$page.host.split(':3000')[0]}/viewer/{currentPhoto.location.country.slug}/{currentPhoto.location.slug}/{currentPhoto.slug}" -->
|
||||||
|
<!-- image={currentPhoto ? fn.getThumbnail(currentPhoto.image.private_hash, 1200, 630) : null} -->
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<svelte:window bind:innerWidth={windowWidth} />
|
<svelte:window bind:innerWidth={windowWidth} />
|
||||||
@@ -95,5 +141,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Carousel {photos} viewer={true} />
|
<Carousel
|
||||||
|
{photos}
|
||||||
|
viewer={true}
|
||||||
|
init={$page}
|
||||||
|
on:photoChange={photoChanged}
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
|
|
||||||
// Photo
|
// Photo
|
||||||
&__photo {
|
&__photo {
|
||||||
$duration: 0.9s;
|
$duration: 1s;
|
||||||
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
transform: scale($scale);
|
transform: scale($scale);
|
||||||
box-shadow: 0 pxVW(15) pxVW(60) rgba(#000, 0.3);
|
box-shadow: 0 pxVW(15) pxVW(60) rgba(#000, 0.3);
|
||||||
transition: transform $duration $ease-quart, opacity $duration $ease-quart;
|
transition: transform $duration $ease-quart, opacity $duration / 2 $ease-quart;
|
||||||
will-change: transform, opacity;
|
will-change: transform, opacity;
|
||||||
|
|
||||||
@include breakpoint (sm) {
|
@include breakpoint (sm) {
|
||||||
|
|||||||
@@ -94,8 +94,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Information
|
// Informations
|
||||||
&__information {
|
&__infos {
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
|
|
||||||
@@ -104,27 +104,26 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Location
|
// Location
|
||||||
&--location {
|
&__locations {
|
||||||
width: 75%;
|
width: 75%;
|
||||||
text-align: left;
|
}
|
||||||
|
&__location {
|
||||||
|
text-align: left;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.street {
|
// Date
|
||||||
margin: 0;
|
&__date {
|
||||||
}
|
color: rgba($color-tertiary, 0.5);
|
||||||
}
|
font-size: rem(12px);
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
// Date
|
@include breakpoint (sm) {
|
||||||
&--date {
|
font-size: rem(14px);
|
||||||
color: rgba($color-tertiary, 0.5);
|
margin: 0;
|
||||||
font-size: rem(12px);
|
|
||||||
margin-top: 16px;
|
|
||||||
|
|
||||||
@include breakpoint (sm) {
|
|
||||||
font-size: rem(14px);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user