feat: add loading indicator for desktop and mobile (#244)

* feat: add new CSS rule for hiding elements

- Add a new CSS rule for the class "hide" with a display property set to "none".

* feat: refactor image loading and navigation logic on desktop

- Add a line of code in `assets/ts/desktop/stage.ts` to reset the `src` of `elc` and add a class `hide` to it
- Add a line of code in `assets/ts/desktop/stage.ts` to call the `loader` function with `elc` as an argument
- Add a function `loader` in `assets/ts/desktop/stage.ts` to handle image loading and error events
- Modify the `initStageNav` function in `assets/ts/desktop/stageNav.ts` to watch the `isLoading` state and set custom cursor accordingly
- Modify the `initStageNav` function in `assets/ts/desktop/stageNav.ts` to handle close click events by calling the `handleClick` function and setting `isLoading` state to false
- Modify the `initStageNav` function in `assets/ts/desktop/stageNav.ts` to handle previous/next click events by calling the `handleClick` function only if `isLoading` is false
- Modify the `initStageNav` function in `assets/ts/desktop/stageNav.ts` to handle previous/next hover events by setting `loadedText` and updating custom cursor depending on `isLoading` state

* feat: refactor createGallery function and enhance loading functionality on mobile

- Add a loading text element to the gallery.scss file
- Add a loading indicator to the createGallery function in gallery.ts
- Modify the createGallery function in gallery.ts to hide the loading text element on image load
- Move the image element append logic to the parent container in the createGallery function in gallery.ts

* feat: update translations and add new loading translation in i18n files

* chore: remove css source map

* chore: modify build command to ignore css source map

* refactor: remove unnecessary style

* fix: fix desktop cursor text transition bug
This commit is contained in:
Spedon
2024-01-20 23:34:13 +08:00
committed by GitHub
parent 8c6b38bb49
commit 9fa1b718b8
20 changed files with 200 additions and 65 deletions

View File

@@ -17,3 +17,7 @@ a,
button {
cursor: pointer;
}
.hide {
display: none;
}

View File

@@ -27,6 +27,18 @@
height: 100%;
object-fit: contain;
}
.loadingText {
position: absolute;
top: 50%;
left: 50%;
transform: translate3d(-50%, -50%, 0);
}
.slideContainer {
position: relative;
display: inline-block;
}
}
}

View File

@@ -25,6 +25,7 @@ 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)
export const isLoading = new Watchable<boolean>(false)
let _gsap: typeof gsap
let _Power3: typeof Power3
@@ -114,9 +115,13 @@ function setPositions(): void {
if (isOpen.get()) {
lores(getElTrail())
hires([getElCurrent(), getElPrev(), getElNext()])
const elc = getElCurrent()
elc.src = '' // reset src to ensure we only display hires images
elc.classList.add('hide')
hires([elc, getElPrev(), getElNext()])
_gsap.set(imgs, { opacity: 0 })
_gsap.set(getElCurrent(), { opacity: 1, x: 0, y: 0, scale: 1 })
_gsap.set(elc, { opacity: 1, x: 0, y: 0, scale: 1 })
loader(elc)
}
}
@@ -127,7 +132,11 @@ function expandImage(): void {
isOpen.set(true)
isAnimating.set(true)
hires([getElCurrent(), getElPrev(), getElNext()])
const elc = getElCurrent()
// don't clear src here because we want a better transition
hires([elc, getElPrev(), getElNext()])
loader(elc)
const tl = _gsap.timeline()
// move down and hide trail inactive
@@ -292,3 +301,27 @@ function lores(imgs: HTMLImageElement[]): void {
img.width = parseInt(img.dataset.loImgW as string)
})
}
function loader(e: HTMLImageElement): void {
if (!e.complete) {
isLoading.set(true)
e.addEventListener(
'load',
() => {
isLoading.set(false)
e.classList.remove('hide')
},
{ once: true, passive: true }
)
e.addEventListener(
'error',
() => {
isLoading.set(false)
},
{ once: true, passive: true }
)
} else {
e.classList.remove('hide')
isLoading.set(false)
}
}

