3. Bouwen 🛠️ - iBadr49/Portfolio-v2 GitHub Wiki
Tijdens het werken aan mijn portfolio heb ik de principes van atomic design gevolgd, waarbij ik verschillende componenten heb gemaakt en geïntegreerd.
Ik wilde een visueel aantrekkelijke animatie creëren voor een reeks "stacking cards" die vloeiend in- en uitzoomen tijdens het scrollen. Door gebruik te maken van de AAT.js library, heb ik een dynamische animatie kunnen toevoegen die reageert op de scroll-positie van de gebruiker.
De animatie wordt aangedreven door de AAT.js library, die scroll-gebonden animaties eenvoudig maakt. Hier is een korte uitleg van hoe de animatie werkt:
- ScrollObserver: Dit object observeert de scroll-positie van de elementen en triggert animaties gebaseerd op de positie van de kaart in het venster.
- valueAtPercentage: Deze functie berekent een waarde gebaseerd op een percentage, wat gebruikt wordt om de schaal en helderheid van de kaarten te bepalen.
- initializeCards: In deze functie worden de kaarten geïnitialiseerd. Elke kaart wordt ingesteld met een paddingTop die afhangt van de positie in de reeks. Vervolgens worden de animaties ingesteld met behulp van ScrollObserver en valueAtPercentage om de schaal en helderheid van de kaarten dynamisch aan te passen tijdens het scrollen.
Door deze stappen te volgen, heb ik een set "stacking cards" kunnen maken met mooie scroll-animaties in een Svelte-project. Dit maakt gebruik van de kracht van AAT.js voor soepele en aantrekkelijke gebruikerservaringen.
Hieronder zie je mijn JS;
<script>
import { onMount } from "svelte";
let ScrollObserver;
let valueAtPercentage;
onMount(async () => {
if (typeof window !== "undefined") {
const aat = await import("aatjs"); // Import from node_modules
ScrollObserver = aat.ScrollObserver;
valueAtPercentage = aat.valueAtPercentage;
function initializeCards() {
const cardsContainer = document.querySelector(".cards");
const cards = document.querySelectorAll(".card");
cardsContainer.style.setProperty("--cards-count", cards.length);
cardsContainer.style.setProperty(
"--card-height",
`${cards[0].clientHeight}px`
);
Array.from(cards).forEach((card, index) => {
const offsetTop = 20 + index * 20;
card.style.paddingTop = `${offsetTop}px`;
if (index === cards.length - 1) {
return;
}
const toScale = 1 - (cards.length - 1 - index) * 0.1;
const nextCard = cards[index + 1];
const cardInner = card.querySelector(".card__inner");
ScrollObserver.Element(nextCard, {
offsetTop,
offsetBottom: window.innerHeight - card.clientHeight,
}).onScroll(({ percentageY }) => {
cardInner.style.scale = valueAtPercentage({
from: 1,
to: toScale,
percentage: percentageY,
});
cardInner.style.filter = `brightness(${valueAtPercentage({
from: 1,
to: 0.6,
percentage: percentageY,
})})`;
});
});
}
// Call initializeCards function
initializeCards();
}
});
</script>
Hier kan je mijn HTML zien;
<div class="cards">
<div class="card" data-index="0">
<div class="card__inner">
<div class="card__image-container">
<img
class="card__image"
src="images/1.svg"
alt=""
/>
</div>
<div class="card__content">
<h3 class="card__title">Project 1</h3>
<p class="card__description">
Lorem ipsum dolor sit amet consectetur adipisicing elit. Ab dicta
error nam eaque. Eum fuga laborum quos expedita iste saepe
similique, unde possimus quia at magnam sed cupiditate?
Reprehenderit, harum!
</p>
</div>
</div>
</div>
</div>
In mijn project heb ik de GitHub API gebruikt om repositories op te halen van mijn GitHub-profiel en deze te filteren en weer te geven op mijn website. Hier is hoe ik dit heb gedaan:
Eerst heb ik een variabele username ingesteld met mijn GitHub-gebruikersnaam en vervolgens de API-URL samengesteld met deze gebruikersnaam. De URL haalt maximaal 50 repositories op, gesorteerd op de meest recent bijgewerkte repositories.
const username = "iBadr49";
const apiUrl = `https://api.github.com/users/${username}/repos?sort=updated&per_page=50`;
Ik heb een Svelte store gemaakt om de repositories op te slaan die ik van de API ontvang. Deze store maakt het eenvoudig om de data reactief te maken en te gebruiken in de componenten.
let repos = writable([]);
Ik heb een functie filterRepos gemaakt die een lijst van repositories als input neemt en alleen de repositories teruggeeft waarvan de namen overeenkomen met een vooraf gedefinieerde lijst. Dit zorgt ervoor dat alleen de repositories die ik belangrijk vind, worden weergegeven.
function filterRepos(repos) {
const specificRepos = [
"visual-thinking",
"i-love-web-app",
"my-first-chatroom",
];
return repos.filter((repo) =>
specificRepos.includes(repo.name.toLowerCase())
);
}
Binnen de onMount lifecycle hook van Svelte, heb ik een asynchrone functie gemaakt om de repositories van de GitHub API op te halen. Nadat de data is opgehaald, heb ik deze gesorteerd op het aantal sterren (stargazers_count) en vervolgens gefilterd met mijn filterRepos functie. De gefilterde en gesorteerde lijst van repositories wordt daarna in de repos store gezet.
onMount(async () => {
try {
const response = await fetch(apiUrl);
let data = await response.json();
data.sort((a, b) => b.stargazers_count - a.stargazers_count);
repos.set(filterRepos(data));
} catch (error) {
console.error("Error fetching repos:", error);
}
});
In de HTML-markup van mijn component, gebruik ik een {#each} block om door de repos store te itereren en elke repository weer te geven in een kaart (card). Voor elke repository toon ik het aantal sterren, watchers, de gebruikte programmeertaal, een beschrijving (of een standaardbericht als er geen beschrijving is), en links naar de homepage en GitHub-pagina van de repository.
<div class="cards">
{#each $repos as repo}
<div class="card" data-index="0">
<div class="card-inner">
<div class="card-image-container">
<img class="card-image" src="images/1.svg" alt="Repository_image" />
<ul>
<li>⭐ {repo.stargazers_count}</li>
<li>👁️ {repo.watchers_count}</li>
<li>💻 {repo.language}</li>
</ul>
</div>
<div class="card-content">
<h3 class="card-title">{repo.name}</h3>
<p class="card-description">
{repo.description || "No description available."}
</p>
<div class="card-links">
<a href={repo.homepage} target="_blank">Website</a>
<a href={repo.html_url} target="_blank">GitHub</a>
</div>
</div>
</div>
</div>
{/each}
</div>
Door deze stappen te volgen, heb ik een dynamische en interactieve manier gecreëerd om mijn GitHub repositories weer te geven op mijn website. Het gebruik van de GitHub API maakt het eenvoudig om de meest actuele informatie over mijn projecten te tonen, terwijl de filtering en sortering ervoor zorgen dat alleen de belangrijkste en meest relevante repositories zichtbaar zijn.
Ik heb de enhance functie geïmporteerd van $app/forms om de formulierverwerking te verbeteren. Daarnaast heb ik variabelen aangemaakt om de inzendingsstatus van het formulier, succesbericht en foutbericht te beheren.
import { enhance } from "$app/forms";
import Contact from "./contact.svelte";
let isSubmitting = false;
let successMessage = "";
let errorMessage = "";
Ik heb een handleEnhance functie gemaakt om de formulierinzending te verwerken. Deze functie retourneert een handleSubmit functie die de status van het formulier bijwerkt op basis van het resultaat van de inzending.
function handleEnhance({ formElement }) {
const handleSubmit = async ({ result }) => {
isSubmitting = false;
successMessage = "";
errorMessage = "";
if (result.type === "failure") {
errorMessage = result.data.error;
} else if (result.type === "success") {
formElement.reset();
successMessage = result.data.message;
}
};
return handleSubmit;
}
In de HTML-structuur heb ik de enhance functie gebruikt voor de formulierinzending en de on:submit gebeurtenis ingesteld om de isSubmitting status bij te werken.
<h2>Contacteer Mij</h2>
<section>
<Contact />
<form
method="POST"
use:enhance={handleEnhance}
on:submit={() => (isSubmitting = true)}
class="contact-form"
>
<fieldset class="form-wrapper">
<label for="name" class="form-label"><span>Naam</span></label>
<input
type="text"
name="name"
id="name"
required
placeholder="Jan Jansen"
class="input-field"
/>
<label for="email" class="form-label"><span>Email</span></label>
<input
type="email"
name="email"
id="email"
required
placeholder="[email protected]"
class="input-field"
/>
<label for="message" class="form-label"><span>Bericht</span></label>
<textarea
name="message"
id="message"
required
placeholder="Typ je bericht hier..."
class="input-field"
></textarea>
</fieldset>
{#if successMessage}
<p class="success-message">{successMessage}</p>
{/if}
{#if errorMessage}
<p class="error-message">{errorMessage}</p>
{/if}
<button type="submit" class="submit-button" disabled={isSubmitting}>
{#if isSubmitting}
Verzenden...
{:else}
Verzend
{/if}
</button>
</form>
</section>
-
Ik heb de enhance functie van Svelte's $app/forms gebruikt om de formulierverwerking te verbeteren. Dit maakt het mogelijk om de inzending van het formulier met JavaScript af te handelen, terwijl het formulier ook zonder JavaScript goed blijft werken.
-
Het formulier heeft velden voor naam, e-mail en bericht, elk met een bijbehorend label en invoerveld.
-
De variabele isSubmitting beheert de inzendingsstatus. Deze schakelt de verzendknop uit en verandert de knoptekst in "Verzenden..." terwijl het formulier wordt ingediend.
-
De variabelen successMessage en errorMessage tonen respectievelijk een succes- of foutmelding aan de gebruiker na het indienen van het formulier.
- Bij het indienen van het formulier zet de on:submit gebeurtenis isSubmitting op true.
- De handleSubmit functie verwerkt de inzending. Als de inzending succesvol is, wordt het formulier gereset en een succesbericht weergegeven. Als de inzending mislukt, wordt een foutbericht getoond.