WIP Animations all over site

- Run a transition In for each page
- Involve a "loader" panel on page change
- TODO: tweak the animations and finish the concept
This commit is contained in:
2020-03-06 14:22:51 +01:00
parent cd1033f97b
commit 9ffc210c02
27 changed files with 827 additions and 296 deletions

View File

@@ -0,0 +1,56 @@
import anime from 'animejs'
import ScrollOut from 'scroll-out'
import { animDuration } from '../utils/store'
/*
** Transition In
*/
export const animateIn = () => {
// Card: Active
const cardActive = ScrollOut({
once: true,
targets: '.gallery__photo--active',
onShown (el) {
anime({
targets: el,
top: [32, 0],
duration: 1200,
delay: 650,
easing: 'easeOutQuart'
})
}
})
// Card: Prev
const cardPrev = ScrollOut({
once: true,
targets: '.gallery__photo--prev',
onShown (el) {
anime({
targets: el,
top: [5, 0],
left: [-64, 0],
duration: 1200,
delay: 690,
easing: 'easeOutQuart'
})
}
})
// Card: Prev
const cardNext = ScrollOut({
once: true,
targets: '.gallery__photo--next',
onShown (el) {
anime({
targets: el,
top: [5, 0],
left: [48, 0],
duration: 1200,
delay: 710,
easing: 'easeOutQuart'
})
}
})
}

View File

@@ -0,0 +1,47 @@
import anime from 'animejs'
import ScrollOut from 'scroll-out'
import { animDuration } from '../utils/store'
/*
** Transition In
*/
export const animateIn = () => {
// Each location
const locations = ScrollOut({
targets: '#locations_list .location',
onShown (el) {
// Timeline
const tl = anime.timeline({
autoplay: false,
duration: 600,
delay: anime.stagger(200),
easing: 'easeOutQuart'
})
// Image
tl.add({
targets: el.querySelector('img'),
scale: [1.3, 1],
opacity: [0, 1],
duration: 1800,
delay: 100
})
// Name
tl.add({
targets: el.querySelector('h3'),
translateY: ['100%', 0]
}, 300)
// Country
tl.add({
targets: el.querySelector('p'),
translateY: ['100%', 0]
}, 550)
// Play
tl.play()
}
})
}

View File

@@ -0,0 +1,34 @@
import anime from 'animejs'
import ScrollOut from 'scroll-out'
import { animDuration } from '../utils/store'
/*
** Transition In
*/
export const animateIn = callback => {
// On scroll animation
const title = ScrollOut({
once: true,
targets: '.title-location',
onShown (el) {
// Each letters
anime({
targets: el.querySelectorAll('span'),
translateY: ['100%', 0],
delay: anime.stagger(40),
duration: 1000,
easing: 'easeOutQuart'
})
// Word in between
anime({
targets: el.querySelectorAll('em span'),
opacity: [0, 1],
delay: anime.stagger(80, { start: 400 }),
duration: 1000,
easing: 'easeOutQuart'
})
}
})
}

View File

@@ -0,0 +1,61 @@
import anime from 'animejs'
import { animDuration, animDurationLong } from '../utils/store'
/*
** Transition In
*/
export const animateIn = () => {
// Panel itself
const transition = anime({
targets: '#transition',
height: ['100%', '100%'],
opacity: [0, 1],
duration: 200,
delay: 0,
easing: 'easeInOutQuart'
})
// Globe icon
const globe = anime({
targets: '#transition svg',
opacity: [0, 1],
duration: 200,
delay: 0,
easing: 'easeInOutQuart'
})
}
/*
** Transition Out
*/
export const animateOut = callback => {
// Panel itself
const transition = anime({
targets: '#transition',
height: ['100%', 0],
duration: animDurationLong,
delay: 800,
easing: 'easeInOutQuart',
complete: callback
})
// Title
const title = anime({
targets: '#transition .title-location',
opacity: 0,
duration: 600,
delay: 1400,
easing: 'easeInOutQuart'
})
// Globe icon
const globe = anime({
targets: '#transition svg',
opacity: 0,
duration: 600,
delay: 1400,
easing: 'easeInOutQuart'
})
}

View File