View File

@@ -3,7 +3,14 @@ import { decIndex, incIndex, state } from '../state'
import { decrement, increment } from '../utils'
import { setCustomCursor } from './customCursor'
import { active, cordHist, isAnimating, isOpen, minimizeImage } from './stage'
import {
active,
cordHist,
isAnimating,
isLoading,
isOpen,
minimizeImage
} from './stage'
/**
* types
@@ -21,6 +28,8 @@ const navItems = [
mainDiv.getAttribute('closeText') as string,
mainDiv.getAttribute('nextText') as string
] as const
const loadingText = (mainDiv.getAttribute('loadingText') as string) + '...'
let loadedText = ''
/**
* main functions
@@ -56,39 +65,80 @@ function handleKey(e: KeyboardEvent): void {
*/
export function initStageNav(): void {
// isLoading
isLoading.addWatcher((o) => {
if (o) setCustomCursor(loadingText)
else setCustomCursor(loadedText)
})
// navOverlay
const navOverlay = document.createElement('div')
navOverlay.className = 'navOverlay'
for (const navItem of navItems) {
for (const [index, navItem] of navItems.entries()) {
const overlay = document.createElement('div')
overlay.className = 'overlay'
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 }
)
const isClose = index === 1
// close
if (isClose) {
overlay.addEventListener(
'click',
() => {
handleCloseClick(navItem)
},
{ passive: true }
)
overlay.addEventListener(
'keydown',
() => {
handleCloseClick(navItem)
},
{ passive: true }
)
overlay.addEventListener(
'mouseover',
() => {
handleCloseHover(navItem)
},
{ passive: true }
)
overlay.addEventListener(
'focus',
() => {
handleCloseHover(navItem)
},
{ passive: true }
)
}
// prev and next
else {
overlay.addEventListener(
'click',
() => {
handlePNClick(navItem)
},
{ passive: true }
)
overlay.addEventListener(
'keydown',
() => {
handlePNClick(navItem)
},
{ passive: true }
)
overlay.addEventListener(
'mouseover',
() => {
handlePNHover(navItem)
},
{ passive: true }
)
overlay.addEventListener(
'focus',
() => {
handlePNHover(navItem)
},
{ passive: true }
)
}
navOverlay.append(overlay)
}
active.addWatcher(() => {
@@ -127,3 +177,23 @@ function prevImage(): void {
decIndex()
}
function handleCloseClick(navItem: NavItem): void {
handleClick(navItem)
isLoading.set(false)
}
function handleCloseHover(navItem: NavItem): void {
loadedText = navItem
setCustomCursor(navItem)
}
function handlePNClick(navItem: NavItem): void {
if (!isLoading.get()) handleClick(navItem)
}
function handlePNHover(navItem: NavItem): void {
loadedText = navItem
if (isLoading.get()) setCustomCursor(loadingText)
else setCustomCursor(navItem)
}

View File

@@ -196,14 +196,34 @@ function createGallery(ijs: ImageJSON[]): void {
for (const ij of ijs) {
const _swiperSlide = document.createElement('div')
_swiperSlide.className = 'swiper-slide'
// loading indicator
const l = document.createElement('div')
l.className = 'loadingText'
l.innerText =
(document.getElementById('main')?.getAttribute('loadingText') as string) + '...'
// img
const e = document.createElement('img')
e.dataset.src = ij.hiUrl
e.height = ij.hiImgH
e.width = ij.hiImgW
e.alt = ij.alt
e.classList.add('hide')
// load event
e.addEventListener(
'load',
() => {
e.classList.remove('hide')
l.classList.add('hide')
},
{ once: true, passive: true }
)
// parent container
const p = document.createElement('div')
p.className = 'slideContainer'
// append
_swiperSlide.append(e)
p.append(e)
p.append(l)
_swiperSlide.append(p)
_swiperWrapper.append(_swiperSlide)
}
// swiper node