feat: replace carousel using alpine with javascript
This commit is contained in:
parent
74b9460ea1
commit
700095d8af
9 changed files with 165 additions and 261 deletions
|
@ -1,97 +0,0 @@
|
|||
---
|
||||
interface Props {
|
||||
data: string;
|
||||
}
|
||||
|
||||
const { data } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
data-slides={data}
|
||||
x-data="{
|
||||
// Sets the time between each slides in milliseconds
|
||||
autoplayIntervalTime: 5000,
|
||||
init() {
|
||||
this.slides = JSON.parse(this.$el.dataset.slides);
|
||||
},
|
||||
currentSlideIndex: 1,
|
||||
isPaused: false,
|
||||
autoplayInterval: null,
|
||||
previous() {
|
||||
if (this.currentSlideIndex > 1) {
|
||||
this.currentSlideIndex = this.currentSlideIndex - 1
|
||||
} else {
|
||||
// If it's the first slide, go to the last slide
|
||||
this.currentSlideIndex = this.slides.length
|
||||
}
|
||||
},
|
||||
next() {
|
||||
if (this.currentSlideIndex < this.slides.length) {
|
||||
this.currentSlideIndex = this.currentSlideIndex + 1
|
||||
} else {
|
||||
// If it's the last slide, go to the first slide
|
||||
this.currentSlideIndex = 1
|
||||
}
|
||||
},
|
||||
autoplay() {
|
||||
this.autoplayInterval = setInterval(() => {
|
||||
if (! this.isPaused) {
|
||||
this.next()
|
||||
}
|
||||
}, this.autoplayIntervalTime)
|
||||
},
|
||||
// Updates interval time
|
||||
setAutoplayInterval(newIntervalTime) {
|
||||
clearInterval(this.autoplayInterval)
|
||||
this.autoplayIntervalTime = newIntervalTime
|
||||
this.autoplay()
|
||||
},
|
||||
}"
|
||||
x-init="autoplay"
|
||||
class="relative w-full overflow-hidden"
|
||||
>
|
||||
<div class="relative min-h-[50svh] w-full">
|
||||
<template x-for="(slide, index) in slides">
|
||||
<div
|
||||
x-cloak
|
||||
x-show="currentSlideIndex == index + 1"
|
||||
class="absolute inset-0"
|
||||
x-transition.opacity.duration.1000ms
|
||||
>
|
||||
<img
|
||||
class="absolute inset-0 h-full w-full rounded-sm object-cover"
|
||||
x-bind:src="slide.data.image.url.src"
|
||||
x-bind:alt="slide.data.image.alt"
|
||||
/>
|
||||
<a
|
||||
class="absolute inset-0 z-20"
|
||||
x-bind:aria-label="slide.data.title"
|
||||
x-bind:href="'/' + slide.collection + '/' + slide.slug"></a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="relative min-h-18">
|
||||
<template x-for="(slide, index) in slides">
|
||||
<div
|
||||
x-show="currentSlideIndex == index + 1"
|
||||
class="text-secondary absolute top-0 left-0 flex w-full justify-end pt-1 text-right font-serif text-lg"
|
||||
x-transition.opacity.duration.1000ms.delay.100ms
|
||||
>
|
||||
<div class="flex w-full flex-col items-end">
|
||||
<h3
|
||||
x-text="slide.data.title"
|
||||
x-bind:aria-describedby="'slide' + (index + 1) + 'Description'"
|
||||
class="italic"
|
||||
>
|
||||
</h3>
|
||||
<p
|
||||
class="text-tertiary w-fit text-sm text-pretty lg:w-1/2"
|
||||
x-text="slide.data.description"
|
||||
x-bind:id="'slide' + (index + 1) + 'Description'"
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
|
@ -1,29 +1,12 @@
|
|||
---
|
||||
import { SITE } from "@consts";
|
||||
import Link from "@components/Link.astro";
|
||||
import { Icon } from "astro-icon/components";
|
||||
---
|
||||
|
||||
<footer class="mx-auto mt-auto w-full">
|
||||
<div class="flex flex-col items-center justify-between gap-2 md:gap-4">
|
||||
<span class="text-tertiary text-center text-sm"
|
||||
>© {new Date().getFullYear()}
|
||||
<a href="/" class="hover:text-secondary transition-colors duration-300"
|
||||
>{SITE.TITLE}</a
|
||||
>. All rights reserved.
|
||||
</span>
|
||||
<div class="flex gap-3 sm:justify-center">
|
||||
{
|
||||
SITE.LINKS.map((i) => (
|
||||
<Link href={i.href}>
|
||||
<Icon
|
||||
name={i.icon}
|
||||
title={i.name}
|
||||
class="text-tertiary hover:text-secondary h-4 w-auto transition-colors duration-300"
|
||||
/>
|
||||
</Link>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<footer class="w-full">
|
||||
<span class="text-tertiary"
|
||||
>© {new Date().getFullYear()}
|
||||
<a href="/" class="hover:text-secondary transition-colors duration-300"
|
||||
>{SITE.TITLE}</a
|
||||
>. All rights reserved.
|
||||
</span>
|
||||
</footer>
|
||||
|
|
|
@ -1,62 +1,30 @@
|
|||
---
|
||||
import { SITE } from "@consts";
|
||||
import { Icon } from "astro-icon/components";
|
||||
|
||||
const pathname = new URL(Astro.request.url).pathname;
|
||||
const currentPath = pathname.replace(/\/$/, "");
|
||||
---
|
||||
|
||||
<header class="mx-auto w-full">
|
||||
<nav
|
||||
class="md:flex-no-wrap flex w-full flex-wrap justify-between"
|
||||
x-data="{ open: false }"
|
||||
>
|
||||
<div class="flex w-full items-center justify-between md:w-fit">
|
||||
<a href="/" title={SITE.TITLE}>
|
||||
<Icon
|
||||
name="icon"
|
||||
title={SITE.TITLE}
|
||||
class="hover:text-tertiary h-8 w-auto transition-colors duration-500 ease-in-out"
|
||||
/>
|
||||
<nav class="flex w-full justify-between">
|
||||
<div class="flex w-fit items-center justify-between">
|
||||
<a href="/" aria-label={SITE.TITLE} title={SITE.TITLE} class="group">
|
||||
<div
|
||||
class="ring-secondary/10 size-8 rounded-full bg-linear-45 from-[#FF5907] from-50% to-[#FF8548] ring-1 transition-transform ease-in-out ring-inset group-hover:scale-105"
|
||||
>
|
||||
</div>
|
||||
</a>
|
||||
<button
|
||||
x-on:click="open = !open"
|
||||
x-bind:aria-expanded="open"
|
||||
type="button"
|
||||
class="text-secondary flex md:hidden"
|
||||
aria-label="mobile menu"
|
||||
aria-controls="mobileMenu"
|
||||
>
|
||||
<Icon
|
||||
name="mdi:menu"
|
||||
aria-hidden="true"
|
||||
class="size-6"
|
||||
x-cloak
|
||||
x-show="!open"
|
||||
/>
|
||||
<Icon
|
||||
name="mdi:window-close"
|
||||
aria-hidden="true"
|
||||
class="size-6"
|
||||
x-cloak
|
||||
x-show="open"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<ul
|
||||
class="my-4 flex w-full flex-col items-center gap-4 text-right md:my-0 md:flex md:w-fit md:flex-row md:text-left"
|
||||
x-bind:class="{ 'hidden': !open }"
|
||||
x-cloak
|
||||
>
|
||||
<ul class="flex w-fit flex-row items-center gap-4">
|
||||
{
|
||||
SITE.NAVLINKS.map((link) => {
|
||||
let linkHref = link.href.replace(/\/$/, "");
|
||||
const isActive = currentPath.startsWith(linkHref);
|
||||
return (
|
||||
<li class="text-tertiary hover:text-secondary focus:text-secondary text-2xl font-medium capitalize transition-colors duration-300 focus:outline-hidden md:text-base">
|
||||
<li class="text-tertiary hover:text-secondary focus:text-secondary font-medium capitalize transition-colors duration-300 focus:outline-hidden">
|
||||
<a
|
||||
href={link.href}
|
||||
class:list={[isActive ? "text-secondary" : ""]}
|
||||
class:list={[isActive ? "text-secondary" : null]}
|
||||
>
|
||||
{link.name}
|
||||
</a>
|
||||
|
|
60
src/components/Slideshow.astro
Normal file
60
src/components/Slideshow.astro
Normal file
|
@ -0,0 +1,60 @@
|
|||
---
|
||||
import { Image } from "astro:assets";
|
||||
|
||||
interface Props {
|
||||
interval?: number;
|
||||
images: any;
|
||||
}
|
||||
|
||||
const { interval = 3000, images } = Astro.props;
|
||||
---
|
||||
|
||||
<div
|
||||
class="relative h-64 w-full overflow-hidden md:h-96"
|
||||
data-interval={interval}
|
||||
>
|
||||
{
|
||||
images.map((image: any, index: number) => (
|
||||
<div
|
||||
class="absolute top-0 left-0 h-full w-full transition-opacity duration-500"
|
||||
style={`opacity: ${index === 0 ? 1 : 0}; z-index: ${
|
||||
index === 0 ? 10 : 1
|
||||
}`}
|
||||
data-slide-index={index}
|
||||
>
|
||||
<a href={`/${image.collection}/${image.slug}`}>
|
||||
<Image
|
||||
src={image.data.image.url}
|
||||
alt={`Slide ${index + 1}`}
|
||||
title={image.data.title}
|
||||
class="h-full w-full rounded-sm object-cover"
|
||||
loading="eager"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const images = document.querySelectorAll<HTMLElement>("[data-slide-index]");
|
||||
let currentImageIndex = 0;
|
||||
const interval =
|
||||
Number(
|
||||
(
|
||||
document
|
||||
.querySelector<HTMLElement>("[data-slide-index]")
|
||||
?.closest(".relative") as HTMLElement
|
||||
)?.dataset?.interval,
|
||||
) || 3000;
|
||||
|
||||
function showNextImage() {
|
||||
images[currentImageIndex].style.opacity = "0";
|
||||
images[currentImageIndex].style.zIndex = "1";
|
||||
currentImageIndex = (currentImageIndex + 1) % images.length;
|
||||
images[currentImageIndex].style.opacity = "1";
|
||||
images[currentImageIndex].style.zIndex = "10";
|
||||
}
|
||||
|
||||
setInterval(showNextImage, interval);
|
||||
</script>
|
|
@ -3,24 +3,21 @@ import Layout from "@layouts/Layout.astro";
|
|||
import { HOME } from "@consts";
|
||||
import { getCollection } from "astro:content";
|
||||
import Hero from "@components/Hero.astro";
|
||||
import Carousel from "@components/Carousel.astro";
|
||||
import Slideshow from "@components/Slideshow.astro";
|
||||
|
||||
const allProjects = await getCollection("projects");
|
||||
const projects = allProjects
|
||||
.filter((project) => !project.data.draft && project.data.featured)
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
|
||||
.slice(0, HOME.HOMESETTINGS?.NUM_PROJECTS_ON_HOMEPAGE);
|
||||
const projectsJSON = JSON.stringify(projects);
|
||||
---
|
||||
|
||||
<Layout title={HOME.TITLE} description={HOME.DESCRIPTION}>
|
||||
<div class="block w-full">
|
||||
<Hero />
|
||||
<section
|
||||
id="featured-projects"
|
||||
class="animate-reveal opacity-0 [animation-delay:0.1s]"
|
||||
>
|
||||
<Carousel data={projectsJSON} />
|
||||
</section>
|
||||
</div>
|
||||
<Hero />
|
||||
<section
|
||||
id="featured-projects"
|
||||
class="animate-reveal opacity-0 [animation-delay:0.1s]"
|
||||
>
|
||||
<Slideshow images={projects} />
|
||||
</section>
|
||||
</Layout>
|
||||
|
|
|
@ -79,7 +79,3 @@
|
|||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
[x-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue