Skip to content

Commit

Permalink
Add basic animation
Browse files Browse the repository at this point in the history
  • Loading branch information
prototypa committed Oct 13, 2024
1 parent ad5ea2a commit 38db18b
Show file tree
Hide file tree
Showing 17 changed files with 216 additions and 27 deletions.
4 changes: 3 additions & 1 deletion src/components/blog/GridItem.astro
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ const image = await findImage(post.image);
const link = APP_BLOG?.post?.isEnabled ? getPermalink(post.permalink, 'post') : '';
---

<article class="mb-6 transition">
<article
class="mb-6 transition intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
>
<div class="relative md:h-64 bg-gray-400 dark:bg-slate-700 rounded shadow-lg mb-6">
{
image &&
Expand Down
4 changes: 3 additions & 1 deletion src/components/blog/ListItem.astro
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ const image = (await findImage(post.image)) as ImageMetadata | undefined;
const link = APP_BLOG?.post?.isEnabled ? getPermalink(post.permalink, 'post') : '';
---

<article class={`max-w-md mx-auto md:max-w-none grid gap-6 md:gap-8 ${image ? 'md:grid-cols-2' : ''}`}>
<article
class={`max-w-md mx-auto md:max-w-none grid gap-6 md:gap-8 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade ${image ? 'md:grid-cols-2' : ''}`}
>
{
image &&
(link ? (
Expand Down
5 changes: 4 additions & 1 deletion src/components/blog/RelatedPosts.astro
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ const relatedPosts = post.tags ? await getRelatedPosts(post, 4) : [];
{
APP_BLOG.isRelatedPostsEnabled ? (
<BlogHighlightedPosts
classes={{ container: 'pt-0 lg:pt-0 md:pt-0' }}
classes={{
container:
'pt-0 lg:pt-0 md:pt-0 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade',
}}
title="Related Posts"
linkText="View All Posts"
linkUrl={getBlogPermalink()}
Expand Down
6 changes: 5 additions & 1 deletion src/components/blog/SinglePost.astro
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ const { post, url } = Astro.props;

<section class="py-8 sm:py-16 lg:py-20 mx-auto">
<article>
<header class={post.image ? '' : ''}>
<header
class={post.image
? 'intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade'
: 'intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade'}
>
<div class="flex justify-between flex-col sm:flex-row max-w-3xl mx-auto mt-0 mb-2 px-4 sm:px-6 sm:items-center">
<p>
<Icon name="tabler:clock" class="w-4 h-4 inline-block -mt-0.5 dark:text-gray-400" />
Expand Down
119 changes: 119 additions & 0 deletions src/components/common/BasicScripts.astro
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,122 @@ import { UI } from 'astrowind:config';
onPageShow();
});
</script>

<script is:inline>
/* Inspired by: https://github.com/heidkaemper/tailwindcss-intersect */
const Observer = {
observers: {},
visibleElementsQueue: [],
processing: false,
delayBetweenAnimations: 100,

start() {
const selectors = [
'[class*=" intersect:"]',
'[class*=":intersect:"]',
'[class^="intersect:"]',
'[class="intersect"]',
'[class*=" intersect "]',
'[class^="intersect "]',
'[class$=" intersect"]',
];

const elements = Array.from(document.querySelectorAll(selectors.join(',')));

elements.forEach((el) => el.setAttribute('no-intersect', ''));

const getThreshold = (element) => {
if (element.classList.contains('intersect-full')) return 0.99;
if (element.classList.contains('intersect-half')) return 0.5;
if (element.classList.contains('intersect-quarter')) return 0.25;
return 0;
};

Object.values(this.observers).forEach((observer) => observer.disconnect());
this.observers = {};

const callback = (entries) => {
entries.forEach((entry) => {
const target = entry.target;

if (entry.isIntersecting) {
if (target.classList.contains('intercept-no-queue')) {
target.removeAttribute('no-intersect');
if (target.classList.contains('intersect-once')) {
Object.values(this.observers).forEach((observer) => observer.unobserve(target));
}
return;
}

if (!this.visibleElementsQueue.includes(target)) {
this.visibleElementsQueue.push(target);
}

this.processQueue();
} else {
target.setAttribute('no-intersect', '');

const index = this.visibleElementsQueue.indexOf(target);
if (index > -1) {
this.visibleElementsQueue.splice(index, 1);
}
}
});
};

elements.forEach((el) => {
const threshold = getThreshold(el);

if (!this.observers[threshold]) {
this.observers[threshold] = new IntersectionObserver(callback, { threshold });
}

this.observers[threshold].observe(el);
});
},

async processQueue() {
if (this.processing) {
return;
}

this.processing = true;

while (this.visibleElementsQueue.length > 0) {
const element = this.visibleElementsQueue.shift();

element.removeAttribute('no-intersect');

if (element.classList.contains('intersect-once')) {
Object.values(this.observers).forEach((observer) => observer.unobserve(element));
}

if (this.isElementInViewport(element)) {
await this.delay(this.delayBetweenAnimations);
}
}

this.processing = false;
},

delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
},

isElementInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top < (window.innerHeight || document.documentElement.clientHeight) &&
rect.bottom > 0 &&
rect.left < (window.innerWidth || document.documentElement.clientWidth) &&
rect.right > 0
);
},
};

Observer.start();

document.addEventListener('astro:after-swap', () => {
Observer.start();
});
</script>
2 changes: 1 addition & 1 deletion src/components/ui/ItemGrid.astro
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const {
)}
>
{items.map(({ title, description, icon, callToAction, classes: itemClasses = {} }) => (
<div>
<div class="intersect-once motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade">
<div class={twMerge('flex flex-row max-w-md', panelClass, itemClasses?.panel)}>
<div class="flex justify-center">
{(icon || defaultIcon) && (
Expand Down
8 changes: 7 additions & 1 deletion src/components/ui/ItemGrid2.astro
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ const {
)}
>
{items.map(({ title, description, icon, callToAction, classes: itemClasses = {} }) => (
<div class={twMerge('relative flex flex-col', panelClass, itemClasses?.panel)}>
<div
class={twMerge(
'relative flex flex-col intersect-once intersect-quarter intercept-no-queue motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade',
panelClass,
itemClasses?.panel
)}
>
{(icon || defaultIcon) && (
<Icon name={icon || defaultIcon} class={twMerge('mb-2 w-10 h-10', defaultIconClass, itemClasses?.icon)} />
)}
Expand Down
8 changes: 7 additions & 1 deletion src/components/ui/Timeline.astro
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,13 @@ const {
items && items.length && (
<div class={containerClass}>
{items.map(({ title, description, icon, classes: itemClasses = {} }, index = 0) => (
<div class={twMerge('flex', panelClass, itemClasses?.panel)}>
<div
class={twMerge(
'flex intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade',
panelClass,
itemClasses?.panel
)}
>
<div class="flex flex-col items-center mr-4 rtl:mr-0 rtl:ml-4">
<div>
<div class="flex items-center justify-center">
Expand Down
5 changes: 4 additions & 1 deletion src/components/ui/WidgetWrapper.astro
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ const WrapperTag = as;
</div>
<div
class:list={[
twMerge('relative mx-auto max-w-7xl px-4 md:px-6 py-12 md:py-16 lg:py-20 text-default', containerClass),
twMerge(
'relative mx-auto max-w-7xl px-4 md:px-6 py-12 md:py-16 lg:py-20 text-default intersect-once intersect-quarter intercept-no-queue motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade',
containerClass
),
{ dark: isDark },
]}
>
Expand Down
4 changes: 3 additions & 1 deletion src/components/widgets/Footer.astro
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ const { socialLinks = [], secondaryLinks = [], links = [], footNote = '', theme

<footer class:list={[{ dark: theme === 'dark' }, 'relative border-t border-gray-200 dark:border-slate-800 not-prose']}>
<div class="dark:bg-dark absolute inset-0 pointer-events-none" aria-hidden="true"></div>
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 dark:text-slate-300">
<div
class="relative max-w-7xl mx-auto px-4 sm:px-6 dark:text-slate-300 intersect-once intersect-quarter intercept-no-queue motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
>
<div class="grid grid-cols-12 gap-4 gap-y-8 sm:gap-8 py-8 md:py-12">
<div class="col-span-12 lg:col-span-4">
<div class="mb-2">
Expand Down
19 changes: 14 additions & 5 deletions src/components/widgets/Hero.astro
Original file line number Diff line number Diff line change
Expand Up @@ -31,24 +31,31 @@ const {
{
tagline && (
<p
class="text-base text-secondary dark:text-blue-200 font-bold tracking-wide uppercase"
class="text-base text-secondary dark:text-blue-200 font-bold tracking-wide uppercase intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
set:html={tagline}
/>
)
}
{
title && (
<h1
class="text-5xl md:text-6xl font-bold leading-tighter tracking-tighter mb-4 font-heading dark:text-gray-200"
class="text-5xl md:text-6xl font-bold leading-tighter tracking-tighter mb-4 font-heading dark:text-gray-200 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
set:html={title}
/>
)
}
<div class="max-w-3xl mx-auto">
{subtitle && <p class="text-xl text-muted mb-6 dark:text-slate-300" set:html={subtitle} />}
{
subtitle && (
<p
class="text-xl text-muted mb-6 dark:text-slate-300 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
set:html={subtitle}
/>
)
}
{
actions && (
<div class="max-w-xs sm:max-w-md m-auto flex flex-nowrap flex-col sm:flex-row sm:justify-center gap-4">
<div class="max-w-xs sm:max-w-md m-auto flex flex-nowrap flex-col sm:flex-row sm:justify-center gap-4 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade">
{Array.isArray(actions) ? (
actions.map((action) => (
<div class="flex w-full sm:w-auto">
Expand All @@ -64,7 +71,9 @@ const {
</div>
{content && <Fragment set:html={content} />}
</div>
<div>
<div
class="intersect-once intercept-no-queue intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
>
{
image && (
<div class="relative m-auto max-w-5xl">
Expand Down
17 changes: 12 additions & 5 deletions src/components/widgets/Hero2.astro
Original file line number Diff line number Diff line change
Expand Up @@ -31,25 +31,32 @@ const {
{
tagline && (
<p
class="text-base text-secondary dark:text-blue-200 font-bold tracking-wide uppercase"
class="text-base text-secondary dark:text-blue-200 font-bold tracking-wide uppercase intersect-once motion-safe:md:intersect:animate-fade motion-safe:md:opacity-0 intersect-quarter"
set:html={tagline}
/>
)
}
{
title && (
<h1
class="text-5xl md:text-6xl font-bold leading-tighter tracking-tighter mb-4 font-heading dark:text-gray-200"
class="text-5xl md:text-6xl font-bold leading-tighter tracking-tighter mb-4 font-heading dark:text-gray-200 intersect-once motion-safe:md:intersect:animate-fade motion-safe:md:opacity-0 intersect-quarter"
set:html={title}
/>
)
}
<div class="max-w-3xl mx-auto lg:max-w-none">
{subtitle && <p class="text-xl text-muted mb-6 dark:text-slate-300" set:html={subtitle} />}
{
subtitle && (
<p
class="text-xl text-muted mb-6 dark:text-slate-300 intersect-once motion-safe:md:intersect:animate-fade motion-safe:md:opacity-0 intersect-quarter"
set:html={subtitle}
/>
)
}

{
actions && (
<div class="max-w-xs sm:max-w-md m-auto flex flex-nowrap flex-col sm:flex-row sm:justify-center gap-4 lg:justify-start lg:m-0 lg:max-w-7xl">
<div class="max-w-xs sm:max-w-md m-auto flex flex-nowrap flex-col sm:flex-row sm:justify-center gap-4 lg:justify-start lg:m-0 lg:max-w-7xl intersect-once motion-safe:md:intersect:animate-fade motion-safe:md:opacity-0 intersect-quarter">
{Array.isArray(actions) ? (
actions.map((action) => (
<div class="flex w-full sm:w-auto">
Expand All @@ -68,7 +75,7 @@ const {
<div class="basis-1/2">
{
image && (
<div class="relative m-auto max-w-5xl">
<div class="relative m-auto max-w-5xl intersect-once intercept-no-queue motion-safe:md:intersect:animate-fade motion-safe:md:opacity-0 intersect-quarter">
{typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
Expand Down
17 changes: 13 additions & 4 deletions src/components/widgets/HeroText.astro
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,31 @@ const {
{
tagline && (
<p
class="text-base text-secondary dark:text-blue-200 font-bold tracking-wide uppercase"
class="text-base text-secondary dark:text-blue-200 font-bold tracking-wide uppercase intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
set:html={tagline}
/>
)
}
{
title && (
<h1
class="text-5xl md:text-6xl font-bold leading-tighter tracking-tighter mb-4 font-heading dark:text-gray-200"
class="text-5xl md:text-6xl font-bold leading-tighter tracking-tighter mb-4 font-heading dark:text-gray-200 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
set:html={title}
/>
)
}
<div class="max-w-3xl mx-auto">
{subtitle && <p class="text-xl text-muted mb-6 dark:text-slate-300" set:html={subtitle} />}
<div class="max-w-xs sm:max-w-md m-auto flex flex-nowrap flex-col sm:flex-row sm:justify-center gap-4">
{
subtitle && (
<p
class="text-xl text-muted mb-6 dark:text-slate-300 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
set:html={subtitle}
/>
)
}
<div
class="max-w-xs sm:max-w-md m-auto flex flex-nowrap flex-col sm:flex-row sm:justify-center gap-4 intersect-once intersect-quarter motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade"
>
{
callToAction && (
<div class="flex w-full sm:w-auto">
Expand Down
2 changes: 1 addition & 1 deletion src/components/widgets/Pricing.astro
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const {
{
prices &&
prices.map(({ title, subtitle, price, period, items, callToAction, hasRibbon = false, ribbonTitle }) => (
<div class="col-span-3 mx-auto flex w-full sm:col-span-1 md:col-span-1 lg:col-span-1 xl:col-span-1">
<div class="col-span-3 mx-auto flex w-full sm:col-span-1 md:col-span-1 lg:col-span-1 xl:col-span-1 intersect-once motion-safe:md:intersect:animate-fade motion-safe:md:opacity-0 intersect-quarter">
{price && period && (
<div class="rounded-lg backdrop-blur border border-gray-200 dark:border-gray-700 bg-white dark:bg-slate-900 shadow px-6 py-8 flex w-full max-w-sm flex-col justify-between text-center">
{hasRibbon && ribbonTitle && (
Expand Down
2 changes: 1 addition & 1 deletion src/components/widgets/Stats.astro
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const {
{
stats &&
stats.map(({ amount, title, icon }) => (
<div class="p-4 md:w-1/4 sm:w-1/2 w-full min-w-[220px] text-center md:border-r md:last:border-none dark:md:border-slate-500">
<div class="p-4 md:w-1/4 sm:w-1/2 w-full min-w-[220px] text-center md:border-r md:last:border-none dark:md:border-slate-500 intersect-once motion-safe:md:opacity-0 motion-safe:md:intersect:animate-fade intersect-quarter">
{icon && (
<div class="flex items-center justify-center mx-auto mb-4 text-primary">
<Icon name={icon} class="w-10 h-10" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/widgets/Testimonials.astro
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const {
{
testimonials &&
testimonials.map(({ title, testimonial, name, job, image }) => (
<div class="flex h-auto">
<div class="flex h-auto intersect-once motion-safe:md:intersect:animate-fade motion-safe:md:opacity-0 intersect-quarter">
<div class="flex flex-col p-4 md:p-6 rounded-md shadow-xl dark:shadow-none dark:border dark:border-slate-600">
{title && <h2 class="text-lg font-medium leading-6 pb-4">{title}</h2>}
{testimonial && (
Expand Down
Loading

0 comments on commit 38db18b

Please sign in to comment.