mirror of
https://github.com/LukeHagar/website.git
synced 2025-12-10 21:07:46 +00:00
compare-plans animation POC
This commit is contained in:
60
src/lib/actions/visible.ts
Normal file
60
src/lib/actions/visible.ts
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -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() {
|
||||||
|
|||||||
8
src/lib/utils/getScrollDir.ts
Normal file
8
src/lib/utils/getScrollDir.ts
Normal 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;
|
||||||
|
}
|
||||||
10
src/lib/utils/isVisible.ts
Normal file
10
src/lib/utils/isVisible.ts
Normal 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;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user