compare-plans animation POC

This commit is contained in:
tglide
2023-09-05 17:23:41 +01:00
parent 584654c4ee
commit e574a95c02
5 changed files with 128 additions and 9 deletions

View File

@@ -0,0 +1,60 @@
import { isVisible } from '$lib/utils/isVisible';
import type { Action } from 'svelte/action';
type Args =
| {
top?: number;
bottom?: number;
left?: number;
right?: number;
}
| undefined;
export const visible: Action<
HTMLElement,
Args,
{ 'on:visible': (e: CustomEvent<boolean>) => void }
> = (node, args) => {
let visible = false;
const createVisibilityHandler = (newArgs: Args) => {
const argsWithDefaults = {
top: 0,
bottom: window.innerHeight,
left: 0,
right: window.innerWidth,
...newArgs
};
return () => {
const prev = visible;
visible = isVisible(node, argsWithDefaults);
if (prev !== visible) {
node.dispatchEvent(new CustomEvent('visible', { detail: visible }));
}
};
};
let handleVisibility = createVisibilityHandler(args);
function destroy() {
window.removeEventListener('scroll', handleVisibility);
window.removeEventListener('resize', handleVisibility);
}
function update(args: Args) {
destroy();
handleVisibility = createVisibilityHandler(args);
window.addEventListener('scroll', handleVisibility);
window.addEventListener('resize', handleVisibility);
}
update(args);
return {
update,
destroy() {
window.removeEventListener('scroll', handleVisibility);
window.removeEventListener('resize', handleVisibility);
}
};
};

View File

@@ -8,6 +8,7 @@
<script lang="ts"> <script lang="ts">
import { browser } from '$app/environment'; import { browser } from '$app/environment';
import { MobileNav } from '$lib/components'; import { MobileNav } from '$lib/components';
import { isVisible } from '$lib/utils/isVisible';
import { addEventListener } from '@melt-ui/svelte/internal/helpers'; import { addEventListener } from '@melt-ui/svelte/internal/helpers';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
@@ -33,12 +34,12 @@
} }
function isInViewport(element: Element): boolean { function isInViewport(element: Element): boolean {
const rect = element.getBoundingClientRect(); return isVisible(element, {
const windowWidth = window.innerWidth || document.documentElement.clientWidth; top: 32,
const vertInView = rect.top <= 32 && rect.bottom >= 32; bottom: 32,
const horInView = rect.left <= windowWidth && rect.right >= 0; left: 0,
right: window.innerWidth
return vertInView && horInView; });
} }
function getVisibleTheme() { function getVisibleTheme() {

View File

@@ -0,0 +1,8 @@
let lastScrollPos = 0;
export function getScrollDir() {
const scrollPos = window.scrollY;
const scrollDir = scrollPos > lastScrollPos ? 'down' : 'up';
lastScrollPos = scrollPos;
return scrollDir;
}

View File

@@ -0,0 +1,10 @@
export function isVisible(
element: Element,
visRect: { left: number; right: number; top: number; bottom: number }
) {
const rect = element.getBoundingClientRect();
const vertInView = rect.top <= visRect.bottom && rect.bottom >= visRect.top;
const horInView = rect.left <= visRect.right && rect.right >= visRect.left;
return vertInView && horInView;
}

View File

@@ -1,7 +1,12 @@
<script lang="ts"> <script lang="ts">
import { browser } from '$app/environment';
import { Tabs } from '$lib/UI'; import { Tabs } from '$lib/UI';
import { visible } from '$lib/actions/visible';
import { getScrollDir } from '$lib/utils/getScrollDir';
import { isVisible } from '$lib/utils/isVisible';
import { createAccordion, melt } from '@melt-ui/svelte'; import { createAccordion, melt } from '@melt-ui/svelte';
import { slide } from 'svelte/transition'; import { writable } from 'svelte/store';
import { fly } from 'svelte/transition';
type Table = { type Table = {
title: string; title: string;
@@ -290,8 +295,18 @@
multiple: true, multiple: true,
forceVisible: true forceVisible: true
}); });
const visibleTables = writable([] as string[]);
$: activeTable = $visibleTables.sort((a, b) => {
return tables.findIndex((t) => t.title === a) - tables.findIndex((t) => t.title === b);
})[0];
let scrollDir = 'down';
$: console.log(scrollDir);
</script> </script>
<svelte:window on:scroll={() => (scrollDir = getScrollDir())} />
<div class="aw-big-padding-section-level-1 aw-white-section theme-light"> <div class="aw-big-padding-section-level-1 aw-white-section theme-light">
<div class="aw-big-padding-section-level-2"> <div class="aw-big-padding-section-level-2">
<div class="u-position-relative"> <div class="u-position-relative">
@@ -327,8 +342,19 @@
aw-u-padding-block-start-80 aw-u-filter-blur-8 u-position-sticky u-z-index-5" aw-u-padding-block-start-80 aw-u-filter-blur-8 u-position-sticky u-z-index-5"
style="--inset-block-start:1rem" style="--inset-block-start:1rem"
> >
<div class="aw-label aw-u-color-text-primary aw-u-cross-child-center" style="opacity:0"> <div
Compare plans class="aw-label aw-u-color-text-primary aw-u-cross-child-center aw-u-relative"
style:opacity={browser ? 1 : 0}
>
{#key activeTable}
<div
style="position: absolute; top: 50%; "
in:fly={{ y: scrollDir === 'down' ? 16 : -16, delay: 500, duration: 500 }}
out:fly={{ y: scrollDir === 'down' ? -16 : 16, duration: 500 }}
>
{activeTable ?? ''}
</div>
{/key}
</div> </div>
<div class="aw-mini-card"> <div class="aw-mini-card">
<div class="u-flex u-cross-center u-gap-16 u-flex-wrap u-main-space-between"> <div class="u-flex u-cross-center u-gap-16 u-flex-wrap u-main-space-between">
@@ -363,10 +389,24 @@
class="aw-compare-table aw-sub-body-400" class="aw-compare-table aw-sub-body-400"
class:is-open-in-mobile={isOpen} class:is-open-in-mobile={isOpen}
use:melt={$item(table.title)} use:melt={$item(table.title)}
use:visible={{
top: 128
}}
on:visible={(e) => {
const isVisible = e.detail;
visibleTables.update((p) => {
if (isVisible) {
return [...p, table.title];
} else {
return p.filter((t) => t !== table.title);
}
});
}}
> >
<caption <caption
class="aw-compare-table-caption aw-description aw-u-color-text-primary" class="aw-compare-table-caption aw-description aw-u-color-text-primary"
use:melt={$heading({ level: 3 })} use:melt={$heading({ level: 3 })}
style:position={browser ? 'unset' : undefined}
> >
<button class="aw-compare-table-caption-button" use:melt={$trigger(table.title)}> <button class="aw-compare-table-caption-button" use:melt={$trigger(table.title)}>
<span>{table.title}</span> <span>{table.title}</span>