mirror of
https://gitea.tendokyu.moe/sk1982/actaeon.git
synced 2026-04-25 23:36:56 -05:00
reduce ticker animation lag
This commit is contained in:
parent
58290aeeed
commit
657bdb3d2b
|
|
@ -1,4 +1,4 @@
|
||||||
import { ReactNode } from 'react';
|
import React, { ReactNode } from 'react';
|
||||||
|
|
||||||
const BACKGROUNDS = [
|
const BACKGROUNDS = [
|
||||||
['bg-[#02a076]'],
|
['bg-[#02a076]'],
|
||||||
|
|
@ -17,10 +17,10 @@ export type ChuniDifficultyContainerProps = {
|
||||||
className?: string,
|
className?: string,
|
||||||
difficulty: number,
|
difficulty: number,
|
||||||
containerClassName?: string
|
containerClassName?: string
|
||||||
};
|
} & React.HTMLAttributes<HTMLDivElement>;
|
||||||
|
|
||||||
export const ChuniDifficultyContainer = ({ children, className, difficulty, containerClassName }: ChuniDifficultyContainerProps) => {
|
export const ChuniDifficultyContainer = ({ children, className, difficulty, containerClassName, ...props }: ChuniDifficultyContainerProps) => {
|
||||||
return (<div className={`relative ${className ?? ''}`}>
|
return (<div className={`relative ${className ?? ''}`} {...props}>
|
||||||
{BACKGROUNDS[difficulty].map((className, i) => <div className={`${className} w-full h-full absolute inset-0 z-0 rounded`} key={i} />)}
|
{BACKGROUNDS[difficulty].map((className, i) => <div className={`${className} w-full h-full absolute inset-0 z-0 rounded`} key={i} />)}
|
||||||
<div className={`z-0 relative w-full h-full ${containerClassName ?? ''}`}>{children}</div>
|
<div className={`z-0 relative w-full h-full ${containerClassName ?? ''}`}>{children}</div>
|
||||||
</div>)
|
</div>)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { Filterers, FilterSorter, Sorter } from '@/components/filter-sorter';
|
||||||
import { WindowScroller, Grid, AutoSizer, List } from 'react-virtualized';
|
import { WindowScroller, Grid, AutoSizer, List } from 'react-virtualized';
|
||||||
import { Button, SelectItem } from '@nextui-org/react';
|
import { Button, SelectItem } from '@nextui-org/react';
|
||||||
import { addFavoriteMusic, getMusic, removeFavoriteMusic } from '@/actions/chuni/music';
|
import { addFavoriteMusic, getMusic, removeFavoriteMusic } from '@/actions/chuni/music';
|
||||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { worldsEndStars } from '@/helpers/chuni/worlds-end-stars';
|
import { worldsEndStars } from '@/helpers/chuni/worlds-end-stars';
|
||||||
import { ChuniDifficultyContainer } from '@/components/chuni/difficulty-container';
|
import { ChuniDifficultyContainer } from '@/components/chuni/difficulty-container';
|
||||||
import { getJacketUrl } from '@/helpers/assets';
|
import { getJacketUrl } from '@/helpers/assets';
|
||||||
|
|
@ -14,7 +14,7 @@ import { ChuniRating } from '@/components/chuni/rating';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { HeartIcon as OutlineHeartIcon, Squares2X2Icon } from '@heroicons/react/24/outline';
|
import { HeartIcon as OutlineHeartIcon, Squares2X2Icon } from '@heroicons/react/24/outline';
|
||||||
import { HeartIcon as SolidHeartIcon } from '@heroicons/react/24/solid';
|
import { HeartIcon as SolidHeartIcon } from '@heroicons/react/24/solid';
|
||||||
import { Ticker } from '@/components/ticker';
|
import { Ticker, TickerHoverProvider } from '@/components/ticker';
|
||||||
import { CHUNI_DIFFICULTIES } from '@/helpers/chuni/difficulties';
|
import { CHUNI_DIFFICULTIES } from '@/helpers/chuni/difficulties';
|
||||||
import { CHUNI_SCORE_RANKS } from '@/helpers/chuni/score-ranks';
|
import { CHUNI_SCORE_RANKS } from '@/helpers/chuni/score-ranks';
|
||||||
import { CHUNI_LAMPS } from '@/helpers/chuni/lamps';
|
import { CHUNI_LAMPS } from '@/helpers/chuni/lamps';
|
||||||
|
|
@ -93,7 +93,7 @@ const MusicGrid = ({ music, size, setMusicList, fullMusicList }: ChuniMusicListP
|
||||||
}, [size])
|
}, [size])
|
||||||
|
|
||||||
return (<WindowScroller>
|
return (<WindowScroller>
|
||||||
{({ height, isScrolling, onChildScroll, scrollTop }) =>
|
{useCallback(({ height, isScrolling, onChildScroll, scrollTop }) =>
|
||||||
(<AutoSizer disableHeight>
|
(<AutoSizer disableHeight>
|
||||||
{({ width }) => {
|
{({ width }) => {
|
||||||
const itemsPerRow = Math.max(1, Math.floor(width / itemWidth));
|
const itemsPerRow = Math.max(1, Math.floor(width / itemWidth));
|
||||||
|
|
@ -103,14 +103,19 @@ const MusicGrid = ({ music, size, setMusicList, fullMusicList }: ChuniMusicListP
|
||||||
onScroll={onChildScroll} scrollTop={scrollTop} ref={listRef}
|
onScroll={onChildScroll} scrollTop={scrollTop} ref={listRef}
|
||||||
rowRenderer={({ index, key, style }) => <div key={key} style={style} className="w-full h-full flex justify-center">
|
rowRenderer={({ index, key, style }) => <div key={key} style={style} className="w-full h-full flex justify-center">
|
||||||
{music.slice(index * itemsPerRow, (index + 1) * itemsPerRow).map(item => <div key={`${item.songId}-${item.chartId}`} className={itemClass}>
|
{music.slice(index * itemsPerRow, (index + 1) * itemsPerRow).map(item => <div key={`${item.songId}-${item.chartId}`} className={itemClass}>
|
||||||
<ChuniDifficultyContainer difficulty={item.chartId!} containerClassName="flex flex-col" className="w-full h-full border border-gray-500/75 rounded-md [&:hover_.ticker]:[animation-play-state:running]">
|
<TickerHoverProvider>
|
||||||
|
{setHover => <ChuniDifficultyContainer difficulty={item.chartId!}
|
||||||
|
containerClassName="flex flex-col"
|
||||||
|
className="w-full h-full border border-gray-500/75 rounded-md"
|
||||||
|
onMouseEnter={() => setHover(true)}
|
||||||
|
onMouseLeave={() => setHover(false)}>
|
||||||
<div className="aspect-square w-full p-[0.2rem] relative">
|
<div className="aspect-square w-full p-[0.2rem] relative">
|
||||||
<img src={getJacketUrl(`chuni/jacket/${item.jacketPath}`)} alt={item.title ?? 'Music'} className="rounded" />
|
<img src={getJacketUrl(`chuni/jacket/${item.jacketPath}`)} alt={item.title ?? 'Music'} className="rounded" />
|
||||||
{item.rating && !item.worldsEndTag && <div className={`${size === 'lg' ? 'text-2xl' : ''} absolute bottom-0.5 left-0.5 bg-gray-200/60 backdrop-blur-sm px-0.5 rounded`}>
|
{item.rating && !item.worldsEndTag && <div className={`${size === 'lg' ? 'text-2xl' : ''} absolute bottom-0.5 left-0.5 bg-gray-200/60 backdrop-blur-sm px-0.5 rounded`}>
|
||||||
<ChuniRating rating={+item.rating * 100} className="-my-0.5">
|
<ChuniRating rating={+item.rating * 100} className="-my-0.5">
|
||||||
{item.rating.slice(0, item.rating.indexOf('.') + 3)}
|
{item.rating.slice(0, item.rating.indexOf('.') + 3)}
|
||||||
</ChuniRating>
|
</ChuniRating>
|
||||||
</div>}
|
</div>}
|
||||||
<ChuniLevelBadge className={`${size === 'lg' ? 'h-14' : 'w-14'} absolute bottom-px right-px`} music={item} />
|
<ChuniLevelBadge className={`${size === 'lg' ? 'h-14' : 'w-14'} absolute bottom-px right-px`} music={item} />
|
||||||
|
|
||||||
<Button isIconOnly className={`absolute top-0 left-0 pt-1 bg-gray-600/25 ${item.favorite ? 'text-red-500': ''}`}
|
<Button isIconOnly className={`absolute top-0 left-0 pt-1 bg-gray-600/25 ${item.favorite ? 'text-red-500': ''}`}
|
||||||
|
|
@ -143,7 +148,7 @@ const MusicGrid = ({ music, size, setMusicList, fullMusicList }: ChuniMusicListP
|
||||||
<div className="px-0.5 mb-1 flex">
|
<div className="px-0.5 mb-1 flex">
|
||||||
{size === 'lg' && <div className="h-full w-1/3 mr-0.5">
|
{size === 'lg' && <div className="h-full w-1/3 mr-0.5">
|
||||||
{item.isSuccess ? <ChuniLampSuccessBadge success={item.isSuccess} /> : null}
|
{item.isSuccess ? <ChuniLampSuccessBadge success={item.isSuccess} /> : null}
|
||||||
</div>}
|
</div>}
|
||||||
|
|
||||||
<div className={`h-full ${size === 'lg' ? 'w-1/3' : 'w-1/2'}`}>
|
<div className={`h-full ${size === 'lg' ? 'w-1/3' : 'w-1/2'}`}>
|
||||||
{item.scoreRank !== null && <ChuniScoreBadge variant={getVariantFromRank(item.scoreRank)} className="h-full">
|
{item.scoreRank !== null && <ChuniScoreBadge variant={getVariantFromRank(item.scoreRank)} className="h-full">
|
||||||
|
|
@ -160,11 +165,12 @@ const MusicGrid = ({ music, size, setMusicList, fullMusicList }: ChuniMusicListP
|
||||||
<Ticker hoverOnly noDelay>{item.title}</Ticker>
|
<Ticker hoverOnly noDelay>{item.title}</Ticker>
|
||||||
</Link>
|
</Link>
|
||||||
<Ticker className={`${size === 'lg' ? 'text-medium mb-1.5' : 'text-xs mb-0.5' } text-center px-1 drop-shadow-2xl text-white`} hoverOnly noDelay>{item.artist}</Ticker>
|
<Ticker className={`${size === 'lg' ? 'text-medium mb-1.5' : 'text-xs mb-0.5' } text-center px-1 drop-shadow-2xl text-white`} hoverOnly noDelay>{item.artist}</Ticker>
|
||||||
</ChuniDifficultyContainer>
|
</ChuniDifficultyContainer>}
|
||||||
|
</TickerHoverProvider>
|
||||||
</div>)}
|
</div>)}
|
||||||
</div>} />)
|
</div>} />)
|
||||||
}}
|
}}
|
||||||
</AutoSizer>)}
|
</AutoSizer>), [music, fullMusicList, pendingFavorite, itemWidth])}
|
||||||
</WindowScroller>);
|
</WindowScroller>);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import { ReactNode } from 'react';
|
'use client';
|
||||||
|
|
||||||
|
import React, { createContext, ReactNode, useContext, useState } from 'react';
|
||||||
import './ticker.scss';
|
import './ticker.scss';
|
||||||
|
|
||||||
export type TickerProps = {
|
export type TickerProps = {
|
||||||
|
|
@ -8,12 +10,37 @@ export type TickerProps = {
|
||||||
noDelay?: boolean
|
noDelay?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TickerHoverContext = createContext<boolean | null>(null);
|
||||||
|
|
||||||
|
type TickerHoverProviderProps = {
|
||||||
|
children: (setHover: (hovering: boolean) => void) => ReactNode
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TickerHoverProvider = ({ children }: TickerHoverProviderProps) => {
|
||||||
|
const [hovering, setHovering] = useState(false);
|
||||||
|
|
||||||
|
return <TickerHoverContext.Provider value={hovering}>
|
||||||
|
{ children(setHovering) }
|
||||||
|
</TickerHoverContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export const Ticker = ({ children, hoverOnly, className, noDelay }: TickerProps) => {
|
export const Ticker = ({ children, hoverOnly, className, noDelay }: TickerProps) => {
|
||||||
const hoverClass = hoverOnly ? '[&:hover_*]:[animation-play-state:running] [&_*]:[animation-play-state:paused]' : '[&:hover_*]:[animation-play-state:paused]';
|
|
||||||
const outerAnimation = noDelay ? 'animate-[outer-overflow-nodelay_15s_linear_infinite_alternate]' : 'animate-[outer-overflow_15s_linear_infinite_alternate]';
|
const outerAnimation = noDelay ? 'animate-[outer-overflow-nodelay_15s_linear_infinite_alternate]' : 'animate-[outer-overflow_15s_linear_infinite_alternate]';
|
||||||
const innerAnimation = noDelay ? 'animate-[inner-overflow-nodelay_15s_linear_infinite_alternate]' : 'animate-[inner-overflow_15s_linear_infinite_alternate]';
|
const innerAnimation = noDelay ? 'animate-[inner-overflow-nodelay_15s_linear_infinite_alternate]' : 'animate-[inner-overflow_15s_linear_infinite_alternate]';
|
||||||
|
const hoverContext = useContext(TickerHoverContext);
|
||||||
|
const [textHovering, setTextHovering] = useState(false);
|
||||||
|
const hovering = (hoverContext !== null && hoverContext) || textHovering;
|
||||||
|
|
||||||
return (<div className={`text-nowrap whitespace-nowrap overflow-hidden w-full ${hoverClass} ${className ?? ''}`}>
|
const hoverClass = !hoverOnly && hovering ? '[&:hover_*]:[animation-play-state:paused]' : '';
|
||||||
|
|
||||||
|
if (hoverOnly && !hovering)
|
||||||
|
return (<div className={`text-nowrap whitespace-nowrap overflow-hidden w-full ${className ?? ''}`}
|
||||||
|
onMouseEnter={() => setTextHovering(true)}>
|
||||||
|
{ children }
|
||||||
|
</div>);
|
||||||
|
|
||||||
|
return (<div className={`text-nowrap whitespace-nowrap overflow-hidden w-full ${hoverClass} ${className ?? ''}`} onMouseLeave={() => setTextHovering(false)}>
|
||||||
<div className={`${outerAnimation} ticker max-w-full inline-block`}>
|
<div className={`${outerAnimation} ticker max-w-full inline-block`}>
|
||||||
<div className={`${innerAnimation} ticker inline-block`}>
|
<div className={`${innerAnimation} ticker inline-block`}>
|
||||||
{ children }
|
{ children }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user