From 65d2b26e15ade01e1aca6580f9fd67e2cefcc763 Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Fri, 19 Sep 2025 06:02:20 -0700 Subject: [PATCH] fix: remove JS timer --- packages/core/skeleton.css | 17 +++++++++++++ packages/core/src/index.ts | 48 ++----------------------------------- packages/react/src/index.ts | 28 +++++++--------------- packages/vue/src/index.ts | 14 ----------- 4 files changed, 27 insertions(+), 80 deletions(-) diff --git a/packages/core/skeleton.css b/packages/core/skeleton.css index c64f57d..8bef558 100644 --- a/packages/core/skeleton.css +++ b/packages/core/skeleton.css @@ -7,6 +7,7 @@ --skeleton-window-width: 0px; --skeleton-left: 0px; --skeleton-percentage: 0; + --skeleton-animation-duration: 2s; } .loading-skeleton { @@ -36,6 +37,7 @@ calc(var(--skeleton-percentage) / 100) - var(--skeleton-left) ) ); + animation: var(--skeleton-animation-duration) skeletonPercent infinite; } @media (prefers-reduced-motion) { @@ -43,3 +45,18 @@ --skeleton-pseudo-element-display: none; } } + +@property --skeleton-percentage { + syntax: ''; + inherits: false; + initial-value: 0; +} + +@keyframes skeletonPercent { + 0% { + --skeleton-percentage: 0; + } + 100% { + --skeleton-percentage: 100; + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 985fc2c..16a4722 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,7 +1,6 @@ import { Effect, Store } from '@tanstack/store' export const percentageStore = new Store(0) -const renderedComponents = new Store(0) const animationDurationStore = new Store(2000) // in milliseconds export interface SkeletonOptions { @@ -20,18 +19,6 @@ const defaultOptions: SkeletonOptions = { const optionsStore = new Store(defaultOptions as Required) -export function addComponent() { - renderedComponents.setState((count) => count + 1) -} - -export function removeComponent() { - renderedComponents.setState((count) => Math.max(0, count - 1)) -} - -function setAnimationDuration(duration: number) { - animationDurationStore.setState(duration) -} - export function setOptions(options: SkeletonOptions) { optionsStore.setState(() => { const fullOptions = Object.entries(options).reduce((acc, [key, value]) => { @@ -66,38 +53,6 @@ export function setOptions(options: SkeletonOptions) { }) } -let frameId: number -export const timingEffect = new Effect({ - deps: [renderedComponents, animationDurationStore], - fn: () => { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (frameId !== undefined) { - cancelAnimationFrame(frameId) - } - const componentCount = renderedComponents.state - const animationDuration = animationDurationStore.state - - if (componentCount === 0) { - return - } - - // Count to 100 in `animationDuration` milliseconds, updating the percentage store - // Loop every `animationDuration` milliseconds so that the percentage goes from 0 to 100 repeatedly - let start: number | null = null - - const step = (timestamp: number) => { - if (!start) start = timestamp - const elapsed = timestamp - start - const progress = (elapsed % animationDuration) / animationDuration - percentageStore.setState(() => progress * 100) - frameId = requestAnimationFrame(step) - } - - frameId = requestAnimationFrame(step) - }, - eager: true, -}) - const setWindowSize = () => { const windowWidth = window.innerWidth document.body.style.setProperty('--skeleton-window-width', `${windowWidth}px`) @@ -106,7 +61,8 @@ const setWindowSize = () => { const distance = windowWidth + optionsStore.state.highlightSize const calculatedDuration = (distance / optionsStore.state.animationSpeed) * 1000 - setAnimationDuration(calculatedDuration) + animationDurationStore.setState(() => calculatedDuration) + document.body.style.setProperty('--skeleton-animation-duration', `${calculatedDuration}ms`) } export const windowSizeEffect = new Effect({ diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 81ae903..a886854 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -1,29 +1,25 @@ -import { useLayoutEffect, useState } from 'react' +import { useEffect, useLayoutEffect, useState } from 'react' import { useStore } from '@tanstack/react-store' import { - addComponent, getSkeletonObserver, percentageStore, - removeComponent, setOptions, - timingEffect, windowSizeEffect, } from '@sync-skeleton/core' import type { SkeletonOptions } from '@sync-skeleton/core' +const useIsomorphicLayoutEffect = + typeof window !== 'undefined' ? useLayoutEffect : useEffect + export function useSkeletonSetup(options?: SkeletonOptions) { - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { if (!options) return setOptions(options) }, [options]) - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { return windowSizeEffect.mount() }, []) - - useLayoutEffect(() => { - return timingEffect.mount() - }, []) } export const useSkeleton = () => { @@ -31,20 +27,12 @@ export const useSkeleton = () => { const percentageVal = useStore(percentageStore) - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { if (!ref) return ref.style.setProperty('--skeleton-percentage', `${percentageVal}`) }, [ref, percentageVal]) - useLayoutEffect(() => { - if (!ref) return - addComponent() - return () => { - removeComponent() - } - }, [ref]) - - useLayoutEffect(() => { + useIsomorphicLayoutEffect(() => { return getSkeletonObserver(ref) }, [ref]) diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 0e5c92e..f539257 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -1,12 +1,9 @@ import { onMounted, onUnmounted, ref, watch, watchEffect } from 'vue' import { useStore } from '@tanstack/vue-store' import { - addComponent, getSkeletonObserver, percentageStore, - removeComponent, setOptions, - timingEffect, windowSizeEffect, } from '@sync-skeleton/core' import type { SkeletonOptions } from '@sync-skeleton/core' @@ -23,15 +20,12 @@ export function useSkeletonSetup(optionsFn = () => ({}) as SkeletonOptions) { ) let windowEffectCleanup: () => void - let timingEffectCleanup: () => void onMounted(() => { windowEffectCleanup = windowSizeEffect.mount() - timingEffectCleanup = timingEffect.mount() }) onUnmounted(() => { windowEffectCleanup() - timingEffectCleanup() }) } @@ -48,14 +42,6 @@ export const useSkeleton = () => { ) }) - onMounted(() => { - addComponent() - }) - - onUnmounted(() => { - removeComponent() - }) - watch(elRef, (_value, _oldValue, onCleanup) => { const cleanup = getSkeletonObserver(elRef.value) onCleanup(() => { -- 2.51.2