페이지 전환 애니메이션 전체 revert
유저 체감 개선이 확실치 않고 오히려 버벅임 느낌이 남아있어 관련 6개 커밋 (d1764de,1344a2f,48f43ec,f5c5c69,670d8ab,f63c1e0) 을 git revert. StaggerGroup 컴포넌트 제거, Feature/Admin 페이지의 Suspense 스피너 복원, 보스 리스트의 border 구조 원복. prefetch(7ebfe4a), backdrop-blur 제거(669b358), font-display optional (6e2159c) 은 애니메이션 무관한 최적화라 유지. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f63c1e06c5
commit
98b27a5fae
8 changed files with 25 additions and 80 deletions
|
|
@ -1,50 +0,0 @@
|
|||
import { Children } from 'react'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
/**
|
||||
* 자식을 각 motion.div 로 감싸 순차 페이드인.
|
||||
* translateY 대신 scale 을 쓰는 이유: Chrome 이 translateY 애니메이션을
|
||||
* 레이아웃 변경(CLS)으로 카운트하는 케이스가 있어서. scale 은 compositor-only 라 CLS 0.
|
||||
*
|
||||
* @param {number} staggerDelay - 자식 간 간격 (초)
|
||||
* @param {number} scaleFrom - 시작 scale (기본 0.97)
|
||||
* @param {number} duration - 각 자식 애니메이션 지속시간 (초)
|
||||
*/
|
||||
export default function StaggerGroup({
|
||||
children,
|
||||
className,
|
||||
style,
|
||||
staggerDelay = 0.08,
|
||||
scaleFrom = 0.97,
|
||||
duration = 0.35,
|
||||
}) {
|
||||
const containerVariants = {
|
||||
hidden: {},
|
||||
show: { transition: { staggerChildren: staggerDelay } },
|
||||
}
|
||||
|
||||
const itemVariants = {
|
||||
hidden: { opacity: 0, scale: scaleFrom },
|
||||
show: {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
transition: { duration },
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className={className}
|
||||
style={style}
|
||||
variants={containerVariants}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
>
|
||||
{Children.map(children, (child, i) => (
|
||||
child == null || child === false
|
||||
? null
|
||||
: <motion.div variants={itemVariants} key={i} style={{ willChange: 'transform, opacity' }}>{child}</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
|
@ -11,7 +11,11 @@ export default function FeaturePage() {
|
|||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<Suspense fallback={
|
||||
<div className="flex items-center justify-center pt-20">
|
||||
<div className="w-6 h-6 border-2 border-emerald-500 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
}>
|
||||
<Component />
|
||||
</Suspense>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -49,7 +49,14 @@ export default function AdminFeaturePage() {
|
|||
}
|
||||
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<Suspense fallback={
|
||||
<div className="flex items-center justify-center pt-20">
|
||||
<div
|
||||
className="w-6 h-6 border-2 border-t-transparent rounded-full animate-spin"
|
||||
style={{ borderColor: 'var(--accent)', borderTopColor: 'transparent' }}
|
||||
/>
|
||||
</div>
|
||||
}>
|
||||
<Component />
|
||||
</Suspense>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { useEffect, useLayoutEffect } from 'react'
|
||||
import { useQuery, useQueries } from '@tanstack/react-query'
|
||||
import { motion } from 'framer-motion'
|
||||
import { api } from '../../../api/client'
|
||||
import { useLayout } from '../../../components/pc/Layout'
|
||||
import CharacterPanel from './user/CharacterPanel'
|
||||
|
|
@ -71,13 +70,7 @@ export default function BossCrystal() {
|
|||
const isMaxReached = currentSelectedCount >= MAX_PER_CHARACTER
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
className="h-full"
|
||||
initial={{ opacity: 0, scale: 0.97 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
style={{ willChange: 'transform, opacity' }}
|
||||
>
|
||||
<div className="h-full">
|
||||
{isLoading ? (
|
||||
<div
|
||||
className="rounded-2xl border p-16 text-center"
|
||||
|
|
@ -124,6 +117,6 @@ export default function BossCrystal() {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'
|
||||
import Select from '../../../../components/common/Select'
|
||||
import StaggerGroup from '../../../../components/common/StaggerGroup'
|
||||
import { DIFFICULTIES, formatMeso } from '../admin/constants'
|
||||
|
||||
const LABEL_EN = { easy: 'EASY', normal: 'NORMAL', hard: 'HARD', chaos: 'CHAOS', extreme: 'EXTREME' }
|
||||
|
|
@ -68,12 +67,7 @@ export default function BossSelector({ characterName, bosses, selections, onChan
|
|||
}}
|
||||
defer
|
||||
>
|
||||
<StaggerGroup
|
||||
className="divide-y divide-[var(--panel-border)] px-2"
|
||||
staggerDelay={0.03}
|
||||
yOffset={10}
|
||||
duration={0.25}
|
||||
>
|
||||
<div className="divide-y px-2" style={{ '--tw-divide-opacity': 1 }}>
|
||||
{bosses.map((boss) => {
|
||||
const availableDiffs = DIFFICULTIES.filter((d) =>
|
||||
boss.difficulties.some((bd) => bd.difficulty === d.key)
|
||||
|
|
@ -94,7 +88,7 @@ export default function BossSelector({ characterName, bosses, selections, onChan
|
|||
return (
|
||||
<div
|
||||
key={boss.id}
|
||||
className={`flex items-center gap-3 px-3 py-3 ${
|
||||
className={`flex items-center gap-3 px-3 py-3 border-t first:border-t-0 ${
|
||||
disabled ? 'pointer-events-none' : ''
|
||||
}`}
|
||||
style={{
|
||||
|
|
@ -108,7 +102,7 @@ export default function BossSelector({ characterName, bosses, selections, onChan
|
|||
className="shrink-0 w-11 h-11 rounded-lg overflow-hidden"
|
||||
style={{ background: 'var(--surface-nested)' }}
|
||||
>
|
||||
<img src={boss.image_url || '/default.png'} alt={boss.name} loading="lazy" decoding="async" className="w-full h-full object-cover" />
|
||||
<img src={boss.image_url || '/default.png'} alt={boss.name} className="w-full h-full object-cover" />
|
||||
</div>
|
||||
<span className="text-base font-medium leading-tight whitespace-nowrap overflow-hidden text-ellipsis">{boss.name}</span>
|
||||
</div>
|
||||
|
|
@ -174,7 +168,7 @@ export default function BossSelector({ characterName, bosses, selections, onChan
|
|||
</div>
|
||||
)
|
||||
})}
|
||||
</StaggerGroup>
|
||||
</div>
|
||||
</OverlayScrollbarsComponent>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import PointsInput from './components/PointsInput'
|
|||
import WeeklyDefault from './components/WeeklyDefault'
|
||||
import DatePicker from '../../../components/common/DatePicker'
|
||||
import ConfirmDialog from '../../../components/common/ConfirmDialog'
|
||||
import StaggerGroup from '../../../components/common/StaggerGroup'
|
||||
|
||||
export default function Destiny() {
|
||||
const calcMode = useLiberationStore((s) => s.destinyCalcMode)
|
||||
|
|
@ -70,7 +69,7 @@ export default function Destiny() {
|
|||
}
|
||||
|
||||
return (
|
||||
<StaggerGroup className="space-y-6">
|
||||
<>
|
||||
{/* 계산 모드 탭 */}
|
||||
<div
|
||||
className="max-w-3xl mx-auto flex gap-1 p-1 rounded-xl border"
|
||||
|
|
@ -213,6 +212,6 @@ export default function Destiny() {
|
|||
confirmText="초기화"
|
||||
destructive
|
||||
/>
|
||||
</StaggerGroup>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import ProgressBar from './components/ProgressBar'
|
|||
import WeeklyDefault from './components/WeeklyDefault'
|
||||
import DatePicker from '../../../components/common/DatePicker'
|
||||
import ConfirmDialog from '../../../components/common/ConfirmDialog'
|
||||
import StaggerGroup from '../../../components/common/StaggerGroup'
|
||||
|
||||
export default function Genesis() {
|
||||
const calcMode = useLiberationStore((s) => s.genesisCalcMode)
|
||||
|
|
@ -94,7 +93,7 @@ export default function Genesis() {
|
|||
}
|
||||
|
||||
return (
|
||||
<StaggerGroup className="space-y-6">
|
||||
<>
|
||||
{/* 계산 모드 탭 */}
|
||||
<div
|
||||
className="max-w-3xl mx-auto flex gap-1 p-1 rounded-xl border"
|
||||
|
|
@ -239,6 +238,6 @@ export default function Genesis() {
|
|||
confirmText="초기화"
|
||||
destructive
|
||||
/>
|
||||
</StaggerGroup>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { formatMesoKorean } from '../../../utils/formatting'
|
|||
import { formatKoreanDate, computeCompletion, TYPE_ORDER, eventBonusForType } from '../utils'
|
||||
import CharacterCard from './user/CharacterCard'
|
||||
import SymbolCard from './user/SymbolCard'
|
||||
import StaggerGroup from '../../../components/common/StaggerGroup'
|
||||
|
||||
export default function Symbol() {
|
||||
const { setFullscreen } = useLayout()
|
||||
|
|
@ -194,7 +193,7 @@ export default function Symbol() {
|
|||
}, [symbols, progress, selectedChar?.event_skill])
|
||||
|
||||
return (
|
||||
<StaggerGroup className="space-y-6 pb-10 max-w-5xl mx-auto">
|
||||
<div className="space-y-6 pb-10 max-w-5xl mx-auto">
|
||||
{/* 캐릭터 조회 */}
|
||||
<div
|
||||
className="rounded-2xl border p-5 space-y-4"
|
||||
|
|
@ -350,6 +349,6 @@ export default function Symbol() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</StaggerGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue