refactor: reduce amount of createEffect and improve imports (#284)

* refactor: update import syntax

* feat: add GalleryImage component for simplicity

* refactor: replace createEffect in GalleryNav with createMemo

* refactor: refactor Gallery component logic and improve imports
This commit is contained in:
Spedon
2024-02-23 15:59:32 +08:00
committed by GitHub
parent 875113448b
commit e081e139fc
10 changed files with 115 additions and 100 deletions

View File

@@ -1,6 +1,6 @@
import { createSignal, onCleanup, onMount, type Accessor, type JSX } from 'solid-js' import { createSignal, onCleanup, onMount, type Accessor, type JSX } from 'solid-js'
export function CustomCursor(props: { export default function CustomCursor(props: {
children?: JSX.Element children?: JSX.Element
active: Accessor<boolean> active: Accessor<boolean>
cursorText: Accessor<string> cursorText: Accessor<string>

View File

@@ -4,10 +4,10 @@ import { Show, createMemo, createSignal, type JSX } from 'solid-js'
import type { ImageJSON } from '../resources' import type { ImageJSON } from '../resources'
import type { Vector } from '../utils' import type { Vector } from '../utils'
import { CustomCursor } from './customCursor' import CustomCursor from './customCursor'
import { Nav } from './nav' import Nav from './nav'
import { Stage } from './stage' import Stage from './stage'
import { StageNav } from './stageNav' import StageNav from './stageNav'
/** /**
* interfaces and types * interfaces and types

View File

@@ -51,7 +51,7 @@ function updateIndexText(indexValue: string, indexLength: string): void {
* Nav component * Nav component
*/ */
export function Nav(): null { export default function Nav(): null {
const [state, { incThreshold, decThreshold }] = useState() const [state, { incThreshold, decThreshold }] = useState()
createEffect(() => { createEffect(() => {

View File

@@ -90,7 +90,7 @@ function onMutation<T extends HTMLElement>(
* Stage component * Stage component
*/ */
export function Stage(props: { export default function Stage(props: {
ijs: ImageJSON[] ijs: ImageJSON[]
setIsLoading: Setter<boolean> setIsLoading: Setter<boolean>
isOpen: Accessor<boolean> isOpen: Accessor<boolean>

View File

@@ -5,7 +5,7 @@ import { decrement, increment, type Vector } from '../utils'
import type { HistoryItem } from './layout' import type { HistoryItem } from './layout'
export function StageNav(props: { export default function StageNav(props: {
children?: JSX.Element children?: JSX.Element
prevText: string prevText: string
closeText: string closeText: string

View File

@@ -31,7 +31,7 @@ function onIntersection<T extends HTMLElement>(
}).observe(element) }).observe(element)
} }
export function Collection(props: { export default function Collection(props: {
children?: JSX.Element children?: JSX.Element
ijs: ImageJSON[] ijs: ImageJSON[]
isAnimating: Accessor<boolean> isAnimating: Accessor<boolean>

View File

@@ -1,13 +1,16 @@
import { type gsap } from 'gsap' import { type gsap } from 'gsap'
import { import {
createEffect, createEffect,
createSignal,
For, For,
on, on,
onMount, onMount,
Show,
type Accessor, type Accessor,
type JSX, type JSX,
type Setter type Setter
} from 'solid-js' } from 'solid-js'
import { createStore } from 'solid-js/store'
import { type Swiper } from 'swiper' import { type Swiper } from 'swiper'
import invariant from 'tiny-invariant' import invariant from 'tiny-invariant'
@@ -15,8 +18,8 @@ import { type ImageJSON } from '../resources'
import { useState } from '../state' import { useState } from '../state'
import { loadGsap, type Vector } from '../utils' import { loadGsap, type Vector } from '../utils'
import { capitalizeFirstLetter, GalleryNav } from './galleryNav' import GalleryImage from './galleryImage'
import type { MobileImage } from './layout' import GalleryNav, { capitalizeFirstLetter } from './galleryNav'
function removeDuplicates<T>(arr: T[]): T[] { function removeDuplicates<T>(arr: T[]): T[] {
if (arr.length < 2) return arr // optimization if (arr.length < 2) return arr // optimization
@@ -28,7 +31,7 @@ async function loadSwiper(): Promise<typeof Swiper> {
return s.Swiper return s.Swiper
} }
export function Gallery(props: { export default function Gallery(props: {
children?: JSX.Element children?: JSX.Element
ijs: ImageJSON[] ijs: ImageJSON[]
closeText: string closeText: string
@@ -43,10 +46,6 @@ export function Gallery(props: {
let _gsap: typeof gsap let _gsap: typeof gsap
let _swiper: Swiper let _swiper: Swiper
// eslint-disable-next-line solid/reactivity
const imgs: MobileImage[] = Array<MobileImage>(props.ijs.length)
// eslint-disable-next-line solid/reactivity
const loadingDivs: HTMLDivElement[] = Array<HTMLDivElement>(props.ijs.length)
let curtain: HTMLDivElement | undefined let curtain: HTMLDivElement | undefined
let gallery: HTMLDivElement | undefined let gallery: HTMLDivElement | undefined
let galleryInner: HTMLDivElement | undefined let galleryInner: HTMLDivElement | undefined
@@ -56,16 +55,18 @@ export function Gallery(props: {
// states // states
let lastIndex = -1 let lastIndex = -1
let libLoaded = false
let mounted = false let mounted = false
let navigateVector: Vector = 'none' let navigateVector: Vector = 'none'
const [state, { setIndex }] = useState() const [state, { setIndex }] = useState()
const [libLoaded, setLibLoaded] = createSignal(false)
// eslint-disable-next-line solid/reactivity
const [loads, setLoads] = createStore(Array<boolean>(props.ijs.length).fill(false))
// helper functions // helper functions
const slideUp: () => void = () => { const slideUp: () => void = () => {
// isAnimating is prechecked in isOpen effect // isAnimating is prechecked in isOpen effect
if (!libLoaded || !mounted) return if (!libLoaded() || !mounted) return
props.setIsAnimating(true) props.setIsAnimating(true)
invariant(curtain, 'curtain is not defined') invariant(curtain, 'curtain is not defined')
@@ -133,11 +134,7 @@ export function Gallery(props: {
activeImagesIndex = [currentIndex, nextIndex, prevIndex] activeImagesIndex = [currentIndex, nextIndex, prevIndex]
break break
} }
removeDuplicates(activeImagesIndex).forEach((i) => { setLoads(removeDuplicates(activeImagesIndex), true)
const e = imgs[i]
if (e.src === e.dataset.src) return // already loaded
e.src = e.dataset.src
})
} }
const changeSlide: (slide: number) => void = (slide) => { const changeSlide: (slide: number) => void = (slide) => {
@@ -149,22 +146,6 @@ export function Gallery(props: {
// effects // effects
onMount(() => { onMount(() => {
imgs.forEach((img, i) => {
const loadingDiv = loadingDivs[i]
img.addEventListener(
'load',
() => {
if (state().index !== parseInt(img.dataset.index)) {
_gsap.set(img, { opacity: 1 })
_gsap.set(loadingDiv, { opacity: 0 })
} else {
_gsap.to(img, { opacity: 1, delay: 0.5, duration: 0.5, ease: 'power3.out' })
_gsap.to(loadingDiv, { opacity: 0, duration: 0.5, ease: 'power3.in' })
}
},
{ once: true, passive: true }
)
})
window.addEventListener( window.addEventListener(
'touchstart', 'touchstart',
() => { () => {
@@ -186,7 +167,7 @@ export function Gallery(props: {
.catch((e) => { .catch((e) => {
console.log(e) console.log(e)
}) })
libLoaded = true setLibLoaded(true)
}, },
{ once: true, passive: true } { once: true, passive: true }
) )
@@ -234,26 +215,19 @@ export function Gallery(props: {
<div ref={gallery} class="gallery"> <div ref={gallery} class="gallery">
<div ref={galleryInner} class="galleryInner"> <div ref={galleryInner} class="galleryInner">
<div class="swiper-wrapper"> <div class="swiper-wrapper">
<For each={props.ijs}> <Show when={libLoaded()}>
{(ij, i) => ( <For each={props.ijs}>
<div class="swiper-slide"> {(ij, i) => (
<div class="slideContainer"> <div class="swiper-slide">
<img <GalleryImage
ref={imgs[i()]} load={loads[i()]}
height={ij.hiImgH} ij={ij}
width={ij.hiImgW} loadingText={_loadingText}
data-src={ij.hiUrl}
data-index={ij.index}
alt={ij.alt}
style={{ opacity: 0 }}
/> />
<div ref={loadingDivs[i()]} class="loadingText">
{_loadingText}
</div>
</div> </div>
</div> )}
)} </For>
</For> </Show>
</div> </div>
</div> </div>
<GalleryNav <GalleryNav

View File

@@ -0,0 +1,69 @@
import { onMount, type JSX } from 'solid-js'
import invariant from 'tiny-invariant'
import type { ImageJSON } from '../resources'
import { useState } from '../state'
import { loadGsap } from '../utils'
export default function GalleryImage(props: {
children?: JSX.Element
load: boolean
ij: ImageJSON
loadingText: string
}): JSX.Element {
let img: HTMLImageElement | undefined
let loadingDiv: HTMLDivElement | undefined
let _gsap: typeof gsap
const [state] = useState()
onMount(() => {
loadGsap()
.then((g) => {
_gsap = g
})
.catch((e) => {
console.log(e)
})
img?.addEventListener(
'load',
() => {
invariant(img, 'ref must be defined')
invariant(loadingDiv, 'loadingDiv must be defined')
if (state().index !== props.ij.index) {
_gsap.set(img, { opacity: 1 })
_gsap.set(loadingDiv, { opacity: 0 })
} else {
_gsap.to(img, {
opacity: 1,
delay: 0.5,
duration: 0.5,
ease: 'power3.out'
})
_gsap.to(loadingDiv, { opacity: 0, duration: 0.5, ease: 'power3.in' })
}
},
{ once: true, passive: true }
)
})
return (
<>
<div class="slideContainer">
<img
ref={img}
{...(props.load && { src: props.ij.hiUrl })}
height={props.ij.hiImgH}
width={props.ij.hiImgW}
data-src={props.ij.hiUrl}
alt={props.ij.alt}
style={{ opacity: 0 }}
/>
<div ref={loadingDiv} class="loadingText">
{props.loadingText}
</div>
</div>
</>
)
}

View File

@@ -1,4 +1,4 @@
import { createEffect, on, type Accessor, type JSX, type Setter } from 'solid-js' import { createMemo, type Accessor, type JSX, type Setter } from 'solid-js'
import { useState } from '../state' import { useState } from '../state'
import { expand } from '../utils' import { expand } from '../utils'
@@ -7,63 +7,35 @@ export function capitalizeFirstLetter(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1) return str.charAt(0).toUpperCase() + str.slice(1)
} }
export function GalleryNav(props: { export default function GalleryNav(props: {
children?: JSX.Element children?: JSX.Element
closeText: string closeText: string
isAnimating: Accessor<boolean> isAnimating: Accessor<boolean>
setIsOpen: Setter<boolean> setIsOpen: Setter<boolean>
}): JSX.Element { }): JSX.Element {
// variables
const indexNums: HTMLSpanElement[] = Array<HTMLSpanElement>(8)
// states // states
const [state] = useState() const [state] = useState()
const stateLength = state().length const indexValue = createMemo(() => expand(state().index + 1))
const indexLength = createMemo(() => expand(state().length))
// helper functions
const updateIndexText: () => void = () => {
const indexValue: string = expand(state().index + 1)
const indexLength: string = expand(stateLength)
indexNums.forEach((e: HTMLSpanElement, i: number) => {
if (i < 4) {
e.innerText = indexValue[i]
} else {
e.innerText = indexLength[i - 4]
}
})
}
const onClick: () => void = () => { const onClick: () => void = () => {
if (props.isAnimating()) return if (props.isAnimating()) return
props.setIsOpen(false) props.setIsOpen(false)
} }
// effects
createEffect(
on(
() => {
state()
},
() => {
updateIndexText()
},
{ defer: true }
)
)
return ( return (
<> <>
<div class="nav"> <div class="nav">
<div> <div>
<span ref={indexNums[0]} class="num" /> <span class="num">{indexValue()[0]}</span>
<span ref={indexNums[1]} class="num" /> <span class="num">{indexValue()[1]}</span>
<span ref={indexNums[2]} class="num" /> <span class="num">{indexValue()[2]}</span>
<span ref={indexNums[3]} class="num" /> <span class="num">{indexValue()[3]}</span>
<span>/</span> <span>/</span>
<span ref={indexNums[4]} class="num" /> <span class="num">{indexLength()[0]}</span>
<span ref={indexNums[5]} class="num" /> <span class="num">{indexLength()[1]}</span>
<span ref={indexNums[6]} class="num" /> <span class="num">{indexLength()[2]}</span>
<span ref={indexNums[7]} class="num" /> <span class="num">{indexLength()[3]}</span>
</div> </div>
<div onClick={onClick} onKeyDown={onClick}> <div onClick={onClick} onKeyDown={onClick}>
{capitalizeFirstLetter(props.closeText)} {capitalizeFirstLetter(props.closeText)}

View File

@@ -2,8 +2,8 @@ import { Show, createSignal, type JSX, type Setter } from 'solid-js'
import type { ImageJSON } from '../resources' import type { ImageJSON } from '../resources'
import { Collection } from './collection' import Collection from './collection'
import { Gallery } from './gallery' import Gallery from './gallery'
/** /**
* interfaces * interfaces