⚠️ Rework completely how transitions works

- Use Svelte/Sapper native if and transitions to show either the page content or the loader, then load each page animationIn
- Code is safe on SSR side, using process.browser on this if
- The <main> element is on position absolute to fade nicely the different pages
- Code cleaning
This commit is contained in:
2020-04-03 22:53:43 +02:00
parent 76e2f8242e
commit 7e0d1e33fb
21 changed files with 300 additions and 315 deletions

View File

@@ -2,7 +2,6 @@
export let href = '#'
export let type = 'a'
export let text = ''
export let noScroll = null
</script>
{#if type === 'button'}
@@ -14,7 +13,7 @@
</button>
{:else}
<a {href} class={$$props.class ? $$props.class : 'button'} sapper-noscroll={noScroll} on:click>
<a {href} class={$$props.class ? $$props.class : 'button'} on:click>
<slot></slot>
<div class="text" data-text={text}>
<span>{text}</span>

View File

@@ -1,10 +1,9 @@
<script>
export let href = '#'
export let text = ''
export let noScroll = null
</script>
<a href={href} class="link-change" sapper-noscroll={noScroll}>
<a href={href} class="link-change">
{text}
<span class="icon">
<slot></slot>

View File

@@ -3,10 +3,9 @@
export let text = ''
export let target = null
export let rel = null
export let noScroll = null
</script>
<a class="link-translate" {href} {target} {rel} sapper-noscroll={noScroll}>
<a class="link-translate" {href} {target} {rel}>
<slot />
<div class="text" data-text={text}>
<span>{text}</span>

View File

@@ -6,28 +6,30 @@
import { animateIn } from 'animations/TitleSite'
// Props and variables
export let init = false
let scope
let mounted = false
let scope
/*
** Run code on component mount
** Run code when mounted
*/
onMount(() => {
animateIn(scope, init)
animateIn(scope)
mounted = true
})
</script>
<div class="title-location title-location--inline" bind:this={scope} style="opacity: {mounted ? 1 : 0}">
<div class="title-location title-location--inline"
bind:this={scope}
style="opacity: {mounted ? 1 : 0}"
>
<div role="heading" aria-level="1" aria-label="Houses">
<div class="anim-mask">
{@html charsToSpan('Houses')}
</div>
</div>
<em>
<em class="anim-mask">
<span>of</span>
<span>the</span>
</em>

View File

@@ -8,7 +8,7 @@
/*
** Run code on browser only
** Run code when mounted
*/
onMount(() => {
// Get layout setting from storage

View File

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

View File

@@ -46,7 +46,7 @@
<div class="photo__image wrap">
<div class="align">
<a href="/viewer/{location.country.slug}/{location.slug}/{photo.slug}" sapper-noscroll>
<a href="/viewer/{location.country.slug}/{location.slug}/{photo.slug}">
<picture class="photo__picture">
<source media="(min-width: 992px)" data-srcset={getThumbnail(private_hash, 1300)}>
<source media="(min-width: 768px)" data-srcset={getThumbnail(private_hash, 992)}>

View File

@@ -22,7 +22,7 @@
<div class="switcher {type}">
<div class="switcher__text" class:empty={!location}>
<a href="/" sapper-noscroll>
<a href="/">
{#if !location}
<span class="top">Houses</span>
<span class="bottom">
@@ -36,7 +36,7 @@
</div>
<div class="button-control button-control--dashed switcher__icon">
<a href="/choose" aria-label="Change the location" sapper-noscroll>
<a href="/choose" aria-label="Change the location">
<IconGlobe
color={type.includes('side') ? '#333' : '#fff'}
width={type.includes('side') ? 18 : 24}

View File

@@ -3,6 +3,8 @@
import { stores } from '@sapper/app'
import { currentLocation, fullscreen } from 'utils/store'
import { getThumbnail, formatDate } from 'utils/functions'
const dispatch = createEventDispatcher()
const { page } = stores()
// Dependencies
import SwipeListener from 'swipe-listener'
@@ -15,13 +17,9 @@
import Counter from 'atoms/Counter'
import PaginationDots from 'molecules/PaginationDots'
// Props
// Props and Variables
export let photos
export let viewer = false
// Variables
const dispatch = createEventDispatcher()
const { page } = stores()
let scope
let swiped
let currentIndex = 0

View File

@@ -17,7 +17,7 @@
<li>
<ul>
<li>
<LinkTranslate href="/credits" text="Credits" rel="prefetch" noScroll="true" />
<LinkTranslate href="/credits" text="Credits" rel="prefetch" />
</li>
{#if $site}
<li class="instagram">

View File

@@ -32,7 +32,7 @@
/*
** Run code on component mount
** Run code when mounted
*/
onMount(() => {
// Entering transition

View File

@@ -73,14 +73,11 @@
locations,
pageReady
} from 'utils/store'
const { page } = stores()
// Components
import Transition from 'utils/Transition'
import AnalyticsTracker from 'utils/AnalyticsTracker'
// Variables
const { page } = stores()
/*
** Manipulate data
@@ -92,7 +89,9 @@
$countries.forEach(country => {
const continent = $continents.find(cont => cont.id === country.continent.id)
continent.countries = []
!continent.countries.includes(country) && continent.countries.push(country)
if (!continent.countries.includes(country)) {
continent.countries.push(country)
}
})
// Replace each location's country by the database
@@ -107,12 +106,12 @@
<link rel="canonical" href={`https://${$page.host}${$page.path}`} />
</svelte:head>
<main class="housesof" style="opacity: { $pageReady ? 1 : 0}">
<main class="housesof"
class:is-transitioning={!$pageReady}
in:fade={{ duration: 600 }}
out:fade={{ duration: 600 }}
>
<slot></slot>
</main>
{#if process.env.CONFIG.TRANSITION === 'true'}
<Transition />
{/if}
<AnalyticsTracker {stores} id={process.env.CONFIG.GA_TRACKER_ID} />

View File

@@ -5,8 +5,7 @@
site,
currentLocation,
currentPhotos,
pageReady,
pageTransition
pageReady
} from 'utils/store'
// Components
@@ -15,11 +14,11 @@
import Globe from 'molecules/InteractiveGlobe'
import Locations from 'organisms/Locations'
import Footer from 'organisms/Footer'
import Transition from 'utils/Transition'
import SocialMetas from 'utils/SocialMetas'
// Animations
import { animateIn } from 'animations/page'
pageTransition.onAnimationEnd = animateIn
// Variables
const { page } = stores()
@@ -52,7 +51,8 @@
/>
</svelte:head>
<section class="page explore">
<Transition {animateIn}>
<section class="page explore">
<div class="wrap">
<div class="page__top">
<a href="/" class="button-control button-control--pink dir-left" rel="prefetch">
@@ -71,6 +71,7 @@
<Globe />
<Locations />
</section>
</section>
<Footer />
<Footer />
</Transition>

View File

@@ -1,7 +1,7 @@
<script>
import { onMount } from 'svelte'
import { stores } from '@sapper/app'
import { site, pageReady, pageTransition } from 'utils/store'
import { site, pageReady } from 'utils/store'
// Components
import IconArrow from 'atoms/IconArrow'
@@ -10,10 +10,10 @@
import InteractiveGlobe from 'molecules/InteractiveGlobe'
import Footer from 'organisms/Footer'
import SocialMetas from 'utils/SocialMetas'
import Transition from 'utils/Transition'
// Animations
import { animateIn } from 'animations/page'
pageTransition.onAnimationEnd = animateIn
// Variables
const { page } = stores()
@@ -39,7 +39,8 @@
/>
</svelte:head>
<section class="page">
<Transition>
<section class="page">
<div class="wrap">
<div class="page__top page__part">
<a href="/" class="button-control button-control--pink dir-left">
@@ -80,4 +81,5 @@
<InteractiveGlobe type="part" />
<Footer />
</section>
</section>
</Transition>

View File

@@ -31,8 +31,7 @@
site,
currentLocation,
currentPhotos,
pageReady,
pageTransition
pageReady
} from 'utils/store'
import { charsToSpan } from 'utils/functions'
@@ -53,7 +52,6 @@
// Animations
import { animateIn } from 'animations/index'
pageTransition.onAnimationEnd = animateIn
// Props and variables
export let photos = ''
@@ -86,7 +84,8 @@
/>
</svelte:head>
<section class="intro">
<Transition animateIn={animateIn}>
<section class="intro">
<div class="anim-mask">
<div class="anim title-parallax" id="title-houses">
<h1 class="title-massive" aria-label="Houses">
@@ -111,9 +110,9 @@
<Fullscreen />
{/if}
</div>
</section>
</section>
<section class="explore explore--homepage">
<section class="explore explore--homepage">
<div class="of" id="title-of" aria-label="of">
<div class="anim-mask">
{@html charsToSpan('of')}
@@ -133,6 +132,7 @@
</div>
<Locations />
</section>
</section>
<Footer />
<Footer />
</Transition>

View File

@@ -29,8 +29,7 @@
locations,
currentLocation,
currentPhotos,
pageReady,
pageTransition
pageReady
} from 'utils/store'
import { formatDate, relativeTime, getThumbnail } from 'utils/functions'
@@ -47,10 +46,10 @@
import Pagination from 'organisms/Pagination'
import Footer from 'organisms/Footer'
import SocialMetas from 'utils/SocialMetas'
import Transition from 'utils/Transition'
// Animations
import { animateIn } from 'animations/place'
pageTransition.onAnimationEnd = animateIn
// Props and variables
export let photos
@@ -108,8 +107,8 @@
/>
</svelte:head>
<section class="place">
<Transition {animateIn}>
<section class="place">
<div class="place__title">
<h1 class="title-location title-location--big" aria-label="Houses of {location.name}">
<span class="place__title_top anim-mask">
@@ -121,7 +120,7 @@
</span>
</h1>
<a href="/choose" class="button-control button-control--big button-control--dashed" aria-label="Change the location" sapper-noscroll>
<a href="/choose" class="button-control button-control--big button-control--dashed" aria-label="Change the location">
<span class="center">
<IconGlobe width="44" color="#fff" />
<span>Change</span>
@@ -140,7 +139,7 @@
{#if description}
<p>
Houses Of
<LinkChange href="/choose" text={location.name} noScroll="true">
<LinkChange href="/choose" text={location.name}>
<IconGlobeSmall width="14" color="#999" />
</LinkChange>
{description}
@@ -164,9 +163,9 @@
style="--url-desktop: url({illustration_desktop.full_url}); --url-mobile: url({illustration_mobile.full_url});"
/>
{/if}
</section>
</section>
<section class="photos photos--{layoutSetting || 'list'}">
<section class="photos photos--{layoutSetting || 'list'}">
<div class="photos__sidewrap wrap">
<aside class="photos__side">
<Switcher type="switcher--side" />
@@ -201,6 +200,7 @@
<p style="text-align: center; color: #333;">No photo for {location.name}</p>
</div>
{/if}
</section>
</section>
<Footer />
<Footer />
</Transition>

View File

@@ -35,8 +35,7 @@
locations,
currentLocation,
currentPhotos,
pageReady,
pageTransition
pageReady
} from 'utils/store'
import { getThumbnail } from 'utils/functions'
@@ -45,11 +44,11 @@
import IconCross from 'atoms/IconCross'
import Carousel from 'organisms/Carousel'
import Fullscreen from 'organisms/Fullscreen'
import Transition from 'utils/Transition'
import SocialMetas from 'utils/SocialMetas'
// Animations
import { animateIn } from 'animations/viewer'
pageTransition.onAnimationEnd = animateIn
// Props
export let photos
@@ -100,7 +99,8 @@
<svelte:window bind:innerWidth={windowWidth} />
<section class="viewer">
<Transition {animateIn}>
<section class="viewer">
<div class="viewer__top">
<p class="tip">Tap for fullscreen</p>
@@ -116,7 +116,7 @@
<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>
<a href="/" bind:this={gotoLink} aria-hidden="true" hidden class="hidden">&nbsp;</a>
</div>
<Carousel
@@ -126,4 +126,5 @@
/>
<Fullscreen />
</section>
</section>
</Transition>

View File

@@ -141,17 +141,22 @@ button {
align-items: center;
justify-content: center;
@include breakpoint (sm) {
flex-direction: row;
align-items: baseline;
}
em {
margin: 8px 0 4px;
@include breakpoint (sm) {
margin: 2px 16px 0 24px;
}
}
@include breakpoint (sm) {
flex-direction: row;
align-items: baseline;
span {
position: relative;
display: inline-block;
}
}
}
}

View File

@@ -1,5 +1,13 @@
// Main page
.housesof {
position: absolute;
top: 0;
left: 0;
right: 0;
transition: opacity 0.4s $ease-quart;
will-change: opacity;
&.is-transitioning {
opacity: 0;
}
}

View File

@@ -1,78 +1,54 @@
<script>
import { onMount } from 'svelte'
import { fly, fade } from 'svelte/transition'
import { quartInOut, quartOut } from 'svelte/easing'
import { stores } from '@sapper/app'
import {
pageReady,
pageTransition,
transitionLong,
transitionPanelIn,
transitionDelay
} from './store'
import { pageReady, firstLoad } from 'utils/store'
const { page } = stores()
// Animations
import { panelBackgroundOut } from 'animations/Transition'
// Components
import TitleSite from 'atoms/TitleSite'
import IconGlobe from 'atoms/IconGlobe'
// Animations
import { animateIn, animateOut } from 'animations/Transition'
// Check if path is excluded
const isExcluded = path => path.includes(['/viewer/'])
/*
** 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
*/
// Props and Variables
export let animateIn = scope => {}
let scope
let firstLoad = true
let previousPage = ''
let show = false
// 1. Watch page change
page.subscribe(page => {
// Run transition if page is not excluded
if (!isExcluded(previousPage) || !isExcluded(page.path)) {
// Run the loader animation (first load only)
if (!firstLoad) {
animateIn(scope)
}
// Reset pageReady when changing page
pageReady.set(false)
}
// Update page for viewer navigation checking
previousPage = page.path
})
// 2. Watch when loaded changes
// Listen for when a route is mounted
pageReady.subscribe(loaded => {
if (loaded) {
// 3. Hide the loader and set firstLoad to false (in order to show the second icon afterwards)
animateOut(scope, () => firstLoad = false)
// Scroll back to top of page
setTimeout(() => window.scrollTo(0,0), transitionDelay)
// 4. Run page entering animation
pageTransition.onAnimationEnd()
setTimeout(() => {
show = true
firstLoad.set(false)
setTimeout(() => animateIn(scope), 1)
}, 1000)
}
})
</script>
<div class="transition" id="transition" aria-hidden="true" bind:this={scope}>
<div class="transition__loader">
{#if firstLoad}
<TitleSite init="true" />
{#if show || !process.browser}
<slot></slot>
{:else}
<div class="transition" id="transition" aria-hidden="true" bind:this={scope}>
<div class="transition__loader"
in:fly={{ y: 24, duration: 800, easing: quartOut }}
out:fly={{ y: -window.innerHeight/2, duration: 1400, easing: quartInOut }}
>
{#if $firstLoad}
<TitleSite />
{:else}
<IconGlobe width="44" color="#fff" animated="true" />
{/if}
</div>
<div class="transition__background" />
<div class="transition__background"
in:fade={{ duration: 600, easing: quartInOut }}
out:panelBackgroundOut={{ duration: 1400 }}
/>
</div>
{/if}

View File

@@ -1,4 +1,3 @@
// Svelte
import { writable } from 'svelte/store'
// Define environment
@@ -25,17 +24,14 @@ export let currentLocation = writable()
export let currentPhotos = writable()
// State
export let pageReady = writable(false)
export const pageTransition = {
onAnimationEnd () {}
}
export let pageReady = writable(false, () => {})
export let firstLoad = writable(true)
export let fullscreen = writable()
/* ==========================================================================
Animation related
========================================================================== */
export const transitionDelay = 1400
export const transitionNormal = 1400
export const transitionLong = 1800
export const transitionPanelIn = 700
export const animDelay = 800
export const animDuration = 1400
export const animDurationLong = 1800