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">
|
||||
import { browser } from '$app/environment';
|
||||
import { MobileNav } from '$lib/components';
|
||||
import { isVisible } from '$lib/utils/isVisible';
|
||||
import { addEventListener } from '@melt-ui/svelte/internal/helpers';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
@@ -33,12 +34,12 @@
|
||||
}
|
||||
|
||||
function isInViewport(element: Element): boolean {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
|
||||
const vertInView = rect.top <= 32 && rect.bottom >= 32;
|
||||
const horInView = rect.left <= windowWidth && rect.right >= 0;
|
||||
|
||||
return vertInView && horInView;
|
||||
return isVisible(element, {
|
||||
top: 32,
|
||||
bottom: 32,
|
||||
left: 0,
|
||||
right: window.innerWidth
|
||||
});
|
||||
}
|
||||
|
||||
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">
|
||||
import { browser } from '$app/environment';
|
||||
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 { slide } from 'svelte/transition';
|
||||
import { writable } from 'svelte/store';
|
||||
import { fly } from 'svelte/transition';
|
||||
|
||||
type Table = {
|
||||
title: string;
|
||||
@@ -290,8 +295,18 @@
|
||||
multiple: 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>
|
||||
|
||||
<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-2">
|
||||
<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"
|
||||
style="--inset-block-start:1rem"
|
||||
>
|
||||
<div class="aw-label aw-u-color-text-primary aw-u-cross-child-center" style="opacity:0">
|
||||
Compare plans
|
||||
<div
|
||||
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 class="aw-mini-card">
|
||||
<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:is-open-in-mobile={isOpen}
|
||||
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
|
||||
class="aw-compare-table-caption aw-description aw-u-color-text-primary"
|
||||
use:melt={$heading({ level: 3 })}
|
||||
style:position={browser ? 'unset' : undefined}
|
||||
>
|
||||
<button class="aw-compare-table-caption-button" use:melt={$trigger(table.title)}>
|
||||
<span>{table.title}</span>
|
||||
|
||||
Reference in New Issue
Block a user