mirror of
https://github.com/Sped0n/bridget.git
synced 2026-04-14 10:09:31 -07:00
feat(customCursor.scss): add custom cursor styles to improve user experience
refactor(customCursor.ts): remove unused code and optimize custom cursor functionality refactor(stageNav.ts): remove unused code and optimize stage navigation functionality refactor(stage.ts): remove unused code and optimize stage functionality
This commit is contained in:
21
assets/scss/_partial/_customCursor.scss
Normal file
21
assets/scss/_partial/_customCursor.scss
Normal file
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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<HistoryItem[]>([])
|
||||
export const isOpen = new Watchable<boolean>(false)
|
||||
export const isAnimating = new Watchable<boolean>(false)
|
||||
export const active = new Watchable<boolean>(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)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
Reference in New Issue
Block a user