@@ -1,10 +1,8 @@
// import anime from 'animejs'
import { crossfade } from 'svelte/transition'
import { quartOut } from 'svelte/easing'
// Crossfade transition
export const [crossfadeSend, crossfadeReceive] = crossfade({
export const [send, receive] = crossfade({
duration: d => Math.sqrt(d * 200),
fallback(node, params) {

121
src/animations/index.js Normal file
View File

@@ -0,0 +1,121 @@
import anime from 'animejs'
import ScrollOut from 'scroll-out'
import { animDuration } from '../utils/store'
import { debounce } from '../utils/functions'
/*
** Transition In
*/
export const animateIn = () => {
// Title: Houses
const titleHouses = ScrollOut({
once: true,
targets: '#title-houses',
onShown (el) {
// Reveal
anime({
targets: el.querySelectorAll('span'),
translateY: ['-70%', 0],
delay: anime.stagger(80),
duration: animDuration,
easing: 'easeOutQuart'
})
// Parallax on scroll
const translate = anime.timeline({
autoplay: false,
duration: animDuration
})
translate.add({
targets: el,
translateX: ['-3%', '-17%'],
easing: 'easeOutQuart',
duration: animDuration
})
window.addEventListener('scroll', fn.debounce(event => {
translate.seek(translate.duration * (window.scrollY / 1000))
}), 50)
}
})
// Intro: Description
const introDescription = ScrollOut({
once: true,
targets: '#intro-description',
onShown (el) {
anime({
targets: el.querySelectorAll('p, a'),
opacity: [0, 1],
translateY: [8, 0],
duration: animDuration,
delay: anime.stagger(200, { start: 400 }),
easing: 'easeOutQuart'
})
}
})
// Intro: Carousel
const introCarousel = ScrollOut({
once: true,
targets: '#intro-carousel',
onShown(el) {
anime({
targets: el,
opacity: [0, 1],
translateY: [24, 0],
duration: animDuration,
delay: 650,
easing: 'easeOutQuart'
})
}
})
// Title: Of
const titleOf = ScrollOut({
once: true,
targets: '#title-of',
onShown (el) {
anime({
targets: el.querySelectorAll('span'),
translateY: ['100%', 0],
delay: anime.stagger(70),
duration: animDuration,
easing: 'easeOutQuart'
})
}
})
// Title: World
const titleWorld = ScrollOut({
once: true,
targets: '#title-world',
onShown (el, ctx) {
anime({
targets: el.querySelectorAll('span'),
translateY: ['100%', 0],
delay: anime.stagger(70),
duration: animDuration,
easing: 'easeOutQuart'
})
// Parallax on scroll
const translate = anime.timeline({
autoplay: false,
duration: animDuration
})
translate.add({
targets: el,
translateX: ['4%', '-4%'],
easing: 'easeOutQuart',
duration: animDuration
})
if (ctx.visible) {
window.addEventListener('scroll', debounce(event => {
translate.seek(translate.duration * (window.scrollY / 1000))
}), 35)
}
}
})
}

25
src/animations/page.js Normal file
View File

@@ -0,0 +1,25 @@
import anime from 'animejs'
import ScrollOut from 'scroll-out'
import { animDuration, animDurationLong } from '../utils/store'
/*
** Transition In
*/
export const animateIn = () => {
// Simple fade
const page = ScrollOut({
once: true,
targets: '.page',
onShown (el) {
anime({
targets: '.page__part',
opacity: [0, 1],
translateY: [8, 0],
duration: 1800,
delay: anime.stagger(220, { start: animDurationLong * 0.3 }),
easing: 'easeOutQuart'
})
}
})
}

60
src/animations/place.js Normal file
View File

@@ -0,0 +1,60 @@
import anime from 'animejs'
// import ScrollOut from 'scroll-out'
import { animDuration } from '../utils/store'
/*
** Transition In
*/
export const animateIn = () => {
const tl = anime.timeline({
duration: 1800,
easing: 'easeOutQuart'
})
// Title: Houses
tl.add({
targets: '.place__title_houses',
translateY: ['150%', 0],
duration: animDuration
})
// Title: Of
tl.add({
targets: '.place__title_of',
opacity: [0, 1],
duration: 800
}, 550)
// Title: Place name
tl.add({
targets: '.place__title_name',
translateY: ['150%', 0],
duration: animDuration
}, 200)
// Switcher link
tl.add({
targets: '.place__title .button-control',
scale: [0.95, 1],
opacity: [0, 1],
duration: animDuration
}, 700)
// Illustration
tl.add({
targets: '.place__illustration',
scale: [1.075, 1],
opacity: [0, 1],
duration: animDuration
}, 200)
// Description
tl.add({
targets: '.place__description',
opacity: [0, 1],
translateY: [24, 0],
duration: animDuration
}, 800)
// Play
tl.play()
}

View File

@@ -1,17 +1,33 @@
<script>
import { onMount } from 'svelte'
import { lettersToSpan } from '../utils/functions'
// Animations
import { animateIn } from '../animations/TitleSite.js'
/*
** Run code on component mount
*/
onMount(() => {
// Entering transition
animateIn()
})
</script>
<div class="title-location title-location--inline">
<div role="heading" aria-level="1" aria-label="Houses" data-aos="letters-translate-bottom">
<div role="heading" aria-level="1" aria-label="Houses">
<div class="anim-mask">
{@html lettersToSpan('Houses')}
</div>
</div>
<em>of the</em>
<em>
<span>of</span>
<span>the</span>
</em>
<div aria-label="World" data-aos="letters-translate-bottom">
<div aria-label="World">
<div class="anim-mask">
{@html lettersToSpan('World')}
</div>

View File

@@ -40,7 +40,7 @@
// const globe = document.querySelector('.globe img')
// const places = document.querySelectorAll('.globe .pin--place')
// // Init function
// Init function
// init = () => {
// mapWidth = globe.getBoundingClientRect().width
// mapHeight = globe.getBoundingClientRect().height
@@ -59,7 +59,7 @@
// init()
// }
// // init()
// init()
})
</script>

View File

@@ -3,7 +3,7 @@
export let location
</script>
<div class="location" data-aos="location">
<div class="location">
<a href="/location/{location.country.slug}/{location.slug}" rel="prefetch" sapper-noscroll>
<img src={location.country.flag.full_url} alt="Flag of {location.country.name}">
<div class="anim-mask mask-city">

View File

@@ -3,9 +3,6 @@
import { fly } from 'svelte/transition'
import { quartOut } from 'svelte/easing'
import { site, currentLocation } from '../utils/store'
// Dependencies
import * as basicScroll from 'basicscroll'
import { getThumbnail, formatDate } from '../utils/functions'
// Props and variables
@@ -29,27 +26,27 @@
*/
onMount(() => {
// Parallax on photo when the image has been loaded
const parallaxNumber = basicScroll.default.create({
elem: photoElement.querySelector('.photo__number'),
direct: photoElement,
from: 'top-bottom',
to: 'bottom-top',
props: {
'--translate': {
from: '-75%',
to: '-25%'
}
}
})
parallaxNumber.start()
parallaxNumber.calculate()
parallaxNumber.update()
// const parallaxNumber = basicScroll.default.create({
// elem: photoElement.querySelector('.photo__number'),
// direct: photoElement,
// from: 'top-bottom',
// to: 'bottom-top',
// props: {
// '--translate': {
// from: '-75%',
// to: '-25%'
// }
// }
// })
// parallaxNumber.start()
// parallaxNumber.calculate()
// parallaxNumber.update()
})
</script>
<div class="photo"
bind:this={photoElement}
transition:fly="{{ y: 40, duration: 1000, easing: quartOut }}"
in:fly="{{ y: 40, duration: 1000, easing: quartOut }}"
>
<div class="photo__location wrap">
<div class="wrapper">

View File

@@ -6,6 +6,9 @@
const dispatch = createEventDispatcher()
const { page } = stores()
// Animations
import { animateIn } from '../animations/carousel'
// Components
import IconArrow from '../atoms/IconArrow'
@@ -89,6 +92,9 @@
** Run code on browser only
*/
onMount(() => {
// Entering transition
animateIn()
// Hover function
hover = event => {
const button = event.currentTarget.querySelector('button')

View File

@@ -1,17 +1,17 @@
<script>
import { onMount } from 'svelte'
import { flip } from 'svelte/animate'
import { crossfadeReceive, crossfadeSend } from '../utils/animations'
import { receive, send } from '../animations/crossfade'
import { locations, countries, continents } from '../utils/store'
// Dependencies
import AOS from 'aos'
import { throttle } from '../utils/functions'
// Components
import Button from '../atoms/Button'
import Location from '../molecules/Location'
// Animations
import { animateIn } from '../animations/Locations'
// Variables
const transitionDuration = 800
let filterLocations
@@ -31,13 +31,11 @@
/*
** Run code on browser only
** Run code on component mount
*/
onMount(() => {
// Scroll apparitions
if (process.browser) {
AOS.init()
}
// Entering transition
animateIn()
})
</script>
@@ -62,8 +60,8 @@
<div class="browse__locations" id="locations_list">
{#each filteredLocations as location (location.id)}
<div animate:flip="{{ duration: transitionDuration }}"
in:crossfadeReceive="{{ key: location.id }}"
out:crossfadeSend="{{ key: location.id }}"
in:receive="{{ key: location.id }}"
out:send="{{ key: location.id }}"
>
<Location {location} />
</div>

View File

@@ -2,9 +2,6 @@
import { onMount } from 'svelte'
import { site } from '../utils/store'
// Dependencies
import AOS from 'aos'
// Components
import IconArrow from '../atoms/IconArrow'
import TitleSite from '../atoms/TitleSite'
@@ -21,10 +18,7 @@
** Run code on browser only
*/
onMount(() => {
// Scroll apparitions
if (process.browser) {
AOS.init()
}
})
</script>

View File

@@ -1,11 +1,5 @@
<script context="module">
import {
apiEndpoints,
site,
continents,
countries,
locations
} from '../utils/store'
import { apiEndpoints, } from '../utils/store'
export async function preload (page, session) {
const req = await this.fetch(apiEndpoints.gql, {
@@ -66,11 +60,27 @@
</script>
<script>
// Manipulate countries data
import { onMount } from 'svelte'
import {
site,
continents,
countries,
locations
} from '../utils/store'
// Components
import Transition from '../utils/Transition'
// Props
export const segment = null
/*
** Manipulate data
*/
// Replace each countrie's continent by the database
$countries.forEach(count => count.continent = $continents.find(cont => cont.id === count.continent.id))
// Manipulate continents data
// Push each country to its continent
$countries.forEach(country => {
const continent = $continents.find(cont => cont.id === country.continent.id)
@@ -78,13 +88,15 @@
!continent.countries.includes(country) && continent.countries.push(country)
})
// Manipulate locations data
// Replace each location's country by the database
$locations.forEach(loc => loc.country = $countries.find(cont => cont.id === loc.country.id))
</script>
<style lang="scss" global>
@import "../style/style.scss";
</style>
<slot></slot>
<Transition />

View File

@@ -3,12 +3,11 @@
import {
site,
currentLocation,
currentPhotos
currentPhotos,
pageReady,
pageTransition
} from '../utils/store'
// Depencencies
import AOS from 'aos'
// Components
import IconArrow from '../atoms/IconArrow'
import TitleSite from '../atoms/TitleSite'
@@ -17,6 +16,11 @@
import Footer from '../organisms/Footer'
import SocialMetas from '../utils/SocialMetas'
// Animations
import { animateIn } from '../animations/page'
pageTransition.onAnimationEnd = animateIn
// Reset current location if existing
$: {
if ($currentLocation) currentLocation.set()
@@ -25,13 +29,11 @@
/*
** Run code on browser only
** Run code when mounted
*/
onMount(() => {
// Scroll apparitions
if (process.browser) {
AOS.init()
}
// Page is loaded
pageReady.set(true)
})
</script>
@@ -57,7 +59,7 @@
<TitleSite />
</div>
<div class="page__description style-description">
<div class="page__description page__part style-description">
<p>{$site.explore_globe}</p>
</div>
</div>

View File

@@ -1,9 +1,6 @@
<script>
import { onMount } from 'svelte'
import { site } from '../utils/store'
// Dependencies
import AOS from 'aos'
import { site, pageReady } from '../utils/store'
// Components
import IconArrow from '../atoms/IconArrow'
@@ -13,18 +10,17 @@
import Footer from '../organisms/Footer'
import SocialMetas from '../utils/SocialMetas'
// Categories
$: credits = $site.credits_list
// Animations
import { animateIn } from '../animations/page'
pageTransition.onAnimationEnd = animateIn
/*
** Run code on browser only
** Run code when mounted
*/
onMount(() => {
// Scroll apparitions
if (process.browser) {
AOS.init()
}
// Page is loaded
pageReady.set(true)
})
</script>
@@ -41,7 +37,7 @@
<section class="page">
<div class="wrap">
<div class="page__top">
<div class="page__top page__part">
<a href="/" class="button-control button-control--pink dir-left">
<IconArrow direction="left" color="#fff" class="icon" />
<IconArrow direction="left" color="#fff" class="icon" hidden="true" />
@@ -50,12 +46,12 @@
<TitleSite />
</div>
<div class="page__description style-description">
<div class="page__description page__part style-description">
<p>{$site.credits_text}</p>
</div>
<div class="page__list">
{#each credits as category}
<div class="page__list page__part">
{#each $site.credits_list as category}
<div class="page__category">
<h2 class="title-category">{category.name}</h2>
{#each category.credits as person}

View File

@@ -1,7 +1,5 @@
<script context="module">
import {
apiEndpoints,
} from '../utils/store'
import { apiEndpoints } from '../utils/store'
// Preload data
export async function preload (page, session) {
@@ -18,12 +16,16 @@
<script>
import { onMount } from 'svelte'
import { site, currentLocation, currentPhotos } from '../utils/store'
import {
site,
currentLocation,
currentPhotos,
pageReady,
pageTransition
} from '../utils/store'
import { lettersToSpan } from '../utils/functions'
// Dependencies
import * as basicScroll from 'basicscroll'
import AOS from 'aos'
import zenscroll from 'zenscroll'
// Components
@@ -35,6 +37,14 @@
import Locations from '../organisms/Locations'
import Footer from '../organisms/Footer'
import SocialMetas from '../utils/SocialMetas'
import Transition from '../utils/Transition'
// Animations
import { animateIn } from '../animations/index'
pageTransition.onAnimationEnd = animateIn
// Props and variables
export let photos
// Reset current location if existing
$: {
@@ -42,48 +52,13 @@
if ($currentPhotos) currentPhotos.set()
}
// Props and variables
export let photos
let appearing
/*
** Run code on browser only
** Run code when mounted
*/
onMount(() => {
if (process.browser) {
// Scroll apparitions
AOS.init()
// Parallax titles
const titleHouses = document.getElementById('title-houses')
const scrollTitleHouses = basicScroll.default.create({
elem: titleHouses,
direct: titleHouses,
from: 0,
to: window.innerHeight * 0.6,
props: {
'--translateX': {
from: '-3%',
to: '-20%'
}
}
}).start()
const titleWorld = document.getElementById('title-world')
const scrollTitleWorld = basicScroll.default.create({
elem: titleWorld,
direct: titleWorld,
from: document.querySelector('.explore__description').getBoundingClientRect().top,
to: document.querySelector('#title-world').getBoundingClientRect().bottom * 1.1,
props: {
'--translateX': {
from: '4%',
to: '-4%'
}
}
}).start()
}
// Page is loaded
pageReady.set(true)
})
</script>
@@ -100,14 +75,14 @@
<section class="intro">
<div class="anim-mask">
<div class="anim title-parallax" id="title-houses" data-aos="letters-translate-top" data-aos-once="true">
<div class="anim title-parallax" id="title-houses">
<h1 class="title-massive" aria-label="Houses">
{@html lettersToSpan('Houses')}
</h1>
</div>
</div>
<div class="wrap">
<div class="wrap" id="intro-description">
<div class="intro__description style-description">
<p>{$site.description}</p>
@@ -117,11 +92,13 @@
</div>
</div>
<div id="intro-carousel">
<Carousel {photos} />
</div>
</section>
<section class="explore explore--homepage">
<div class="of" aria-label="of" data-aos="letters-translate-bottom" data-aos-once="true">
<div class="of" id="title-of" aria-label="of">
<div class="anim-mask">
{@html lettersToSpan('of')}
</div>
@@ -134,7 +111,7 @@
<InteractiveGlobe />
<div class="anim-mask anim-title">
<h1 class="title-massive title-parallax" id="title-world" aria-label="World" data-aos="letters-translate-bottom" data-aos-once="true">
<h1 class="title-massive title-parallax" id="title-world" aria-label="World">
{@html lettersToSpan('World')}
</h1>
</div>

View File

@@ -1,12 +1,5 @@
<script context="module">
import {
apiEndpoints,
site,
locations,
currentLocation,
currentPhotos
} from '../../../utils/store'
import { stores } from '@sapper/app'
import { apiEndpoints } from '../../../utils/store'
// Preload data
export async function preload (page, session) {
@@ -22,11 +15,20 @@
<script>
import { onMount } from 'svelte'
import { stores } from '@sapper/app'
import {
site,
locations,
currentLocation,
currentPhotos,
pageReady,
pageTransition
} from '../../../utils/store'
import { formatDate, relativeTime, getThumbnail } from '../../../utils/functions'
const { page } = stores()
// Dependencies
import AOS from 'aos'
import lazySizes from 'lazysizes'
// Components
import IconGlobe from '../../../atoms/IconGlobe'
@@ -39,6 +41,10 @@
import Footer from '../../../organisms/Footer'
import SocialMetas from '../../../utils/SocialMetas'
// Animations
import { animateIn } from '../../../animations/place'
pageTransition.onAnimationEnd = animateIn
// Props and variables
export let photos
let layoutSetting
@@ -70,18 +76,15 @@
/*
** Run code on browser only
** Run code when mounted
*/
onMount(() => {
// Page is loaded
pageReady.set(true)
// Get layout setting from storage
layoutSetting = localStorage.getItem('photosLayout')
if (process.browser) {
// Scroll apparitions
AOS.init()
}
})
</script>
<svelte:head>
@@ -95,6 +98,7 @@
/>
</svelte:head>
<section class="place">
<div class="place__title">
<h1 class="title-location title-location--big" aria-label="Houses of {location.name}">

View File

@@ -1,11 +1,6 @@
<script context="module">
import { stores } from '@sapper/app'
import {
apiEndpoints,
locations,
currentLocation,
currentPhotos
} from '../../../../utils/store'
import { apiEndpoints } from '../../../../utils/store'
// Define either to preload data or use the store
let preloaded
@@ -16,7 +11,7 @@
export async function preload (page, session) {
// Load the photos if not loaded
if (!preloaded) {
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 req = await this.fetch(`${apiEndpoints.rest}/items/photos?fields=id,name,slug,date,image.*,location.*,location.country.*,created_on,modified_on&filter[location.slug][rlike]=%${page.params.location}%`)
const photos = await req.json()
return {
photos: photos.data
@@ -31,7 +26,14 @@
<script>
import { onMount, createEventDispatcher } from 'svelte'
import { goto } from '@sapper/app'
import {
site,
locations,
currentLocation,
currentPhotos,
pageReady,
pageTransition
} from '../../../../utils/store'
import { getThumbnail } from '../../../../utils/functions'
const { page } = stores()
const dispatch = createEventDispatcher()
@@ -42,24 +44,25 @@
import Carousel from '../../../../organisms/Carousel'
import SocialMetas from '../../../../utils/SocialMetas'
// Animations
// import { animateIn } from '../../../animations/photoPage'
// pageTransition.onAnimationEnd = animateIn
// Props
export let photos
// Variables
let windowWidth
let currentPhoto
let currentPhoto = photos.find(photo => photo.slug === $page.params.photo)
// Define current location
const location = (!$currentLocation) ? $locations.find(loc => loc.slug === $page.params.location) : $currentLocation
// Update store current location informations
if (!$currentLocation) currentLocation.set(location)
// Update store current location
if (!$currentLocation) currentLocation.set($locations.find(loc => loc.slug === $page.params.location))
if (!$currentPhotos) currentPhotos.set(photos)
// The photo has changed from the carousel
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) {
@@ -82,22 +85,16 @@
/*
!!! 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 when mounted
*/
onMount(() => {
// Page is loaded
pageReady.set(true)
/*
!!! TODO:
- Change the title with the current photo name and update Metas (with window location url)
*/
// console.log(currentPhoto)
dispatch('changeUrl', {
page: $page
@@ -106,14 +103,14 @@
</script>
<svelte:head>
<title>Houses Of Photos of {location.name}, {location.country.name}</title>
<meta name="description" content={location.description}>
<title>{$site.seo_name} Photos of {$currentLocation.name}, {$currentLocation.country.name}</title>
<meta name="description" content="{$site.seo_name} {$currentLocation.name} {$currentLocation.description}">
<SocialMetas
title="Houses Of - Beautiful houses of {location.name}, {location.country.name}"
description="Houses Of {location.name} {location.description}"
title="{$site.seo_name} - Beautiful homes of {$currentLocation.name}, {$currentLocation.country.name}"
description="{$site.seo_name} {$currentLocation.name} {$currentLocation.description}"
image={getThumbnail(currentPhoto.image.private_hash, 1200, 630)}
url="//{$page.host.split(':3000')[0]}/viewer/{currentPhoto.location.country.slug}/{currentPhoto.location.slug}/{currentPhoto.slug}"
/>
<!-- 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:window bind:innerWidth={windowWidth} />
@@ -129,7 +126,7 @@
<circle cx="50%" cy="50%" r="{windowWidth >= 768 ? 32 : 24}px"></circle>
</svg>
</a>
<a href="/location/{location.country.slug}/{location.slug}" class="button-control button-control--pink dir-bottom" aria-label="Close">
<a href="/location/{$currentLocation.country.slug}/{$currentLocation.slug}" class="button-control button-control--pink dir-bottom" aria-label="Close">
<IconCross color="#fff" width="18" class="icon" />
<IconCross color="#fff" width="18" class="icon" hidden="true" />
</a>
@@ -137,7 +134,7 @@
</div>
<Carousel
{photos}
photos={photos}
viewer={true}
init={$page}
on:photoChange={photoChanged}

View File

@@ -1,10 +1,19 @@
/* ==========================================================================
PARALLAX EFFECTS
PAGE TRANSITION
========================================================================== */
// Parallax title: Translate X
.title-parallax {
transform: translateX(var(--translateX));
will-change: transform;
.transition {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1000;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
background-color: $color-primary;
will-change: height, transform, padding-top, padding-bottom;
}
@@ -15,124 +24,164 @@
.anim-mask {
display: block;
overflow: hidden;
}
// Translate each letter from a direction
[data-aos="letters-translate-top"],
[data-aos="letters-translate-bottom"] {
white-space: nowrap;
span {
display: inline-block;
transition: transform 1000ms $ease-quart;
will-change: transform;
@for $i from 1 to 8 {
&:nth-child(#{$i}) {
transition-delay: $i * 40ms;
}
}
}
&.aos-animate {
span {
transform: translate(0, 0);
}
}
}
[data-aos="letters-translate-top"] {
span {
transform: translate(0, -100%);
}
}
[data-aos="letters-translate-bottom"] {
span {
transform: translate(0, 100%);
}
}
// Carousel prev/active/next photos
[data-aos="carousel-prev"] {
transform: translate(-16%, -4%) rotate(-3deg) scale(0.8);
opacity: 0;
transition-duration: 1.6s;
transition-delay: 450ms;
// Translate each letter from a direction
// [data-aos="letters-translate-top"],
// [data-aos="letters-translate-bottom"] {
// white-space: nowrap;
&.aos-animate {
transform: translate(-9%, -1%) rotate(-1deg) scale(0.9);
opacity: 1;
}
}
[data-aos="carousel-active"] {
transform: scale(0.8);
opacity: 0;
transition-delay: 400ms;
&.aos-animate {
transform: scale(1);
opacity: 1;
}
}
[data-aos="carousel-next"] {
transform: translate(16%, -4%) rotate(3deg) scale(0.8);
opacity: 0;
transition-duration: 1.6s;
transition-delay: 500ms;
&.aos-animate {
transform: translate(9%, -1%) rotate(1deg) scale(0.9);
opacity: 1;
}
}
// span {
// display: inline-block;
// transition: transform 1s $ease-quart;
// will-change: transform;
// Scale down and Fade in
[data-aos="scale-down-fade-in"] {
transform: scale(1.1);
opacity: 0;
transition: transform 2s $ease-quart, opacity 2s $ease-quart;
will-change: transform, opacity;
// @for $i from 1 to 8 {
// &:nth-child(#{$i}) {
// transition-delay: $i * 40ms;
// }
// }
// }
&.aos-animate {
transform: scale(1);
opacity: 1;
}
}
// &.aos-animate {
// span {
// transform: translate(0, 0);
// }
// }
// }
// [data-aos="letters-translate-top"] {
// span {
// transform: translate(0, -100%);
// }
// }
// [data-aos="letters-translate-bottom"] {
// span {
// transform: translate(0, 100%);
// }
// }
// // Carousel prev/active/next photos
// [data-aos="carousel-prev"] {
// transform: translate(-16%, -4%) rotate(-3deg) scale(0.8);
// opacity: 0;
// transition-duration: 1.6s;
// transition-delay: 0.45s;
// &.aos-animate {
// transform: translate(-9%, -1%) rotate(-1deg) scale(0.9);
// opacity: 1;
// }
// }
// [data-aos="carousel-active"] {
// transform: scale(0.8);
// opacity: 0;
// transition-delay: 0.4s;
// &.aos-animate {
// transform: scale(1);
// opacity: 1;
// }
// }
// [data-aos="carousel-next"] {
// transform: translate(16%, -4%) rotate(3deg) scale(0.8);
// opacity: 0;
// transition-duration: 1.6s;
// transition-delay: 0.5s;
// &.aos-animate {
// transform: translate(9%, -1%) rotate(1deg) scale(0.9);
// opacity: 1;
// }
// }
// Location reveal
[data-aos="location"] {
img {
opacity: 0;
transform: scale(1.15);
transition: opacity 0.6s $ease-quart, transform 0.6s $ease-quart;
will-change: opacity, transform;
}
// // Scale down and Fade in
// [data-aos="scale-down-fade-in"] {
// transform: scale(1.1);
// opacity: 0;
// transition: transform 2s $ease-quart, opacity 2s $ease-quart;
// will-change: transform, opacity;
h3, p {
transform: translateY(150%);
transition: transform 0.6s $ease-quart;
will-change: transform;
}
h3 {
transition: all 0.6s $ease-quart;
transition-delay: 100ms;
}
p {
transition-delay: 200ms;
}
// &.aos-animate {
// transform: scale(1);
// opacity: 1;
// }
// }
&.aos-animate {
img {
opacity: 1;
transform: scale(1);
}
h3, p {
transform: translateY(0);
}
}
}
// // Place title reveal
// [data-aos="place-title"] {
// .anim-fade {
// opacity: 0;
// transition: opacity 0.9s $ease-inout;
// transition-delay: 1.8s;
// will-change: opacity;
// }
// .anim-translate {
// transform: translateY(120%);
// transition: transform 0.9s $ease-quart;
// transition-delay: 1.8s;
// will-change: transform;
// }
// .place__title_of {
// transition-delay: 2.0s;
// }
// .place__title_name {
// transition-delay: 2.1s;
// }
// .button-control {
// transition-delay: 2.2s;
// transition-duration: 0.6s;
// }
// &.aos-animate {
// .anim-fade {
// opacity: 1;
// }
// .anim-translate {
// transform: translateY(0);
// }
// }
// }
// // Location reveal
// [data-aos="location"] {
// img {
// opacity: 0;
// transform: scale(1.15);
// transition: opacity 0.6s $ease-quart, transform 0.6s $ease-quart;
// will-change: opacity, transform;
// }
// h3, p {
// transform: translateY(150%);
// transition: transform 0.6s $ease-quart;
// will-change: transform;
// }
// h3 {
// transition: all 0.6s $ease-quart;
// transition-delay: 0.1s;
// }
// p {
// transition-delay: 0.2s;
// }
// &.aos-animate {
// img {
// opacity: 1;
// transform: scale(1);
// }
// h3, p {
// transform: translateY(0);
// }
// }
// }

View File

@@ -59,6 +59,7 @@ button {
letter-spacing: -2vw;
pointer-events: none;
user-select: none;
will-change: transform;
@include breakpoint (lg) {
font-size: pxVW(700);

View File

@@ -57,8 +57,8 @@
height: 100%;
transform: scale($scale);
box-shadow: 0 pxVW(15) pxVW(60) rgba(#000, 0.3);
transition: transform $duration $ease-quart, opacity $duration / 2 $ease-quart;
will-change: transform, opacity;
transition: transform $duration $ease-quart, opacity ($duration / 2) $ease-quart;
will-change: transform, opacity, top, left;
@include breakpoint (sm) {
border-radius: $radius;

View File

@@ -32,9 +32,9 @@
flex-flow: row wrap;
@include breakpoint (sm) {
position: static;
// position: static;
justify-content: center;
margin: 24px 0 0;
margin: 24px 0;
}
// Tip message
@@ -56,6 +56,10 @@
a {
margin-left: 16px;
&:first-child {
margin-left: 0;
}
}
}
}
@@ -73,10 +77,12 @@
margin: 0;
@include breakpoint (sm) {
position: static;
transform: none;
margin-top: auto;
margin-bottom: auto;
// position: static;
// transform: none;
// margin-top: auto;
// margin-bottom: auto;
left: 50%;
transform: translate(-50%, -45%);
width: 85%;
min-height: 500px;
}

View File

@@ -0,0 +1,63 @@
<script>
import { stores } from '@sapper/app'
import { pageReady, animDurationLong, pageTransition } from './store'
const { page } = stores()
// Components
import TitleSite from '../atoms/TitleSite'
import IconGlobe from '../atoms/IconGlobe'
// Animations
import { animateIn, animateOut } from '../animations/Transition'
/*
** PAGE LOADING PROCESS
** 1. Set pageReady to false
** 1. Runs the Loader transition In
** ?. The next page changes the value of pageReady when mounted (or later)
** 2. The Loader detects the value change of pageReady
** 3. Hide the loader with transition Out
** 4. The Loader runs the page transition In via pageTransition
*/
let firstLoad = true
// 1. Watch page change
page.subscribe(() => {
// Run the loader animation (only after first load)
if (!firstLoad && process.browser) {
animateIn()
}
// Set pageReady to false (?)
pageReady.set(false)
})
// 2. Watch when loaded changes
pageReady.subscribe(loaded => {
if (loaded) {
setTimeout(() => {
// Scroll back to top of page
window.scrollTo(0,0)
// 3. Hide the loader
// Also sets firstLoad to false in order to show the second icon afterwards
animateOut(() => firstLoad = false)
}, 200) // This duration allows to not come over the transition In
// [OU ALORS] les pages changent la valeur de loaded plus tard
// 4. Run the page's transition in, but a little bit before the end of the loader
setTimeout(() => {
pageTransition.onAnimationEnd()
}, animDurationLong * 0.666 + 200)
}
})
</script>
<div class="transition" id="transition" aria-hidden="true">
<div class="loader">
{#if firstLoad}
<TitleSite />
{:else}
<IconGlobe width="44" color="#fff" animated="true" />
{/if}
</div>
</div>

View File

@@ -25,4 +25,15 @@ export let currentLocation = writable()
export let currentPhotos = writable()
// State
export let loaded = writable(false)
// export let ready = writable(false)
export let pageReady = writable(false)
export const pageTransition = {
onAnimationEnd () {}
}
/* ==========================================================================
Animation related
========================================================================== */
export const animDuration = 1400
export const animDurationLong = 1800