diff --git a/assets/scss/_partial/_customCursor.scss b/assets/scss/_partial/_customCursor.scss new file mode 100644 index 0000000..591c8f5 --- /dev/null +++ b/assets/scss/_partial/_customCursor.scss @@ -0,0 +1,21 @@ +.cursor { + position: fixed; + z-index: var(--z-cursor); + top: 0; + left: 0; + + display: none; + cursor: none; + pointer-events: none; + + color: white; + mix-blend-mode: difference; +} + +.active { + display: block; +} + +.cursorInner { + transform: translate3d(-50%, -50%, 0); +} diff --git a/assets/ts/customCursor.ts b/assets/ts/customCursor.ts deleted file mode 100644 index 030e1a2..0000000 --- a/assets/ts/customCursor.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { active } from './stage' - -let cursor: HTMLDivElement - -// create cursor -cursor = document.createElement('div') -cursor.className = 'cursor' -cursor.classList.add('active') -// create cursor inner -const cursorInner = document.createElement('div') -cursorInner.className = 'cursorInner' -// append cursor inner to cursor -cursor.append(cursorInner) - -function onMouse(e: MouseEvent) { - const x = e.clientX - const y = e.clientY - cursor.style.transform = `translate3d(${x}px, ${y}px, 0)` -} - -export function initCustomCursor(): void { - // append cursor to main - document.getElementById('main')!.append(cursor) - // bind mousemove event to window - window.addEventListener('mousemove', onMouse) - // add active callback - active.addWatcher(() => { - if (active.get()) { - cursor.classList.add('active') - } else { - cursor.classList.remove('active') - } - }) -} - -export function setCustomCursor(text: string): void { - cursorInner.innerText = text -} diff --git a/assets/ts/desktop/stageNav.ts b/assets/ts/desktop/stageNav.ts index 61e42b7..c126bd6 100644 --- a/assets/ts/desktop/stageNav.ts +++ b/assets/ts/desktop/stageNav.ts @@ -1,8 +1,8 @@ -import { setCustomCursor } from './customCursor' -import { decIndex, incIndex, state } from '../state' -import { increment, decrement } from '../utils' -import { cordHist, isOpen, isAnimating, active, minimizeImage } from './stage' import { container } from '../container' +import { decIndex, incIndex, state } from '../state' +import { decrement, increment } from '../utils' +import { setCustomCursor } from './customCursor' +import { active, cordHist, isAnimating, isOpen, minimizeImage } from './stage' /** * types diff --git a/assets/ts/stage.ts b/assets/ts/stage.ts deleted file mode 100644 index 8b22caf..0000000 --- a/assets/ts/stage.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { incIndex, getState } from './state' -import { gsap, Power3 } from 'gsap' -import { ImageJSON } from './resources' -import { Watchable } from './utils' - -// types - -export type HistoryItem = { i: number; x: number; y: number } - -// variables - -let imgs: HTMLImageElement[] = [] -let last = { x: 0, y: 0 } -export const cordHist = new Watchable([]) -export const isOpen = new Watchable(false) -export const isAnimating = new Watchable(false) -export const active = new Watchable(false) - -// getter - -function getElTrail(): HTMLImageElement[] { - return cordHist.get().map((item) => imgs[item.i]) -} - -function getElTrailCurrent(): HTMLImageElement[] { - return getElTrail().slice(-getState().trailLength) -} - -function getElTrailInactive(): HTMLImageElement[] { - const elTrailCurrent = getElTrailCurrent() - return elTrailCurrent.slice(0, elTrailCurrent.length - 1) -} - -function getElCurrent(): HTMLImageElement { - const elTrail = getElTrail() - return elTrail[elTrail.length - 1] -} - -// main functions - -// on mouse -function onMouse(e: MouseEvent): void { - if (isOpen.get() || isAnimating.get()) return - const cord = { x: e.clientX, y: e.clientY } - const travelDist = Math.hypot(cord.x - last.x, cord.y - last.y) - - if (travelDist > getState().threshold) { - last = cord - incIndex() - - const newHist = { i: getState().index, ...cord } - cordHist.set([...cordHist.get(), newHist].slice(-getState().length)) - } -} - -// set image position with gsap -function setPositions(): void { - const elTrail = getElTrail() - if (!elTrail.length) return - - gsap.set(elTrail, { - x: (i: number) => cordHist.get()[i].x - window.innerWidth / 2, - y: (i: number) => cordHist.get()[i].y - window.innerHeight / 2, - opacity: (i: number) => - i + 1 + getState().trailLength <= cordHist.get().length ? 0 : 1, - zIndex: (i: number) => i, - scale: 0.6 - }) - - if (isOpen.get()) { - gsap.set(imgs, { opacity: 0 }) - gsap.set(getElCurrent(), { opacity: 1, x: 0, y: 0, scale: 1 }) - } -} - -// open image into navigation -function expandImage(): void { - if (isAnimating.get()) return - - isOpen.set(true) - isAnimating.set(true) - - const tl = gsap.timeline() - // move down and hide trail inactive - tl.to(getElTrailInactive(), { - y: '+=20', - ease: Power3.easeIn, - stagger: 0.075, - duration: 0.3, - delay: 0.1, - opacity: 0 - }) - // current move to center - tl.to(getElCurrent(), { - x: 0, - y: 0, - ease: Power3.easeInOut, - duration: 0.7, - delay: 0.3 - }) - // current expand - tl.to(getElCurrent(), { - delay: 0.1, - scale: 1, - ease: Power3.easeInOut - }) - // finished - tl.then(() => { - isAnimating.set(false) - }) -} - -// close navigation and back to stage -export function minimizeImage(): void { - if (isAnimating.get()) return - - isOpen.set(false) - isAnimating.set(true) - - const tl = gsap.timeline() - // shrink current - tl.to(getElCurrent(), { - scale: 0.6, - duration: 0.6, - ease: Power3.easeInOut - }) - // move current to original position - tl.to(getElCurrent(), { - delay: 0.3, - duration: 0.7, - ease: Power3.easeInOut, - x: cordHist.get()[cordHist.get().length - 1].x - window.innerWidth / 2, - y: cordHist.get()[cordHist.get().length - 1].y - window.innerHeight / 2 - }) - // show trail inactive - tl.to(getElTrailInactive(), { - y: '-=20', - ease: Power3.easeOut, - stagger: -0.1, - duration: 0.3, - opacity: 1 - }) - // finished - tl.then(() => { - isAnimating.set(false) - }) -} - -// init - -export function initStage(ijs: ImageJSON[]): void { - // create stage element - createStage(ijs) - // get stage - const stage = document.getElementsByClassName('stage').item(0) as HTMLDivElement - // get image elements - imgs = Array.from(stage.getElementsByTagName('img')) - // event listeners - stage.addEventListener('click', () => expandImage()) - stage.addEventListener('keydown', () => expandImage()) - window.addEventListener('mousemove', onMouse) - // watchers - isOpen.addWatcher(() => active.set(isOpen.get() && !isAnimating.get())) - isAnimating.addWatcher(() => active.set(isOpen.get() && !isAnimating.get())) - cordHist.addWatcher(() => setPositions()) -} - -// hepler - -function createStage(ijs: ImageJSON[]): void { - // create container for images - const stage: HTMLDivElement = document.createElement('div') - stage.className = 'stage' - // append images to container - for (let ij of ijs) { - const e = document.createElement('img') - e.src = ij.url - e.height = ij.imgH - e.width = ij.imgW - e.alt = 'image' - stage.append(e) - } - document.getElementById('main')!.append(stage) -} diff --git a/assets/ts/stageNav.ts b/assets/ts/stageNav.ts deleted file mode 100644 index f37009c..0000000 --- a/assets/ts/stageNav.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { setCustomCursor } from './customCursor' -import { decIndex, incIndex, getState } from './state' -import { increment, decrement } from './utils' -import { cordHist, isOpen, isAnimating, active, minimizeImage } from './stage' - -type NavItem = (typeof navItems)[number] -const navItems = ['prev', 'close', 'next'] as const - -// main functions - -function handleClick(type: NavItem) { - switch (type) { - case 'prev': - prevImage() - break - case 'close': - minimizeImage() - break - case 'next': - nextImage() - break - } -} - -function handleKey(e: KeyboardEvent) { - if (isOpen.get() || isAnimating.get()) return - switch (e.key) { - case 'ArrowLeft': - prevImage() - break - case 'Escape': - minimizeImage() - break - case 'ArrowRight': - nextImage() - break - } -} - -// init - -export function initStageNav() { - const navOverlay = document.createElement('div') - navOverlay.className = 'navOverlay' - for (let navItem of navItems) { - const overlay = document.createElement('div') - overlay.className = 'overlay' - overlay.addEventListener('click', () => handleClick(navItem)) - overlay.addEventListener('keydown', () => handleClick(navItem)) - overlay.addEventListener('mouseover', () => setCustomCursor(navItem)) - overlay.addEventListener('focus', () => setCustomCursor(navItem)) - navOverlay.append(overlay) - } - active.addWatcher(() => { - if (active.get()) { - navOverlay.classList.add('active') - } else { - navOverlay.classList.remove('active') - } - }) - document.getElementById('main')!.append(navOverlay) - window.addEventListener('keydown', handleKey) -} - -// hepler - -function nextImage() { - if (isAnimating.get()) return - cordHist.set( - cordHist.get().map((item) => { - return { ...item, i: increment(item.i, getState().length) } - }) - ) - - incIndex() -} - -function prevImage() { - if (isAnimating.get()) return - cordHist.set( - cordHist.get().map((item) => { - return { ...item, i: decrement(item.i, getState().length) } - }) - ) - - decIndex() -}