Add photos reveal on scroll and loading on Photos page

This commit is contained in:
2021-11-17 21:44:59 +01:00
parent 78cacbcd46
commit d37a88f1ae
2 changed files with 151 additions and 23 deletions

View File

@@ -2,17 +2,19 @@
import { page } from '$app/stores' import { page } from '$app/stores'
import { browser } from '$app/env' import { browser } from '$app/env'
import { goto } from '$app/navigation' import { goto } from '$app/navigation'
import { getContext } from 'svelte'
import { fly } from 'svelte/transition' import { fly } from 'svelte/transition'
import { getContext, onMount } from 'svelte'
import { quartOut } from 'svelte/easing' import { quartOut } from 'svelte/easing'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime.js' import relativeTime from 'dayjs/plugin/relativeTime.js'
import { map, lerp, throttle } from '$utils/functions' import { map, lerp, throttle } from '$utils/functions'
// Components // Components
import Metas from '$components/Metas.svelte' import Metas from '$components/Metas.svelte'
import SplitText from '$components/SplitText.svelte'
import IconEarth from '$components/atoms/IconEarth.svelte' import IconEarth from '$components/atoms/IconEarth.svelte'
import Button from '$components/atoms/Button.svelte' import Button from '$components/atoms/Button.svelte'
import Image from '$components/atoms/Image.svelte' import Image from '$components/atoms/Image.svelte'
import ScrollingTitle from '$components/atoms/ScrollingTitle.svelte'
import DiscoverText from '$components/atoms/DiscoverText.svelte' import DiscoverText from '$components/atoms/DiscoverText.svelte'
import PostCard from '$components/molecules/PostCard.svelte' import PostCard from '$components/molecules/PostCard.svelte'
import Select from '$components/molecules/Select.svelte' import Select from '$components/molecules/Select.svelte'
@@ -27,10 +29,17 @@
const { countries }: any = getContext('global') const { countries }: any = getContext('global')
let buttonReset: HTMLElement let introEl: HTMLElement
// let buttonShuffle: HTMLElement let filtersEl: HTMLElement
let photosContentEl: HTMLElement
let photosGridEl: HTMLElement
let observerPhotos: IntersectionObserver
let mutationPhotos: MutationObserver
let scrollY: number let scrollY: number
let innerWidth: number, innerHeight: number let innerWidth: number, innerHeight: number
let scrolledPastIntro: boolean
let scrollDirection = 0
let lastScrollTop = 0
/** /**
@@ -38,8 +47,8 @@
*/ */
const urlFiltersParams = new URLSearchParams() const urlFiltersParams = new URLSearchParams()
let filtered: boolean let filtered: boolean
let filterCountry: any = $page.query.get('country') || defaultCountry let filterCountry = $page.query.get('country') || defaultCountry
let filterSort: string = $page.query.get('sort') || defaultSort let filterSort = $page.query.get('sort') || defaultSort
let countryFlagId: string let countryFlagId: string
$: filtered = filterCountry !== defaultCountry || filterSort !== defaultSort $: filtered = filterCountry !== defaultCountry || filterSort !== defaultSort
$: latestPhoto = photos[0] $: latestPhoto = photos[0]
@@ -140,6 +149,7 @@
limit: ${import.meta.env.VITE_GRID_INCREMENT}, limit: ${import.meta.env.VITE_GRID_INCREMENT},
page: ${page}, page: ${page},
) { ) {
id
title title
slug slug
image { image {
@@ -188,6 +198,77 @@
currentPhotosAmount += newPhotos.length currentPhotosAmount += newPhotos.length
} }
} }
/**
* Detect scroll passed intro
*/
$: if (browser && scrollY) {
// Detect scroll direction
throttle(() => {
scrollDirection = scrollY > lastScrollTop ? 1 : -1
lastScrollTop = scrollY
}, 50)
// Scrolled past grid of photos
if (scrollY > photosContentEl.offsetTop) {
if (!scrolledPastIntro) {
introEl.classList.add('is-passed')
filtersEl.classList.add('is-over')
}
scrolledPastIntro = true
} else {
if (scrolledPastIntro) {
introEl.classList.remove('is-passed')
filtersEl.classList.remove('is-over')
}
scrolledPastIntro = false
}
// Show/hide filters bar when scrolling back up
filtersEl.classList.toggle('is-visible', scrollDirection < 0)
}
onMount(() => {
// Photos IntersectionObserver
observerPhotos = new IntersectionObserver(entries => {
entries.forEach(({ isIntersecting, target }: IntersectionObserverEntry) => {
target.classList.toggle('is-visible', isIntersecting)
// Run effect once
isIntersecting && observerPhotos.unobserve(target)
})
}, {
threshold: 0,
rootMargin: '0px 0px 0px'
})
// Photos MutationObserver
mutationPhotos = new MutationObserver((mutationsList, observer) => {
// Use traditional 'for loops' for IE 11
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed.')
mutation.addedNodes.forEach((item: HTMLElement) => observerPhotos.observe(item))
}
}
})
mutationPhotos.observe(photosGridEl, {
childList: true,
})
// Observe existing elements
const existingPhotos = photosGridEl.querySelectorAll('.photo')
existingPhotos.forEach(el => observerPhotos.observe(el))
// Destroy
return () => {
observerPhotos && observerPhotos.disconnect()
mutationPhotos && mutationPhotos.disconnect()
}
})
</script> </script>
<Metas <Metas
@@ -202,13 +283,18 @@
/> />
<main class="photos"> <main class="photos"
<section class="photos__intro"> in:fade={{ duration: DURATION.PAGE_IN, delay: DURATION.PAGE_OUT }}
<h1 class="title-huge">Houses</h1> out:fade={{ duration: DURATION.PAGE_OUT }}
>
<section class="photos__intro" bind:this={introEl}>
<ScrollingTitle tag="h1" text="Houses">
<SplitText text="Houses" mode="chars" />
</ScrollingTitle>
<DiscoverText /> <DiscoverText />
<div class="filter"> <div class="filter" bind:this={filtersEl}>
<span class="text-label filter__label">Filter photos</span> <span class="text-label filter__label">Filter photos</span>
<div class="filter__bar"> <div class="filter__bar">
@@ -274,27 +360,22 @@
<div class="filter__actions"> <div class="filter__actions">
{#if filtered} {#if filtered}
<button class="reset button-link" bind:this={buttonReset} <button class="reset button-link"
on:click={resetFiltered} on:click={resetFiltered}
transition:fly={{ y: 4, duration: 600, easing: quartOut }} transition:fly={{ y: 4, duration: 600, easing: quartOut }}
> >
Reset Reset
</button> </button>
{/if} {/if}
<!-- <button class="shuffle" bind:this={buttonShuffle}>
<svg width="14" height="14" viewBox="0 0 14 14" fill="#3C0576" xmlns="http://www.w3.org/2000/svg" aria-label="Shuffle icon">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.8168 13.7928C10.565 13.5166 10.565 13.0688 10.8168 12.7925L11.6371 11.8927C10.0729 11.7978 8.9174 11.2494 8.00077 10.4379C7.01752 9.56747 6.3426 8.41893 5.73606 7.382L5.72202 7.358C5.10114 6.29653 4.5499 5.35411 3.79106 4.65375C3.05738 3.97661 2.10408 3.50731 0.644748 3.50731C0.288663 3.50731 0 3.19063 0 2.8C0 2.40936 0.288666 2.09269 0.644751 2.09269C2.39889 2.0927 3.64837 2.6734 4.62121 3.57126C5.53188 4.41176 6.17567 5.5133 6.76119 6.51514L6.82133 6.61801C7.44316 7.68108 8.01476 8.63254 8.81058 9.33707C9.55258 9.99396 10.5204 10.4595 11.9721 10.491L10.8168 9.22362C10.565 8.9474 10.565 8.49956 10.8168 8.22333C11.0686 7.94711 11.4768 7.94711 11.7286 8.22333L13.8112 10.5079C14.0629 10.7842 14.0629 11.232 13.8112 11.5082L11.7286 13.7928C11.4768 14.0691 11.0686 14.0691 10.8168 13.7928ZM10.8168 5.77667C10.565 5.50045 10.565 5.0526 10.8168 4.77638L11.972 3.50905C10.5204 3.54053 9.55258 4.00608 8.81058 4.66297C8.50789 4.93094 8.23764 5.23463 7.98472 5.5666C7.73665 5.18234 7.48128 4.77156 7.2176 4.37967C7.45664 4.09011 7.71575 3.81442 8.00077 3.5621C8.9174 2.75061 10.0729 2.2022 11.6371 2.10738L10.8168 1.20745C10.565 0.931231 10.565 0.483387 10.8168 0.207166C11.0686 -0.0690553 11.4768 -0.0690553 11.7286 0.207166L13.8112 2.49177C14.0629 2.768 14.0629 3.21584 13.8112 3.49206L11.7286 5.77667C11.4768 6.05289 11.0686 6.05289 10.8168 5.77667ZM0 11.2C0 11.5907 0.288666 11.9073 0.644751 11.9073C2.39889 11.9073 3.64837 11.3266 4.62121 10.4288C4.82778 10.2381 5.02061 10.034 5.20215 9.82074C5.03927 9.5548 4.88613 9.293 4.73943 9.0422L4.73621 9.0367C4.647 8.88418 4.56006 8.73561 4.47456 8.59108C4.26231 8.86556 4.03764 9.11871 3.79106 9.34629C3.05738 10.0234 2.10408 10.4927 0.644748 10.4927C0.288663 10.4927 0 10.8094 0 11.2Z" />
</svg>
</button> -->
</div> </div>
</div> </div>
</section> </section>
<section class="photos__content" style="--margin-sides: {sideMargins}px;"> <section class="photos__content" style="--margin-sides: {sideMargins}px;" bind:this={photosContentEl}>
<div class="grid container"> <div class="grid container">
{#if photos.length} {#if photos.length}
<div class="photos__grid"> <div class="photos__grid" bind:this={photosGridEl}>
{#each photos as { image, slug, location, title, city }, index} {#each photos as { id, image, slug, location, title, city }, index (id)}
<figure class="photo shadow-photo"> <figure class="photo shadow-photo">
<a href="/{location.country.slug}/{location.slug}/{slug}" sveltekit:prefetch sveltekit:noscroll> <a href="/{location.country.slug}/{location.slug}/{slug}" sveltekit:prefetch sveltekit:noscroll>
<Image <Image
@@ -385,6 +466,7 @@
limit: ${import.meta.env.VITE_GRID_AMOUNT}, limit: ${import.meta.env.VITE_GRID_AMOUNT},
page: 1, page: 1,
) { ) {
id
title title
slug slug
image { image {

View File

@@ -2,7 +2,6 @@
.photos { .photos {
// Intro Section // Intro Section
&__intro { &__intro {
overflow: hidden;
margin-bottom: clamp(32px, 7.5vw, 96px); margin-bottom: clamp(32px, 7.5vw, 96px);
color: $color-text; color: $color-text;
text-align: center; text-align: center;
@@ -14,7 +13,7 @@
line-height: 1; line-height: 1;
@include bp (sm) { @include bp (sm) {
margin: -100px 0 0; margin: -100px 0 0 calc(-1 * clamp(24px, 6vw, 64px));
} }
} }
// Text // Text
@@ -27,6 +26,13 @@
max-width: 524px; max-width: 524px;
} }
} }
// Passed scroll
&.is-passed {
@include bp (sm) {
padding-bottom: 72px;
}
}
} }
// Filter // Filter
@@ -194,6 +200,37 @@
border-radius: 50vh; border-radius: 50vh;
} }
} }
/*
** States
*/
// Fixed when scrolled pass intro
&.is-over {
--top: 24px;
transform: translate3d(0, calc(-100% - var(--top)), 0);
pointer-events: none;
transition: transform 1.0s var(--ease-quart);
.filter__bar {
pointer-events: auto;
box-shadow: 0 10px 20px rgba(#000, 0.1);
}
@include bp (sm) {
position: fixed;
z-index: 10;
top: var(--top);
left: 0;
right: 0;
}
}
// Visible when scrolling back up
&.is-visible {
transform: translate3d(0,0,0);
pointer-events: auto;
}
} }
// Content Block // Content Block
@@ -265,6 +302,15 @@
// Photo // Photo
.photo { .photo {
position: relative; position: relative;
opacity: 0;
transform: translate3d(0, 96px, 0);
transition: opacity 1.2s var(--ease-quart), transform 1.2s var(--ease-quart);
// Hidden state
&.is-visible {
opacity: 1;
transform: translate3d(0,0,0);
}
a { a {
display: block; display: block;
@@ -350,12 +396,12 @@
border-top-left-radius: 8px; border-top-left-radius: 8px;
pointer-events: none; pointer-events: none;
opacity: 0; opacity: 0;
transform: translate3d(12%, 23%, 0) rotate(-5deg); transform: translate3d(6%, 12%, 0) rotate(-1deg);
transition: opacity 0.4s var(--ease-quart), transform 1.0s var(--ease-quart); transition: opacity 0.5s var(--ease-quart), transform 0.9s var(--ease-quart);
&--small { &--small {
border-radius: 6px 6px 0 0; border-radius: 6px 6px 0 0;
transform: translate3d(8%, 12%, 0) rotate(-3deg); transform: translate3d(6%, 12%, 0) rotate(-3deg);
} }
} }