Skip to content

Commit

Permalink
Merge pull request #2 from Musab-Hassan/dev
Browse files Browse the repository at this point in the history
Major design fixes
  • Loading branch information
Musab-Hassan committed Jan 31, 2022
2 parents d753e65 + 22767bb commit 32b2325
Show file tree
Hide file tree
Showing 9 changed files with 1,383 additions and 982 deletions.
1,785 changes: 1,018 additions & 767 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"bezier-easing": "^2.1.0",
"node-sass": "^7.0.1",
"sirv-cli": "^2.0.1",
"slickscrolljs": "^1.3.0",
"slickscrolljs": "^1.3.2",
"three": "^0.136.0"
}
}
1 change: 1 addition & 0 deletions src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ onMount(async () => {
// Remove horizontal scrolling
await scrollPromise;
scrollContainer.scrollTo(0, 0);
scrollContainer.style.overflowY = "hidden";
scrollContainer.style.overflowX = "hidden";
Expand Down
259 changes: 259 additions & 0 deletions src/animations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import BezierEasing from "bezier-easing";
import { asyncAnimation } from "./utils";
import anime from "animejs";

// Letter reveal animation used with the 'in:' and 'out:' svelte directives
export function letterSlide() {
return {
in: (node, params: { duration?: number, delay?: number, initDelay?: number, breakWord?: boolean, useAnime?: boolean }) => {

if (!params.delay) params.delay = 35;
if (!params.initDelay) params.initDelay = 0;
if (!params.duration) params.duration = 600;
if (params.breakWord === undefined) params.breakWord = true;
if (params.useAnime === undefined) params.useAnime = false;

let masks = wordWrapHandler(node, params.breakWord);

// Set default properties
masks.forEach(e => {
e.childNodes.forEach(child => {
child.style.transform = "translateX(150%)";
});
e.style.transform = "translateX(80%)";
e.style.display = "inline-flex";
e.style.overflow = "hidden";
});

let eased = 0, animeTargets = []; // t value with easing applied

masks.forEach((element) => {
let index = Array.from(element.parentNode.children).indexOf(element) + 1;

if (params.useAnime) { // Register children for use with anime
animeTargets = [...animeTargets, element, ...element.childNodes];
} else {
// Async animations for better performance in svetle tick only
element.childNodes.forEach(e => {
asyncAnimation((params.delay * index), () => {
e.style.transform = `translateX(${(150 + (-eased * 150)).toFixed(2)}%)`;
}, () => eased >= 1);
});

asyncAnimation((params.delay * index), () => {
element.style.transform = `translateX(${(80 + (-eased * 80)).toFixed(2)}%)`;
}, () => eased >= 1);
}
});

return {
delay: params.initDelay,
duration: params.duration,
tick: t => { // Svelte transitions
eased = BezierEasing(.2, .58, .43, 1)(t); // t value with easing applied
},
anime: (easing?) => { // Call animation outside of svelte blocks programmatically with animejs
if (!params.useAnime) return;
anime({
targets: animeTargets,
translateX: "0%",
easing: easing ? easing : "cubicBezier(.2, .58, .43, 1)",
duration: params.duration,
delay: anime.stagger(params.delay, {start: params.initDelay})
})
}
}
},

out: (node, params: { duration?: number, delay?: number, initDelay?: number, extraElems?: any }) => {

let masks = node.querySelectorAll(".a-text-mask");

if (!params.delay) params.delay = 25;
if (!params.initDelay) params.initDelay = 0;

return {
delay: params.initDelay,
duration: 600,
tick: t => {
// let eased = BezierEasing(.59, .11, .07, 1.07)(t);
let eased = BezierEasing(.32, .24, .76, .26)(t);

masks.forEach((e) => {
e.childNodes.forEach(i => {
i.style.transform = `translateX(${(-150 + (eased * 150)).toFixed(2)}%)`;
});

e.style.transform = `translateX(${(-80 + (eased * 80)).toFixed(2)}%)`;
});
}
}
}
}

// Wrap each word with a mask and return masks
function wordWrapHandler(node, breakWord: boolean) {

let masks = node.querySelectorAll(".a-text-mask");

if (masks.length < 1) {
node.innerHTML = parseLetters(node.innerHTML, "<div class=\"a-text-mask\"><div class=\"a-text-block\">", "</div></div>");
masks = node.querySelectorAll(".a-text-mask");
}

if (breakWord) {
let words = node.querySelectorAll(".a-word");
words.forEach(element => {
element.style.display = "inline-block";
element.style.marginRight = "0.5vw"
element.style.whiteSpace = "nowrap"
});
}

return masks;

// parse the letters and break apart words
function parseLetters(string: string, startWord: string, endWord: string) {
let newString = "";
let isTag = false;
let isWord = false;

[...string].forEach((e, i) => {
if (e === " " || string[i - 1] === " " || i === 0 || i === string.length) {
isWord = !isWord;
newString += isWord ? "<div class=\"a-word\">" : "</div>";
}
if (e == "<") isTag = true;
if (string[i - 1] == ">") isTag = false;

if (!isTag && e !== " ") newString += startWord + e + endWord;
});

return newString;
}
}
}


// Mask reveal animation used with the 'in:' and 'out:' svelte directives
export function maskSlide() {
return {
in: (node, params: { duration?: number, delay?: number, maskStyles?: { property: string, value: string }[] }) => {

if (!params.delay) params.delay = 20;
if (!params.duration) params.duration = 700;

let mask = addMask();

return {
delay: params.delay,
duration: params.duration,
tick: t => {
let eased = BezierEasing(.2, .58, .43, 1)(t);

mask.style.transform = `translateX(${(100 + (-eased * 100)).toFixed(2)}%)`;
node.style.transform = `translateX(${(-100 + (eased * 100)).toFixed(2)}%)`;
},
anime: (easing?) => {
anime({
targets: [mask, node],
translateX: "0%",
easing: easing ? easing : "cubicBezier(.2, .58, .43, 1)",
duration: params.duration,
delay: params.delay
})
}
}

function addMask() {
let mask = document.createElement("div");
let parent = node.parentNode;
let index = Array.from(parent.children).indexOf(node);

mask.classList.add("a-mask");
node.classList.add("a-content");
mask.insertBefore(node, mask.children[0]);
mask.style.display = "inline-block";
mask.style.overflow = "hidden";
if (params.maskStyles) {
params.maskStyles.forEach(element => {
mask.style[element.property] = element.value;
});
}

parent.insertBefore(mask, parent.children[index]);

mask.style.transform = "translateX(100%)";
node.style.transform = "translateX(-100%)"
return mask;
}
},

out: (node, params: { duration?: number, delay?: number }) => {

if (!params.delay) params.delay = 0;
if (!params.duration) params.duration = 400;

return {
delay: params.delay,
duration: params.duration,
tick: t => {
let eased = BezierEasing(.32, .24, .76, .26)(t);

node.style.transform = `translateX(${(-100 + (eased * 100)).toFixed(2)}%)`;
}
}
}
}
}

// Animation for workItem image when workContainer is scrolled into view
export function workImageIntro(node, params: { promise, delay?: number }) {
if (!params.delay) params.delay = 0;

node.style.width = "0%";
node.style.marginRight = "0%";
node.style.opacity = "0"
node.style.transition = "none"

params.promise.then(() => {
anime({
targets: node,
width: "85%",
marginRight: "15%",
opacity: "1",
easing: "easeInOutQuint",
duration: 1300,
delay: params.delay,
complete: () => {
node.style.opacity = null;
node.style.width = null;
node.style.marginRight = null;
node.style.transition = null;
}
});
});
}

// Opacity animation for workItem when workContainer is scrolled into view
export function workOpacityIntro(node, params: { promise, delay?: number }) {
if (!params.delay) params.delay = 0;

node.style.transform = "translateX(50%)";
node.style.opacity = "0"

params.promise.then(() => {
anime({
targets: node,
translateX: "0%",
opacity: "1",
easing: "easeInOutQuint",
duration: 1300,
delay: params.delay + 100,
complete: () => {
node.style.opacity = null;
node.style.transform = null;
}
});
});
}
4 changes: 3 additions & 1 deletion src/components/nav.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ const navigate = pos => { scrollContainer.scrollTo(0, pos); mobileActive = false
src="assets/imgs/logo.svg"
class = "logo-icon clickable"
alt="Logo"
draggable="false">
draggable="false"
on:click={() => navigate(positions.home)}
>
</div>

<div class="flex-wrapper">
Expand Down
49 changes: 41 additions & 8 deletions src/sections/about.svelte
Original file line number Diff line number Diff line change
@@ -1,26 +1,56 @@
<script lang="ts">
import { onMount } from "svelte";
import { letterSlide, maskSlide } from "../animations";
import { aboutPosition, clickables } from "../store";
let aboutContainer;
let githubLink, emailLink
let profilePicContainer;
// Animation elements
let title, paragraph, image, links;
onMount(() => {
$aboutPosition = aboutContainer.offsetTop; // Update current height for nav scrolling
window.onresize = () => $aboutPosition = aboutContainer.offsetTop; // Update current height for nav scrolling
$aboutPosition = aboutContainer.offsetTop - (window.innerHeight / 5); // Update current height for nav scrolling
window.onresize = () => $aboutPosition = aboutContainer.offsetTop - (window.innerHeight / 5); // Update current height for nav scrolling
clickables.update(value => [...value, githubLink, emailLink]);
slickScroll.then((slick) => {
slick.addOffset({
element: profilePicContainer,
speedY: 0.8
});
})
// Scroll activated animations using anime instead of svelte transitions
const titleAnimate = letterSlide().in(title, { useAnime: true, delay: 15 });
const paragraphAnimate = letterSlide().in(paragraph, { useAnime: true, delay: 2 });
const linkAnimate = maskSlide().in(links, { delay: 500 });
const imageAnimate = maskSlide().in(image, {
duration: 1200,
maskStyles: [
{ property: "width", value: "100%"},
{ property: "height", value: "100%"}
]
});
let observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
titleAnimate.anime();
paragraphAnimate.anime();
linkAnimate.anime();
imageAnimate.anime("easeInOutQuint");
observer.disconnect();
}
});
}, { root: null, threshold: 0.4 });
observer.observe(aboutContainer);
});
export let slickScroll;
Expand All @@ -29,17 +59,19 @@ export let slickScroll;

