diff --git a/assets/ts/desktop/customCursor.ts b/assets/ts/desktop/customCursor.ts index 8d91739..5476a35 100644 --- a/assets/ts/desktop/customCursor.ts +++ b/assets/ts/desktop/customCursor.ts @@ -1,6 +1,7 @@ -import { active } from './stage' import { container } from '../container' +import { active } from './stage' + /** * variables */ @@ -12,7 +13,7 @@ const cursorInner = document.createElement('div') * main functions */ -function onMouse(e: MouseEvent) { +function onMouse(e: MouseEvent): void { const x = e.clientX const y = e.clientY cursor.style.transform = `translate3d(${x}px, ${y}px, 0)` @@ -35,7 +36,7 @@ export function initCustomCursor(): void { // append cursor to main container.append(cursor) // bind mousemove event to window - window.addEventListener('mousemove', onMouse) + window.addEventListener('mousemove', onMouse, { passive: true }) // add active callback active.addWatcher(() => { if (active.get()) { diff --git a/assets/ts/desktop/init.ts b/assets/ts/desktop/init.ts new file mode 100644 index 0000000..923752b --- /dev/null +++ b/assets/ts/desktop/init.ts @@ -0,0 +1,11 @@ +import { type ImageJSON } from '../resources' + +import { initCustomCursor } from './customCursor' +import { initStage } from './stage' +import { initStageNav } from './stageNav' + +export function initDesktop(ijs: ImageJSON[]): void { + initCustomCursor() + initStage(ijs) + initStageNav() +} diff --git a/assets/ts/desktop/stage.ts b/assets/ts/desktop/stage.ts index 1614479..04a0eb3 100644 --- a/assets/ts/desktop/stage.ts +++ b/assets/ts/desktop/stage.ts @@ -1,14 +1,19 @@ import { Power3, gsap } from 'gsap' + import { container } from '../container' -import { ImageJSON } from '../resources' +import { type ImageJSON } from '../resources' import { incIndex, state } from '../state' -import { Watchable } from '../utils' +import { Watchable, decrement, increment } from '../utils' /** * types */ -export type HistoryItem = { i: number; x: number; y: number } +export interface HistoryItem { + i: number + x: number + y: number +} /** * variables @@ -44,7 +49,22 @@ function getElCurrent(): HTMLImageElement { } function getElNextFive(): HTMLImageElement[] { - return state.get().nextFive.map((i) => imgs[i]) + const s = state.get() + const els = [] + for (let i = 0; i < 5; i++) { + els.push(imgs[increment(s.index + i, s.length)]) + } + return els +} + +function getElPrev(): HTMLImageElement { + const s = state.get() + return imgs[increment(s.index, s.length)] +} + +function getElNext(): HTMLImageElement { + const s = state.get() + return imgs[decrement(s.index, s.length)] } /** @@ -69,7 +89,7 @@ function onMouse(e: MouseEvent): void { // set image position with gsap function setPositions(): void { const elTrail = getElTrail() - if (!elTrail.length) return + if (elTrail.length === 0) return // preload lores(getElNextFive()) @@ -98,7 +118,7 @@ function expandImage(): void { isOpen.set(true) isAnimating.set(true) - hires([getElCurrent()]) + hires([getElCurrent(), getElPrev(), getElNext()]) const tl = gsap.timeline() // move down and hide trail inactive @@ -125,6 +145,7 @@ function expandImage(): void { ease: Power3.easeInOut }) // finished + // eslint-disable-next-line @typescript-eslint/no-floating-promises tl.then(() => { isAnimating.set(false) }) @@ -137,6 +158,9 @@ export function minimizeImage(): void { isOpen.set(false) isAnimating.set(true) + lores([getElCurrent()]) + lores(getElTrailInactive()) + const tl = gsap.timeline() // shrink current tl.to(getElCurrent(), { @@ -161,6 +185,7 @@ export function minimizeImage(): void { opacity: 1 }) // finished + // eslint-disable-next-line @typescript-eslint/no-floating-promises tl.then(() => { isAnimating.set(false) }) @@ -178,13 +203,23 @@ export function initStage(ijs: ImageJSON[]): void { // get image elements imgs = Array.from(stage.getElementsByTagName('img')) // event listeners - stage.addEventListener('click', () => expandImage()) - stage.addEventListener('keydown', () => expandImage()) - window.addEventListener('mousemove', onMouse) + stage.addEventListener('click', () => { + expandImage() + }) + stage.addEventListener('keydown', () => { + expandImage() + }) + window.addEventListener('mousemove', onMouse, { passive: true }) // watchers - isOpen.addWatcher(() => active.set(isOpen.get() && !isAnimating.get())) - isAnimating.addWatcher(() => active.set(isOpen.get() && !isAnimating.get())) - cordHist.addWatcher(() => setPositions()) + isOpen.addWatcher(() => { + active.set(isOpen.get() && !isAnimating.get()) + }) + isAnimating.addWatcher(() => { + active.set(isOpen.get() && !isAnimating.get()) + }) + cordHist.addWatcher(() => { + setPositions() + }) // preload lores(getElNextFive()) } @@ -198,7 +233,7 @@ function createStage(ijs: ImageJSON[]): void { const stage: HTMLDivElement = document.createElement('div') stage.className = 'stage' // append images to container - for (let ij of ijs) { + for (const ij of ijs) { const e = document.createElement('img') e.height = ij.loImgH e.width = ij.loImgW @@ -217,16 +252,16 @@ function createStage(ijs: ImageJSON[]): void { function hires(imgs: HTMLImageElement[]): void { imgs.forEach((img) => { - img.src = img.dataset.hiUrl! - img.height = parseInt(img.dataset.hiImgH!) - img.width = parseInt(img.dataset.hiImgW!) + img.src = img.dataset.hiUrl as string + img.height = parseInt(img.dataset.hiImgH as string) + img.width = parseInt(img.dataset.hiImgW as string) }) } function lores(imgs: HTMLImageElement[]): void { imgs.forEach((img) => { - img.src = img.dataset.loUrl! - img.height = parseInt(img.dataset.loImgH!) - img.width = parseInt(img.dataset.loImgW!) + img.src = img.dataset.loUrl as string + img.height = parseInt(img.dataset.loImgH as string) + img.width = parseInt(img.dataset.loImgW as string) }) } diff --git a/assets/ts/desktop/stageNav.ts b/assets/ts/desktop/stageNav.ts index c126bd6..fe164d1 100644 --- a/assets/ts/desktop/stageNav.ts +++ b/assets/ts/desktop/stageNav.ts @@ -1,6 +1,7 @@ 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' @@ -20,7 +21,7 @@ const navItems = ['prev', 'close', 'next'] as const * main functions */ -function handleClick(type: NavItem) { +function handleClick(type: NavItem): void { switch (type) { case 'prev': prevImage() @@ -34,7 +35,7 @@ function handleClick(type: NavItem) { } } -function handleKey(e: KeyboardEvent) { +function handleKey(e: KeyboardEvent): void { if (isOpen.get() || isAnimating.get()) return switch (e.key) { case 'ArrowLeft': @@ -53,16 +54,40 @@ function handleKey(e: KeyboardEvent) { * init */ -export function initStageNav() { +export function initStageNav(): void { const navOverlay = document.createElement('div') navOverlay.className = 'navOverlay' - for (let navItem of navItems) { + for (const 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)) + overlay.addEventListener( + 'click', + () => { + handleClick(navItem) + }, + { passive: true } + ) + overlay.addEventListener( + 'keydown', + () => { + handleClick(navItem) + }, + { passive: true } + ) + overlay.addEventListener( + 'mouseover', + () => { + setCustomCursor(navItem) + }, + { passive: true } + ) + overlay.addEventListener( + 'focus', + () => { + setCustomCursor(navItem) + }, + { passive: true } + ) navOverlay.append(overlay) } active.addWatcher(() => { @@ -73,14 +98,14 @@ export function initStageNav() { } }) container.append(navOverlay) - window.addEventListener('keydown', handleKey) + window.addEventListener('keydown', handleKey, { passive: true }) } /** * hepler */ -function nextImage() { +function nextImage(): void { if (isAnimating.get()) return cordHist.set( cordHist.get().map((item) => { @@ -91,7 +116,7 @@ function nextImage() { incIndex() } -function prevImage() { +function prevImage(): void { if (isAnimating.get()) return cordHist.set( cordHist.get().map((item) => { diff --git a/assets/ts/main.ts b/assets/ts/main.ts index c2d0a53..a2c2e79 100644 --- a/assets/ts/main.ts +++ b/assets/ts/main.ts @@ -1,27 +1,20 @@ import { initContainer } from './container' -import { initCustomCursor } from './desktop/customCursor' -import { initStage } from './desktop/stage' -import { initStageNav } from './desktop/stageNav' -import { initCollection } from './mobile/collection' -import { initGallery } from './mobile/gallery' import { initNav } from './nav' import { initResources } from './resources' import { initState } from './state' import { isMobile } from './utils' initContainer() -initCustomCursor() const ijs = initResources() initState(ijs.length) - initNav() if (ijs.length > 0) { if (!isMobile()) { - initStage(ijs) - initStageNav() + const d = await import('./desktop/stage') + d.initStage(ijs) } else { - initCollection(ijs) - initGallery(ijs) + const m = await import('./mobile/init') + m.initMobile(ijs) } } diff --git a/assets/ts/mobile/collection.ts b/assets/ts/mobile/collection.ts index f8c08c4..84d224a 100644 --- a/assets/ts/mobile/collection.ts +++ b/assets/ts/mobile/collection.ts @@ -1,7 +1,8 @@ import { container } from '../container' -import { ImageJSON } from '../resources' -import { getNextFive, setIndex } from '../state' +import { type ImageJSON } from '../resources' +import { setIndex } from '../state' import { Watchable, getRandom, onVisible } from '../utils' + import { slideUp } from './gallery' /** @@ -42,16 +43,27 @@ export function initCollection(ijs: ImageJSON[]): void { imgs = Array.from(collection.getElementsByTagName('img')) // add event listeners imgs.forEach((img, i) => { - img.addEventListener('click', () => handleClick(i)) - img.addEventListener('keydown', () => handleClick(i)) + img.addEventListener( + 'click', + () => { + handleClick(i) + }, + { passive: true } + ) + img.addEventListener( + 'keydown', + () => { + handleClick(i) + }, + { passive: true } + ) // preload onVisible(img, () => { - // minues one because we want to preload the current and the next 4 images - getNextFive(i - 1, imgs.length) - .map((i) => imgs[i]) - .forEach((e) => { - e.src = e.dataset.src! - }) + for (let _i = 0; _i < 5; _i++) { + const n = i + _i + if (n < 0 || n > imgs.length - 1) continue + imgs[n].src = imgs[n].dataset.src as string + } }) }) } @@ -65,7 +77,7 @@ function createCollection(ijs: ImageJSON[]): void { const _collection: HTMLDivElement = document.createElement('div') _collection.className = 'collection' // append images to container - for (let [i, ij] of ijs.entries()) { + for (const [i, ij] of ijs.entries()) { // random x and y const x = i !== 0 ? getRandom(-25, 25) : 0 const y = i !== 0 ? getRandom(-30, 30) : 0 diff --git a/assets/ts/mobile/gallery.ts b/assets/ts/mobile/gallery.ts index d585660..f983789 100644 --- a/assets/ts/mobile/gallery.ts +++ b/assets/ts/mobile/gallery.ts @@ -1,9 +1,11 @@ import { Power3, gsap } from 'gsap' -import Swiper from 'swiper' +import { Swiper } from 'swiper' + import { container } from '../container' -import { ImageJSON } from '../resources' +import { type ImageJSON } from '../resources' import { setIndex, state } from '../state' import { Watchable, expand } from '../utils' + import { imgs, mounted } from './collection' import { scrollable } from './scroll' @@ -46,7 +48,6 @@ export function slideUp(): void { setTimeout(() => { scrollable.set(false) isAnimating.set(false) - console.log(swiper.activeIndex) }, 1200) } @@ -76,7 +77,7 @@ export function initGallery(ijs: ImageJSON[]): void { createGallery(ijs) // get elements indexDispNums = Array.from( - document.getElementsByClassName('nav').item(0)!.getElementsByClassName('num') + document.getElementsByClassName('nav').item(0)?.getElementsByClassName('num') ?? [] ) as HTMLSpanElement[] swiperNode = document.getElementsByClassName('galleryInner').item(0) as HTMLDivElement gallery = document.getElementsByClassName('gallery').item(0) as HTMLDivElement @@ -100,9 +101,10 @@ export function initGallery(ijs: ImageJSON[]): void { setIndex(realIndex) }) }) - // mounted mounted.set(true) + // dynamic load + window.addEventListener('touchstart', () => {}) } /** @@ -111,7 +113,7 @@ export function initGallery(ijs: ImageJSON[]): void { function changeSlide(slide: number): void { loadImages() - swiper!.slideTo(slide, 0) + swiper.slideTo(slide, 0) } function scrollToActive(): void { @@ -151,7 +153,7 @@ function createGallery(ijs: ImageJSON[]): void { const _swiperWrapper = document.createElement('div') _swiperWrapper.className = 'swiper-wrapper' // swiper slide - for (let ij of ijs) { + for (const ij of ijs) { const _swiperSlide = document.createElement('div') _swiperSlide.className = 'swiper-slide' // img @@ -179,8 +181,12 @@ function createGallery(ijs: ImageJSON[]): void { // close const _close = document.createElement('div') _close.innerText = 'Close' - _close.addEventListener('click', () => slideDown()) - _close.addEventListener('keydown', () => slideDown()) + _close.addEventListener('click', () => { + slideDown() + }) + _close.addEventListener('keydown', () => { + slideDown() + }) // nav const _navDiv = document.createElement('div') _navDiv.className = 'nav' @@ -210,14 +216,14 @@ function createGallery(ijs: ImageJSON[]): void { */ function loadImages(): void { - const activeImages = new Array() + const activeImages = [] // load current, next, prev image activeImages.push(galleryImages[swiper.activeIndex]) activeImages.push( galleryImages[Math.min(swiper.activeIndex + 1, swiper.slides.length)] ) activeImages.push(galleryImages[Math.max(swiper.activeIndex - 1, 0)]) - for (let e of activeImages) { - e.src = e.dataset.src! + for (const e of activeImages) { + e.src = e.dataset.src as string } } diff --git a/assets/ts/mobile/init.ts b/assets/ts/mobile/init.ts new file mode 100644 index 0000000..110ea5f --- /dev/null +++ b/assets/ts/mobile/init.ts @@ -0,0 +1,9 @@ +import { type ImageJSON } from '../resources' + +import { initCollection } from './collection' +import { initGallery } from './gallery' + +export function initMobile(ijs: ImageJSON[]): void { + initCollection(ijs) + initGallery(ijs) +} diff --git a/assets/ts/nav.ts b/assets/ts/nav.ts index 78cc2fc..f3a6051 100644 --- a/assets/ts/nav.ts +++ b/assets/ts/nav.ts @@ -39,13 +39,16 @@ const links = Array.from(linksDiv.getElementsByClassName('link')) as HTMLAnchorE // current link index const currentLinkIndex = document - .getElementById('main')! - .getAttribute('currentMenuItemIndex') as string + .getElementById('main') + ?.getAttribute('currentMenuItemIndex') as string // set current link -for (let [index, link] of links.entries()) { +for (const [index, link] of links.entries()) { if (index === parseInt(currentLinkIndex)) { + // set current link style link.classList.add('current') + // set current link title (only if not home) + if (index !== 0) document.title = link.innerText + ' | ' + document.title } } @@ -53,14 +56,26 @@ for (let [index, link] of links.entries()) { * init */ -export function initNav() { +export function initNav(): void { // init threshold text updateThresholdText() // init index text updateIndexText() // event listeners - decButton.addEventListener('click', () => decThreshold()) - incButton.addEventListener('click', () => incThreshold()) + decButton.addEventListener( + 'click', + () => { + decThreshold() + }, + { passive: true } + ) + incButton.addEventListener( + 'click', + () => { + incThreshold() + }, + { passive: true } + ) } // helper diff --git a/assets/ts/resources.ts b/assets/ts/resources.ts index 74aff97..f57f5a5 100644 --- a/assets/ts/resources.ts +++ b/assets/ts/resources.ts @@ -11,7 +11,7 @@ export interface ImageJSON { export function initResources(): ImageJSON[] { const imagesJson = document.getElementById('imagesSource') - if (!imagesJson) { + if (imagesJson === null) { return [] } return JSON.parse(imagesJson.textContent as string).sort( diff --git a/assets/ts/state.ts b/assets/ts/state.ts index 37d98ad..d4e96aa 100644 --- a/assets/ts/state.ts +++ b/assets/ts/state.ts @@ -21,7 +21,6 @@ const thresholds = [ const defaultState = { index: -1, - nextFive: new Array(), // for preload length: 0, threshold: thresholds[2].threshold, trailLength: thresholds[2].trailLength @@ -36,7 +35,6 @@ export const state = new Watchable(defaultState) export function initState(length: number): void { const s = state.get() s.length = length - s.nextFive = getNextFive(s.index, s.length) state.set(s) state.addWatcher(() => { updateIndexText() @@ -47,21 +45,18 @@ export function initState(length: number): void { export function setIndex(index: number): void { const s = state.get() s.index = index - s.nextFive = getNextFive(s.index, s.length) state.set(s) } export function incIndex(): void { const s = state.get() s.index = increment(s.index, s.length) - s.nextFive = getNextFive(s.index, s.length) state.set(s) } export function decIndex(): void { const s = state.get() s.index = decrement(s.index, s.length) - s.nextFive = getNextFive(s.index, s.length) state.set(s) } @@ -82,17 +77,9 @@ export function decThreshold(): void { */ function updateThreshold(state: State, inc: number): State { - const i = thresholds.findIndex((t) => state.threshold === t.threshold) - const newItems = thresholds[i + inc] - // out of range - if (!newItems) return state + const i = thresholds.findIndex((t) => state.threshold === t.threshold) + inc + // out of bounds + if (i < 0 || i >= thresholds.length) return state + const newItems = thresholds[i] return { ...state, ...newItems } } - -export function getNextFive(index: number, length: number): number[] { - const five = [] - for (let i = 0; i < 5; i++) { - five.push(increment(index + i, length)) - } - return five -} diff --git a/assets/ts/utils.ts b/assets/ts/utils.ts index 4fda31f..751a3c0 100644 --- a/assets/ts/utils.ts +++ b/assets/ts/utils.ts @@ -18,14 +18,14 @@ export function isMobile(): boolean { return window.matchMedia('(hover: none)').matches } -export function getRandom(min: number, max: number) { +export function getRandom(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min } -export function onVisible( - element: HTMLImageElement, - callback: (arg0: HTMLImageElement) => void -) { +export function onVisible( + element: T, + callback: (arg0: T) => void +): void { new IntersectionObserver((entries, observer) => { entries.forEach((entry) => { if (entry.intersectionRatio > 0) { @@ -34,7 +34,6 @@ export function onVisible( } }) }).observe(element) - if (!callback) return new Promise((r) => (callback = r)) } /** @@ -43,7 +42,7 @@ export function onVisible( export class Watchable { constructor(private obj: T) {} - private watchers: (() => void)[] = [] + private readonly watchers: Array<() => void> = [] get(): T { return this.obj @@ -51,7 +50,9 @@ export class Watchable { set(e: T): void { this.obj = e - this.watchers.forEach((watcher) => watcher()) + this.watchers.forEach((watcher) => { + watcher() + }) } addWatcher(watcher: () => void): void {