mirror of
https://github.com/Sped0n/bridget.git
synced 2026-04-22 14:09:30 -07:00
fix(main.ts): import correct functions from utils module
feat(stage.ts): implement stage navigation functionality feat(stageNav.ts): implement stage navigation overlay functionality feat(state.ts): implement state management for index and threshold feat(utils.ts): add utility functions for increment and decrement
This commit is contained in:
38
assets/ts/customCursor.ts
Normal file
38
assets/ts/customCursor.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { addActiveCallback } 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
|
||||||
|
addActiveCallback((active) => {
|
||||||
|
if (active) {
|
||||||
|
cursor.classList.add('active')
|
||||||
|
} else {
|
||||||
|
cursor.classList.remove('active')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCustomCursor(text: string): void {
|
||||||
|
cursorInner.innerText = text
|
||||||
|
}
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { type ImageData } from './utils'
|
|
||||||
|
|
||||||
// fetch images info from JSON
|
|
||||||
const imageArrayElement = document.getElementById('images_info') as HTMLScriptElement
|
|
||||||
const rawImagesInfo = imageArrayElement.textContent as string
|
|
||||||
export const imagesInfo: ImageData[] = JSON.parse(rawImagesInfo).sort(
|
|
||||||
(a: ImageData, b: ImageData) => {
|
|
||||||
if (a.index < b.index) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
export const imagesLen: number = imagesInfo.length
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
import { overlayEnable } from './overlay'
|
|
||||||
import {
|
|
||||||
calcImageIndex,
|
|
||||||
center,
|
|
||||||
delay,
|
|
||||||
mouseToTransform,
|
|
||||||
pushIndex,
|
|
||||||
type position,
|
|
||||||
hideImage
|
|
||||||
} from './utils'
|
|
||||||
import { thresholdIndex, thresholdSensitivityArray } from './thresholdCtl'
|
|
||||||
import { imgIndexSpanUpdate } from './indexDisp'
|
|
||||||
import { imagesLen } from './dataFetch'
|
|
||||||
import { imagesDivNodes as images } from './elemGen'
|
|
||||||
|
|
||||||
// global index for "activated"
|
|
||||||
export let globalIndex: number = 0
|
|
||||||
// last position set as "activated"
|
|
||||||
let last: position = { x: 0, y: 0 }
|
|
||||||
export let trailingImageIndexes: number[] = []
|
|
||||||
// only used in overlay disable, for storing positions temporarily
|
|
||||||
export let transformCache: string[] = []
|
|
||||||
// abort controller for enter overlay event listener
|
|
||||||
let EnterOverlayClickAbCtl = new AbortController()
|
|
||||||
// stack depth of images array
|
|
||||||
export let stackDepth: number = 5
|
|
||||||
export let lastStackDepth: number = 5
|
|
||||||
|
|
||||||
export const addEnterOverlayEL = (e: HTMLImageElement): void => {
|
|
||||||
EnterOverlayClickAbCtl.abort()
|
|
||||||
EnterOverlayClickAbCtl = new AbortController()
|
|
||||||
e.addEventListener(
|
|
||||||
'click',
|
|
||||||
() => {
|
|
||||||
void enterOverlay()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
passive: true,
|
|
||||||
once: true,
|
|
||||||
signal: EnterOverlayClickAbCtl.signal
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// activate top image
|
|
||||||
const activate = (index: number, mouseX: number, mouseY: number): void => {
|
|
||||||
addEnterOverlayEL(images[index])
|
|
||||||
if (stackDepth !== lastStackDepth) {
|
|
||||||
trailingImageIndexes.push(index)
|
|
||||||
refreshStack()
|
|
||||||
lastStackDepth = stackDepth
|
|
||||||
}
|
|
||||||
const indexesNum: number = pushIndex(
|
|
||||||
index,
|
|
||||||
trailingImageIndexes,
|
|
||||||
stackDepth,
|
|
||||||
images,
|
|
||||||
imagesLen
|
|
||||||
)
|
|
||||||
// set img position
|
|
||||||
images[index].style.transform = mouseToTransform(mouseX, mouseY, true, true)
|
|
||||||
images[index].dataset.status = 'null'
|
|
||||||
// reset z index
|
|
||||||
for (let i = indexesNum; i > 0; i--) {
|
|
||||||
images[trailingImageIndexes[i - 1]].style.zIndex = `${i}`
|
|
||||||
}
|
|
||||||
images[index].style.visibility = 'visible'
|
|
||||||
last = { x: mouseX, y: mouseY }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare the current mouse position with the last activated position
|
|
||||||
const distanceFromLast = (x: number, y: number): number => {
|
|
||||||
return Math.hypot(x - last.x, y - last.y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle mouse move
|
|
||||||
export const handleOnMove = (e: MouseEvent): void => {
|
|
||||||
// meet threshold
|
|
||||||
if (
|
|
||||||
distanceFromLast(e.clientX, e.clientY) >
|
|
||||||
window.innerWidth / thresholdSensitivityArray[thresholdIndex]
|
|
||||||
) {
|
|
||||||
// calculate the actual index
|
|
||||||
const imageIndex = calcImageIndex(globalIndex, imagesLen)
|
|
||||||
// show top image and change index
|
|
||||||
activate(imageIndex, e.clientX, e.clientY)
|
|
||||||
imgIndexSpanUpdate(imageIndex + 1, imagesLen)
|
|
||||||
// self increment
|
|
||||||
globalIndexInc()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function enterOverlay(): Promise<void> {
|
|
||||||
// stop images animation
|
|
||||||
window.removeEventListener('mousemove', handleOnMove)
|
|
||||||
// get index array length
|
|
||||||
const indexesNum: number = trailingImageIndexes.length
|
|
||||||
for (let i = 0; i < indexesNum; i++) {
|
|
||||||
// create image element
|
|
||||||
const e: HTMLImageElement = images[trailingImageIndexes[i]]
|
|
||||||
// cache images' position
|
|
||||||
transformCache.push(e.style.transform)
|
|
||||||
// set style for the images
|
|
||||||
if (i === indexesNum - 1) {
|
|
||||||
e.style.transitionDelay = `${0.1 * i + 0.2}s, ${0.1 * i + 0.2 + 0.5}s`
|
|
||||||
e.dataset.status = 'top'
|
|
||||||
center(e)
|
|
||||||
} else {
|
|
||||||
e.style.transitionDelay = `${0.1 * i}s`
|
|
||||||
e.dataset.status = 'trail'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// sleep
|
|
||||||
await delay(stackDepth * 100 + 100 + 1000)
|
|
||||||
// post process
|
|
||||||
for (let i = 0; i < indexesNum; i++) {
|
|
||||||
images[trailingImageIndexes[i]].style.transitionDelay = ''
|
|
||||||
if (i === indexesNum - 1) {
|
|
||||||
images[trailingImageIndexes[i]].dataset.status = 'overlay'
|
|
||||||
} else {
|
|
||||||
images[trailingImageIndexes[i]].style.visibility = 'hidden'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Offset previous self increment of global index (by handleOnMove)
|
|
||||||
globalIndexDec()
|
|
||||||
// overlay init
|
|
||||||
overlayEnable()
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialization
|
|
||||||
export const trackMouseInit = (): void => {
|
|
||||||
window.addEventListener('mousemove', handleOnMove)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const globalIndexDec = (): void => {
|
|
||||||
globalIndex--
|
|
||||||
}
|
|
||||||
|
|
||||||
export const globalIndexInc = (): void => {
|
|
||||||
globalIndex++
|
|
||||||
}
|
|
||||||
|
|
||||||
export const emptyTransformCache = (): void => {
|
|
||||||
transformCache = []
|
|
||||||
}
|
|
||||||
|
|
||||||
export const emptyTrailingImageIndexes = (): void => {
|
|
||||||
trailingImageIndexes = []
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setStackDepth = (newStackDepth: number): void => {
|
|
||||||
if (stackDepth !== newStackDepth) {
|
|
||||||
lastStackDepth = stackDepth
|
|
||||||
stackDepth = newStackDepth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const refreshStack = (): void => {
|
|
||||||
const l: number = trailingImageIndexes.length
|
|
||||||
if (stackDepth < lastStackDepth && l > stackDepth) {
|
|
||||||
const times: number = l - stackDepth
|
|
||||||
for (let i = 0; i < times; i++)
|
|
||||||
hideImage(images[trailingImageIndexes.shift() as number])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { imagesInfo, imagesLen } from './dataFetch'
|
|
||||||
import { createImgElement } from './utils'
|
|
||||||
|
|
||||||
// get components of overlay
|
|
||||||
export let overlayCursor: HTMLDivElement
|
|
||||||
export let cursorInnerContent: HTMLDivElement
|
|
||||||
export let imagesDivNodes: NodeListOf<HTMLImageElement>
|
|
||||||
|
|
||||||
const mainDiv = document.getElementById('main') as HTMLDivElement
|
|
||||||
|
|
||||||
const passDesktopElements = (): void => {
|
|
||||||
overlayCursor = document
|
|
||||||
.getElementsByClassName('overlay_cursor')
|
|
||||||
.item(0) as HTMLDivElement
|
|
||||||
cursorInnerContent = document
|
|
||||||
.getElementsByClassName('cursor_innerText')
|
|
||||||
.item(0) as HTMLDivElement
|
|
||||||
imagesDivNodes = document.getElementsByClassName('imagesDesktop')[0]
|
|
||||||
.childNodes as NodeListOf<HTMLImageElement>
|
|
||||||
}
|
|
||||||
|
|
||||||
const passMobileElements = (): void => {
|
|
||||||
imagesDivNodes = document.getElementsByClassName('imagesMobile')[0]
|
|
||||||
.childNodes as NodeListOf<HTMLImageElement>
|
|
||||||
}
|
|
||||||
|
|
||||||
const createCursorDiv = (): HTMLDivElement => {
|
|
||||||
const cursorDiv: HTMLDivElement = document.createElement('div')
|
|
||||||
cursorDiv.className = 'overlay_cursor'
|
|
||||||
const innerTextDiv: HTMLDivElement = document.createElement('div')
|
|
||||||
innerTextDiv.className = 'cursor_innerText'
|
|
||||||
cursorDiv.appendChild(innerTextDiv)
|
|
||||||
return cursorDiv
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createDesktopElements = (): void => {
|
|
||||||
mainDiv.appendChild(createCursorDiv())
|
|
||||||
const imagesDiv: HTMLDivElement = document.createElement('div')
|
|
||||||
imagesDiv.className = 'imagesDesktop'
|
|
||||||
for (let i = 0; i < imagesLen; i++) {
|
|
||||||
imagesDiv.appendChild(createImgElement(imagesInfo[i]))
|
|
||||||
}
|
|
||||||
mainDiv.appendChild(imagesDiv)
|
|
||||||
passDesktopElements()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createMobileElements = (): void => {
|
|
||||||
const imagesDiv: HTMLDivElement = document.createElement('div')
|
|
||||||
imagesDiv.className = 'imagesMobile'
|
|
||||||
for (let i = 0; i < imagesLen; i++) {
|
|
||||||
imagesDiv.appendChild(createImgElement(imagesInfo[i]))
|
|
||||||
}
|
|
||||||
mainDiv.appendChild(imagesDiv)
|
|
||||||
passMobileElements()
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { imagesInfo, imagesLen } from './dataFetch'
|
|
||||||
import { preloadImage, calcImageIndex } from './utils'
|
|
||||||
|
|
||||||
let lastIndex: number = 0
|
|
||||||
|
|
||||||
export const preloader = (index: number): void => {
|
|
||||||
if (lastIndex === index) {
|
|
||||||
for (let i: number = -2; i <= 1; i++)
|
|
||||||
preloadImage(imagesInfo[calcImageIndex(index + i, imagesLen)].url)
|
|
||||||
} else if (lastIndex > index) {
|
|
||||||
for (let i: number = 1; i <= 3; i++)
|
|
||||||
preloadImage(imagesInfo[calcImageIndex(index - i, imagesLen)].url)
|
|
||||||
} else {
|
|
||||||
for (let i: number = 1; i <= 3; i++)
|
|
||||||
preloadImage(imagesInfo[calcImageIndex(index + i, imagesLen)].url)
|
|
||||||
}
|
|
||||||
lastIndex = index
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { duper } from './utils'
|
|
||||||
|
|
||||||
// update index of displaying image
|
|
||||||
export const imgIndexSpanUpdate = (numOne: number, numTwo: number): void => {
|
|
||||||
// footer index number display module
|
|
||||||
const footerIndexDisp = document.getElementsByClassName('ftid')
|
|
||||||
const numOneString: string = duper(numOne)
|
|
||||||
const numTwoString: string = duper(numTwo)
|
|
||||||
for (let i: number = 0; i <= 7; i++) {
|
|
||||||
const footerIndex = footerIndexDisp[i] as HTMLSpanElement
|
|
||||||
if (i > 3) {
|
|
||||||
footerIndex.innerText = numTwoString[i - 4]
|
|
||||||
} else {
|
|
||||||
footerIndex.innerText = numOneString[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +1,13 @@
|
|||||||
import { createDesktopElements, createMobileElements } from './elemGen'
|
import { initResources } from './resources'
|
||||||
import { imgIndexSpanUpdate } from './indexDisp'
|
import { initState } from './state'
|
||||||
import { trackMouseInit } from './desktop'
|
import { initCustomCursor } from './customCursor'
|
||||||
import { thresholdCtlInit } from './thresholdCtl'
|
import { initNav } from './nav'
|
||||||
import { imagesLen } from './dataFetch'
|
import { initStage } from './stage'
|
||||||
import { vwRefreshInit } from './overlay'
|
import { initStageNav } from './stageNav'
|
||||||
import { preloader } from './imageCache'
|
|
||||||
import { getDeviceType } from './utils'
|
|
||||||
import { renderImages } from './mobile'
|
|
||||||
|
|
||||||
const desktopInit = (): void => {
|
initCustomCursor()
|
||||||
createDesktopElements()
|
const ijs = initResources()
|
||||||
preloader(0)
|
initState(ijs.length)
|
||||||
vwRefreshInit()
|
initStage(ijs)
|
||||||
imgIndexSpanUpdate(0, imagesLen)
|
initStageNav()
|
||||||
thresholdCtlInit()
|
initNav()
|
||||||
trackMouseInit()
|
|
||||||
}
|
|
||||||
|
|
||||||
const mobileInit = (): void => {
|
|
||||||
createMobileElements()
|
|
||||||
vwRefreshInit()
|
|
||||||
imgIndexSpanUpdate(0, imagesLen)
|
|
||||||
renderImages()
|
|
||||||
console.log('mobile')
|
|
||||||
}
|
|
||||||
|
|
||||||
getDeviceType().desktop ? mobileInit() : desktopInit()
|
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import { imagesDivNodes as images } from './elemGen'
|
|
||||||
import { imagesLen } from './dataFetch'
|
|
||||||
|
|
||||||
export const renderImages = (): void => {
|
|
||||||
images.forEach((img: HTMLImageElement, idx: number): void => {
|
|
||||||
const randomX: number = Math.floor(Math.random() * 35) + 2
|
|
||||||
let randomY: number
|
|
||||||
|
|
||||||
// random Y calculation
|
|
||||||
if (idx === 0) {
|
|
||||||
randomY = 68
|
|
||||||
} else if (idx === 1) {
|
|
||||||
randomY = 44
|
|
||||||
} else if (idx === imagesLen - 1) {
|
|
||||||
randomY = 100
|
|
||||||
} else {
|
|
||||||
randomY = Math.floor(Math.random() * 51) + 2
|
|
||||||
}
|
|
||||||
|
|
||||||
img.style.transform = `translate(${randomX}vw, -${randomY}%)`
|
|
||||||
img.style.marginTop = `${idx === 1 ? 70 : 0}vh`
|
|
||||||
img.style.visibility = 'visible'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
59
assets/ts/nav.ts
Normal file
59
assets/ts/nav.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { getState, incThreshold, decThreshold } from './state'
|
||||||
|
import { expand } from './utils'
|
||||||
|
|
||||||
|
// threshold div
|
||||||
|
const thresholdDiv = document
|
||||||
|
.getElementsByClassName('threshold')
|
||||||
|
.item(0) as HTMLDivElement
|
||||||
|
|
||||||
|
// threshold nums span
|
||||||
|
const thresholdDispNums = Array.from(
|
||||||
|
thresholdDiv.getElementsByClassName('num')
|
||||||
|
) as HTMLSpanElement[]
|
||||||
|
|
||||||
|
// threshold buttons
|
||||||
|
const decButton = thresholdDiv
|
||||||
|
.getElementsByClassName('dec')
|
||||||
|
.item(0) as HTMLButtonElement
|
||||||
|
const incButton = thresholdDiv
|
||||||
|
.getElementsByClassName('inc')
|
||||||
|
.item(0) as HTMLButtonElement
|
||||||
|
|
||||||
|
// index div
|
||||||
|
const indexDiv = document.getElementsByClassName('index').item(0) as HTMLDivElement
|
||||||
|
|
||||||
|
// index nums span
|
||||||
|
const indexDispNums = Array.from(
|
||||||
|
indexDiv.getElementsByClassName('num')
|
||||||
|
) as HTMLSpanElement[]
|
||||||
|
|
||||||
|
export function initNav() {
|
||||||
|
// init threshold text
|
||||||
|
updateThresholdText()
|
||||||
|
// init index text
|
||||||
|
updateIndexText()
|
||||||
|
// event listeners
|
||||||
|
decButton.addEventListener('click', () => decThreshold())
|
||||||
|
incButton.addEventListener('click', () => incThreshold())
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper
|
||||||
|
|
||||||
|
export function updateThresholdText(): void {
|
||||||
|
const thresholdValue: string = expand(getState().threshold)
|
||||||
|
thresholdDispNums.forEach((e: HTMLSpanElement, i: number) => {
|
||||||
|
e.innerText = thresholdValue[i]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateIndexText(): void {
|
||||||
|
const indexValue: string = expand(getState().index + 1)
|
||||||
|
const indexLength: string = expand(getState().length)
|
||||||
|
indexDispNums.forEach((e: HTMLSpanElement, i: number) => {
|
||||||
|
if (i < 4) {
|
||||||
|
e.innerText = indexValue[i]
|
||||||
|
} else {
|
||||||
|
e.innerText = indexLength[i - 4]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
import { delay, center, calcImageIndex, mouseToTransform, pushIndex } from './utils'
|
|
||||||
import {
|
|
||||||
handleOnMove,
|
|
||||||
globalIndex,
|
|
||||||
globalIndexDec,
|
|
||||||
globalIndexInc,
|
|
||||||
trailingImageIndexes,
|
|
||||||
transformCache,
|
|
||||||
emptyTransformCache,
|
|
||||||
emptyTrailingImageIndexes,
|
|
||||||
stackDepth,
|
|
||||||
addEnterOverlayEL
|
|
||||||
} from './desktop'
|
|
||||||
import { imagesLen } from './dataFetch'
|
|
||||||
import { imgIndexSpanUpdate } from './indexDisp'
|
|
||||||
import { overlayCursor, cursorInnerContent, imagesDivNodes as images } from './elemGen'
|
|
||||||
|
|
||||||
let oneThird: number = Math.round(window.innerWidth / 3)
|
|
||||||
|
|
||||||
// set cursor text
|
|
||||||
const setCursorText = (text: string): void => {
|
|
||||||
cursorInnerContent.innerText = text
|
|
||||||
}
|
|
||||||
|
|
||||||
// overlay cursor event handler
|
|
||||||
const setTextPos = (e: MouseEvent): void => {
|
|
||||||
overlayCursor.style.transform = mouseToTransform(e.clientX, e.clientY, false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// disable listeners
|
|
||||||
const disableListener = (): void => {
|
|
||||||
window.removeEventListener('mousemove', handleOverlayMouseMove)
|
|
||||||
overlayCursor.removeEventListener('click', handleOverlayClick)
|
|
||||||
}
|
|
||||||
|
|
||||||
// enable overlay
|
|
||||||
export const overlayEnable = (): void => {
|
|
||||||
// show the overlay components
|
|
||||||
overlayCursor.style.zIndex = '21'
|
|
||||||
// set overlay event listeners
|
|
||||||
setListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
// disable overlay
|
|
||||||
export const overlayDisable = (): void => {
|
|
||||||
// hide the overlay components
|
|
||||||
overlayCursor.style.zIndex = '-1'
|
|
||||||
// set overlay cursor text content to none
|
|
||||||
setCursorText('')
|
|
||||||
// disable overlay event listeners
|
|
||||||
disableListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle close click
|
|
||||||
async function handleCloseClick(): Promise<void> {
|
|
||||||
// disable overlay
|
|
||||||
overlayDisable()
|
|
||||||
// get length of indexes and empty indexes array
|
|
||||||
const indexesNum = trailingImageIndexes.length
|
|
||||||
emptyTrailingImageIndexes()
|
|
||||||
// prepare animation
|
|
||||||
for (let i: number = 0; i < indexesNum; i++) {
|
|
||||||
// get element from index and store the index
|
|
||||||
const index: number = calcImageIndex(globalIndex - i, imagesLen)
|
|
||||||
const e: HTMLImageElement = images[index]
|
|
||||||
trailingImageIndexes.unshift(index)
|
|
||||||
// set z index for the image element
|
|
||||||
e.style.zIndex = `${indexesNum - i - 1}`
|
|
||||||
// set different style for trailing and top image
|
|
||||||
if (i === 0) {
|
|
||||||
// set position
|
|
||||||
e.style.transform = transformCache[indexesNum - i - 1]
|
|
||||||
// set transition delay
|
|
||||||
e.style.transitionDelay = '0s, 0.7s'
|
|
||||||
// set status for css
|
|
||||||
e.dataset.status = 'resumeTop'
|
|
||||||
} else {
|
|
||||||
// set position
|
|
||||||
e.style.transform = transformCache[indexesNum - i - 1]
|
|
||||||
// set transition delay
|
|
||||||
e.style.transitionDelay = `${1.2 + 0.1 * i - 0.1}s`
|
|
||||||
// set status for css
|
|
||||||
e.dataset.status = 'resume'
|
|
||||||
}
|
|
||||||
// style process complete, show the image
|
|
||||||
e.style.visibility = 'visible'
|
|
||||||
}
|
|
||||||
// halt the function while animation is running
|
|
||||||
await delay(1200 + stackDepth * 100 + 100)
|
|
||||||
// add back enter overlay event listener to top image
|
|
||||||
addEnterOverlayEL(images[calcImageIndex(globalIndex, imagesLen)])
|
|
||||||
// clear unused status and transition delay
|
|
||||||
for (let i: number = 0; i < indexesNum; i++) {
|
|
||||||
const index: number = calcImageIndex(globalIndex - i, imagesLen)
|
|
||||||
images[index].dataset.status = 'null'
|
|
||||||
images[index].style.transitionDelay = ''
|
|
||||||
}
|
|
||||||
// Add back previous self increment of global index (by handleOnMove)
|
|
||||||
globalIndexInc()
|
|
||||||
// add back mousemove event listener
|
|
||||||
window.addEventListener('mousemove', handleOnMove, { passive: true })
|
|
||||||
// empty the position array cache
|
|
||||||
emptyTransformCache()
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSideClick = (CLD: boolean): void => {
|
|
||||||
// get last displayed image's index
|
|
||||||
const imgIndex: number = calcImageIndex(globalIndex, imagesLen)
|
|
||||||
// change global index and get current displayed image's index
|
|
||||||
CLD ? globalIndexInc() : globalIndexDec()
|
|
||||||
const currImgIndex: number = calcImageIndex(globalIndex, imagesLen)
|
|
||||||
// store current displayed image's index
|
|
||||||
CLD
|
|
||||||
? pushIndex(
|
|
||||||
currImgIndex,
|
|
||||||
trailingImageIndexes,
|
|
||||||
stackDepth,
|
|
||||||
images,
|
|
||||||
imagesLen,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
: pushIndex(
|
|
||||||
currImgIndex,
|
|
||||||
trailingImageIndexes,
|
|
||||||
stackDepth,
|
|
||||||
images,
|
|
||||||
imagesLen,
|
|
||||||
true,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
// hide last displayed image
|
|
||||||
images[imgIndex].style.visibility = 'hidden'
|
|
||||||
images[imgIndex].dataset.status = 'trail'
|
|
||||||
// process the image going to display
|
|
||||||
center(images[currImgIndex])
|
|
||||||
images[currImgIndex].dataset.status = 'overlay'
|
|
||||||
// process complete, show the image
|
|
||||||
images[currImgIndex].style.visibility = 'visible'
|
|
||||||
// change index display
|
|
||||||
imgIndexSpanUpdate(currImgIndex + 1, imagesLen)
|
|
||||||
}
|
|
||||||
|
|
||||||
// change text and position of overlay cursor
|
|
||||||
const handleOverlayMouseMove = (e: MouseEvent): void => {
|
|
||||||
// set text position
|
|
||||||
setTextPos(e)
|
|
||||||
// set text content
|
|
||||||
if (e.clientX < oneThird) {
|
|
||||||
setCursorText('PREV')
|
|
||||||
overlayCursor.dataset.status = 'PREV'
|
|
||||||
} else if (e.clientX < oneThird * 2) {
|
|
||||||
setCursorText('CLOSE')
|
|
||||||
overlayCursor.dataset.status = 'CLOSE'
|
|
||||||
} else {
|
|
||||||
setCursorText('NEXT')
|
|
||||||
overlayCursor.dataset.status = 'NEXT'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleOverlayClick = (): void => {
|
|
||||||
switch (overlayCursor.dataset.status) {
|
|
||||||
case 'PREV':
|
|
||||||
handleSideClick(false)
|
|
||||||
break
|
|
||||||
case 'CLOSE':
|
|
||||||
void handleCloseClick()
|
|
||||||
break
|
|
||||||
case 'NEXT':
|
|
||||||
handleSideClick(true)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// set event listener
|
|
||||||
const setListener = (): void => {
|
|
||||||
// add mouse move event listener (for overlay text cursor)
|
|
||||||
window.addEventListener('mousemove', handleOverlayMouseMove, { passive: true })
|
|
||||||
// add close/prev/next click event listener
|
|
||||||
overlayCursor.addEventListener('click', handleOverlayClick, { passive: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const vwRefreshInit = (): void => {
|
|
||||||
window.addEventListener(
|
|
||||||
'resize',
|
|
||||||
() => {
|
|
||||||
// refresh value of one third
|
|
||||||
oneThird = Math.round(window.innerWidth / 3)
|
|
||||||
// reset footer height
|
|
||||||
const r = document.querySelector(':root') as HTMLStyleElement
|
|
||||||
if (window.innerWidth > 768) {
|
|
||||||
r.style.setProperty('--footer-height', '38px')
|
|
||||||
} else {
|
|
||||||
r.style.setProperty('--footer-height', '31px')
|
|
||||||
}
|
|
||||||
// recenter image (only in overlay)
|
|
||||||
if (
|
|
||||||
images[calcImageIndex(globalIndex, imagesLen)].dataset.status === 'overlay'
|
|
||||||
)
|
|
||||||
center(images[calcImageIndex(globalIndex, imagesLen)])
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
21
assets/ts/resources.ts
Normal file
21
assets/ts/resources.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// data structure for images info
|
||||||
|
export interface ImageJSON {
|
||||||
|
index: number
|
||||||
|
url: string
|
||||||
|
imgH: number
|
||||||
|
imgW: number
|
||||||
|
pColor: string
|
||||||
|
sColor: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initResources(): ImageJSON[] {
|
||||||
|
const imagesJson = document.getElementById('imagesSource') as HTMLScriptElement
|
||||||
|
return JSON.parse(imagesJson.textContent as string).sort(
|
||||||
|
(a: ImageJSON, b: ImageJSON) => {
|
||||||
|
if (a.index < b.index) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
217
assets/ts/stage.ts
Normal file
217
assets/ts/stage.ts
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import { incIndex, getState } from './state'
|
||||||
|
import { gsap, Power3 } from 'gsap'
|
||||||
|
import { ImageJSON } from './resources'
|
||||||
|
|
||||||
|
export type HistoryItem = { i: number; x: number; y: number }
|
||||||
|
|
||||||
|
let imgs: HTMLImageElement[] = []
|
||||||
|
|
||||||
|
class CordHist {
|
||||||
|
private obj: HistoryItem[] = []
|
||||||
|
|
||||||
|
get(): HistoryItem[] {
|
||||||
|
return this.obj
|
||||||
|
}
|
||||||
|
|
||||||
|
set(e: HistoryItem[]): void {
|
||||||
|
this.obj = e
|
||||||
|
setPositions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IsOpen {
|
||||||
|
private obj = false
|
||||||
|
|
||||||
|
get(): boolean {
|
||||||
|
return this.obj
|
||||||
|
}
|
||||||
|
|
||||||
|
set(e: boolean): void {
|
||||||
|
this.obj = e
|
||||||
|
activeCallbacks.forEach((callback) => callback(getActive()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let last = { x: 0, y: 0 }
|
||||||
|
export let cordHist = new CordHist()
|
||||||
|
export let isOpen = new IsOpen()
|
||||||
|
let isAnimating = false
|
||||||
|
let activeCallbacks: ((active: boolean) => void)[] = []
|
||||||
|
|
||||||
|
// 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]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getIsAnimating(): boolean {
|
||||||
|
return isAnimating
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getActive(): boolean {
|
||||||
|
return isOpen.get() && !getIsAnimating()
|
||||||
|
}
|
||||||
|
|
||||||
|
// setter
|
||||||
|
|
||||||
|
export function addActiveCallback(callback: (active: boolean) => void): void {
|
||||||
|
activeCallbacks.push(callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setIsAnimating(e: boolean): void {
|
||||||
|
isAnimating = e
|
||||||
|
activeCallbacks.forEach((callback) => callback(getActive()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// main functions
|
||||||
|
|
||||||
|
// on mouse
|
||||||
|
function onMouse(e: MouseEvent): void {
|
||||||
|
if (isOpen.get() || getIsAnimating()) 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 (getIsAnimating()) return
|
||||||
|
|
||||||
|
isOpen.set(true)
|
||||||
|
setIsAnimating(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(() => {
|
||||||
|
setIsAnimating(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// close navigation and back to stage
|
||||||
|
export function minimizeImage(): void {
|
||||||
|
if (isAnimating) return
|
||||||
|
|
||||||
|
isOpen.set(false)
|
||||||
|
setIsAnimating(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(() => {
|
||||||
|
setIsAnimating(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// init
|
||||||
|
|
||||||
|
export function initStage(ijs: ImageJSON[]): void {
|
||||||
|
createStage(ijs)
|
||||||
|
const stage = document.getElementsByClassName('stage').item(0) as HTMLDivElement
|
||||||
|
imgs = Array.from(stage.getElementsByTagName('img'))
|
||||||
|
stage.addEventListener('click', () => expandImage())
|
||||||
|
stage.addEventListener('keydown', () => expandImage())
|
||||||
|
window.addEventListener('mousemove', onMouse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
93
assets/ts/stageNav.ts
Normal file
93
assets/ts/stageNav.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import { setCustomCursor } from './customCursor'
|
||||||
|
import { decIndex, incIndex, getState } from './state'
|
||||||
|
import { increment, decrement } from './utils'
|
||||||
|
import {
|
||||||
|
cordHist,
|
||||||
|
isOpen,
|
||||||
|
getIsAnimating,
|
||||||
|
minimizeImage,
|
||||||
|
addActiveCallback
|
||||||
|
} 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() || getIsAnimating()) 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)
|
||||||
|
}
|
||||||
|
addActiveCallback((active) => {
|
||||||
|
if (active) {
|
||||||
|
navOverlay.classList.add('active')
|
||||||
|
} else {
|
||||||
|
navOverlay.classList.remove('active')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
document.getElementById('main')!.append(navOverlay)
|
||||||
|
window.addEventListener('keydown', handleKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// hepler
|
||||||
|
|
||||||
|
function nextImage() {
|
||||||
|
if (getIsAnimating()) return
|
||||||
|
cordHist.set(
|
||||||
|
cordHist.get().map((item) => {
|
||||||
|
return { ...item, i: increment(item.i, getState().length) }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
incIndex()
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevImage() {
|
||||||
|
if (getIsAnimating()) return
|
||||||
|
cordHist.set(
|
||||||
|
cordHist.get().map((item) => {
|
||||||
|
return { ...item, i: decrement(item.i, getState().length) }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
decIndex()
|
||||||
|
}
|
||||||
68
assets/ts/state.ts
Normal file
68
assets/ts/state.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { increment, decrement } from './utils'
|
||||||
|
import { updateIndexText, updateThresholdText } from './nav'
|
||||||
|
|
||||||
|
const thresholds = [
|
||||||
|
{ threshold: 20, trailLength: 20 },
|
||||||
|
{ threshold: 40, trailLength: 10 },
|
||||||
|
{ threshold: 80, trailLength: 5 },
|
||||||
|
{ threshold: 140, trailLength: 5 },
|
||||||
|
{ threshold: 200, trailLength: 5 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
index: -1,
|
||||||
|
length: 0,
|
||||||
|
threshold: thresholds[2].threshold,
|
||||||
|
trailLength: thresholds[2].trailLength
|
||||||
|
}
|
||||||
|
|
||||||
|
export type State = typeof defaultState
|
||||||
|
|
||||||
|
let state = defaultState
|
||||||
|
|
||||||
|
export function getState(): State {
|
||||||
|
// return a copy of state
|
||||||
|
return Object.create(
|
||||||
|
Object.getPrototypeOf(state),
|
||||||
|
Object.getOwnPropertyDescriptors(state)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initState(length: number): void {
|
||||||
|
state.length = length
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setIndex(index: number): void {
|
||||||
|
state.index = index
|
||||||
|
updateIndexText()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function incIndex(): void {
|
||||||
|
state.index = increment(state.index, state.length)
|
||||||
|
updateIndexText()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decIndex(): void {
|
||||||
|
state.index = decrement(state.index, state.length)
|
||||||
|
updateIndexText()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function incThreshold(): void {
|
||||||
|
state = updateThreshold(state, 1)
|
||||||
|
updateThresholdText()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decThreshold(): void {
|
||||||
|
state = updateThreshold(state, -1)
|
||||||
|
updateThresholdText()
|
||||||
|
}
|
||||||
|
|
||||||
|
// helper
|
||||||
|
|
||||||
|
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
|
||||||
|
return { ...state, ...newItems }
|
||||||
|
}
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import { duper } from './utils'
|
|
||||||
import { setStackDepth } from './desktop'
|
|
||||||
|
|
||||||
// get threshold display element
|
|
||||||
const thresholdDisp = document.getElementsByClassName('thid').item(0) as HTMLSpanElement
|
|
||||||
|
|
||||||
// threshold data
|
|
||||||
const threshold: number[] = [0, 40, 80, 120, 160, 200]
|
|
||||||
export const thresholdSensitivityArray: number[] = [100, 40, 18, 14, 9, 5]
|
|
||||||
export let thresholdIndex: number = 2
|
|
||||||
|
|
||||||
export const stackDepthArray: number[] = [15, 8, 5, 5, 5, 5]
|
|
||||||
|
|
||||||
// update inner text of threshold display element
|
|
||||||
const thresholdUpdate = (): void => {
|
|
||||||
thresholdDisp.innerText = duper(threshold[thresholdIndex])
|
|
||||||
}
|
|
||||||
|
|
||||||
const stackDepthUpdate = (): void => {
|
|
||||||
setStackDepth(stackDepthArray[thresholdIndex])
|
|
||||||
}
|
|
||||||
|
|
||||||
// threshold control initialization
|
|
||||||
export const thresholdCtlInit = (): void => {
|
|
||||||
thresholdUpdate()
|
|
||||||
const dec = document.getElementById('thresholdDec') as HTMLButtonElement
|
|
||||||
dec.addEventListener(
|
|
||||||
'click',
|
|
||||||
function () {
|
|
||||||
if (thresholdIndex > 0) {
|
|
||||||
thresholdIndex--
|
|
||||||
thresholdUpdate()
|
|
||||||
stackDepthUpdate()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
const inc = document.getElementById('thresholdInc') as HTMLButtonElement
|
|
||||||
inc.addEventListener(
|
|
||||||
'click',
|
|
||||||
function () {
|
|
||||||
if (thresholdIndex < 5) {
|
|
||||||
thresholdIndex++
|
|
||||||
thresholdUpdate()
|
|
||||||
stackDepthUpdate()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ passive: true }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,145 +1,15 @@
|
|||||||
export interface ImageData {
|
export function increment(num: number, length: number): number {
|
||||||
index: string
|
return (num + 1) % length
|
||||||
url: string
|
|
||||||
imgH: string
|
|
||||||
imgW: string
|
|
||||||
pColor: string
|
|
||||||
sColor: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface position {
|
export function decrement(num: number, length: number): number {
|
||||||
x: number
|
return (num + length - 1) % length
|
||||||
y: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface deviceType {
|
export function expand(num: number): string {
|
||||||
mobile: boolean
|
|
||||||
tablet: boolean
|
|
||||||
desktop: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
// 0 to 0001, 25 to 0025
|
|
||||||
export const duper = (num: number): string => {
|
|
||||||
return ('0000' + num.toString()).slice(-4)
|
return ('0000' + num.toString()).slice(-4)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mouseToTransform = (
|
export function isMobile(): boolean {
|
||||||
x: number,
|
return window.matchMedia('(hover: none)').matches
|
||||||
y: number,
|
|
||||||
centerCorrection: boolean = true,
|
|
||||||
accelerate: boolean = false
|
|
||||||
): string => {
|
|
||||||
return `translate${accelerate ? '3d' : ''}(${
|
|
||||||
centerCorrection ? `calc(${x}px - 50%)` : `${x}px`
|
|
||||||
}, ${centerCorrection ? `calc(${y}px - 50%)` : `${y}px`}${accelerate ? ', 0' : ''})`
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
|
||||||
export function delay(ms: number): Promise<void> {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove all event listeners from a node
|
|
||||||
export const removeAllEventListeners = (e: Node): Node => {
|
|
||||||
return e.cloneNode(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// center top div
|
|
||||||
export const center = (e: HTMLElement): void => {
|
|
||||||
const x: number = window.innerWidth / 2
|
|
||||||
let y: number
|
|
||||||
if (window.innerWidth > 768) {
|
|
||||||
y = (window.innerHeight - 38) / 2
|
|
||||||
} else {
|
|
||||||
y = (window.innerHeight - 31) / 2 + 31
|
|
||||||
}
|
|
||||||
e.style.transform = mouseToTransform(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createImgElement = (input: ImageData): HTMLImageElement => {
|
|
||||||
const img = document.createElement('img')
|
|
||||||
img.setAttribute('src', input.url)
|
|
||||||
img.setAttribute('alt', '')
|
|
||||||
img.setAttribute('height', input.imgH)
|
|
||||||
img.setAttribute('width', input.imgW)
|
|
||||||
img.style.visibility = 'hidden'
|
|
||||||
img.dataset.status = 'trail'
|
|
||||||
// img.style.backgroundImage = `linear-gradient(15deg, ${input.pColor}, ${input.sColor})`
|
|
||||||
return img
|
|
||||||
}
|
|
||||||
|
|
||||||
export const calcImageIndex = (index: number, imgCounts: number): number => {
|
|
||||||
if (index >= 0) {
|
|
||||||
return index % imgCounts
|
|
||||||
} else {
|
|
||||||
return (imgCounts + (index % imgCounts)) % imgCounts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const preloadImage = (src: string): void => {
|
|
||||||
const cache = new Image()
|
|
||||||
cache.src = src
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getDeviceType = (): deviceType => {
|
|
||||||
const ua: string = navigator.userAgent
|
|
||||||
const result: deviceType = { mobile: false, tablet: false, desktop: false }
|
|
||||||
if (/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i.test(ua)) {
|
|
||||||
result.mobile = true
|
|
||||||
result.tablet = true
|
|
||||||
} else if (
|
|
||||||
/Mobile|iP(hone|od)|Android|BlackBerry|IEMobile|Kindle|Silk-Accelerated|(hpw|web)OS|Opera M(obi|ini)/.test(
|
|
||||||
ua
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
result.mobile = true
|
|
||||||
} else result.desktop = true
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export const hideImage = (e: HTMLImageElement): void => {
|
|
||||||
e.style.visibility = 'hidden'
|
|
||||||
e.dataset.status = 'trail'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const pushIndex = (
|
|
||||||
index: number,
|
|
||||||
indexesArray: number[],
|
|
||||||
stackDepth: number,
|
|
||||||
imagesArray: NodeListOf<HTMLImageElement>,
|
|
||||||
imagesArrayLen: number,
|
|
||||||
invertFlag: boolean = false,
|
|
||||||
autoHideFlag: boolean = true
|
|
||||||
): number => {
|
|
||||||
let indexesNum: number = indexesArray.length
|
|
||||||
// create variable overflow to store the tail index
|
|
||||||
let overflow: number
|
|
||||||
if (!invertFlag) {
|
|
||||||
// if stack is full, push the tail index out and hide the image
|
|
||||||
if (indexesNum === stackDepth) {
|
|
||||||
// insert
|
|
||||||
indexesArray.push(index)
|
|
||||||
// pop out
|
|
||||||
overflow = indexesArray.shift() as number
|
|
||||||
// auto hide tail image
|
|
||||||
if (autoHideFlag) hideImage(imagesArray[overflow])
|
|
||||||
} else {
|
|
||||||
indexesArray.push(index)
|
|
||||||
indexesNum += 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if stack is full, push the tail index out and hide the image
|
|
||||||
if (indexesNum === stackDepth) {
|
|
||||||
// insert
|
|
||||||
indexesArray.unshift(calcImageIndex(index - stackDepth + 1, imagesArrayLen))
|
|
||||||
// pop out
|
|
||||||
overflow = indexesArray.pop() as number
|
|
||||||
// auto hide tail image
|
|
||||||
if (autoHideFlag) hideImage(imagesArray[overflow])
|
|
||||||
} else {
|
|
||||||
indexesArray.unshift(calcImageIndex(index - indexesNum + 1, imagesArrayLen))
|
|
||||||
indexesNum += 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return indexesNum
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user