<div id="content-container" class="about" bind:this={aboutContainer}>
<div class="content-wrapper">
<h1 class = "title">The Name's<br>Musab</h1>
<p class = "paragraph">
<h1 class = "title" bind:this={title}>The Name's<br>Musab</h1>
<p class = "paragraph" bind:this={paragraph}>
I'm a web developer from British Columbia, Canada. I love writing beautiful code and creating memorable web experiences. <br><br>I work with organizations and individuals to create taylor-made experiences designed to be beautiful, scalable, and affordable. If you like my work, you can send me an email and we can chat.
</p>
<div class="social-button-wrapper">
<span class="button" bind:this={emailLink}><a href="mailto:musabhassan04@gmail.com" target="_blank" class="clickable sublink link">Email</a></span>
<span class="button" bind:this={githubLink}><a href="https://github.com/Musab-Hassan" target="_blank" class="clickable sublink link">Github</a></span>
<div bind:this={links}>
<span class="button" bind:this={emailLink}><a href="mailto:musabhassan04@gmail.com" target="_blank" class="clickable sublink link">Email</a></span>
<span class="button" bind:this={githubLink}><a href="https://github.com/Musab-Hassan" target="_blank" class="clickable sublink link">Github</a></span>
</div>
</div>
</div>
<div class="profile-image" bind:this={profilePicContainer}>
<img src="assets/imgs/profile-photo.jpg" alt="Musab's Sillouette" class="profile-pic">
<img bind:this={image} src="assets/imgs/profile-photo.jpg" alt="Musab's Sillouette" class="profile-pic">
</div>
</div>
<div class="horizontal-flex">
Expand Down Expand Up @@ -95,6 +127,7 @@ export let slickScroll;
width: 70%
overflow: hidden
margin-top: -30vh
position: relative
img
height: 80%
Expand Down
Loading

0 comments on commit 32b2325

Please sign in to comment.