diff --git a/packages/frontend/core/package.json b/packages/frontend/core/package.json index a8e52d00f..ad3a1e935 100644 --- a/packages/frontend/core/package.json +++ b/packages/frontend/core/package.json @@ -39,7 +39,7 @@ "@toeverything/pdf-viewer": "^0.1.1", "@toeverything/theme": "^1.1.12", "@vanilla-extract/dynamic": "^2.1.2", - "animejs": "^3.2.2", + "animejs": "^4.0.0", "bytes": "^3.1.2", "clsx": "^2.1.1", "cmdk": "^1.0.4", diff --git a/packages/frontend/core/src/components/affine/onboarding/steps/animate-in.tsx b/packages/frontend/core/src/components/affine/onboarding/steps/animate-in.tsx index e6adc0f71..e6774e962 100644 --- a/packages/frontend/core/src/components/affine/onboarding/steps/animate-in.tsx +++ b/packages/frontend/core/src/components/affine/onboarding/steps/animate-in.tsx @@ -1,4 +1,4 @@ -import anime from 'animejs'; +import { createSpring, waapi } from 'animejs'; import { useEffect } from 'react'; import type { PaperProps } from '../curve-paper/paper'; @@ -13,15 +13,14 @@ interface AnimateInProps { onFinished?: () => void; } -const easing = 'spring(3.2, 100, 10, 0)'; +const ease = createSpring({ + mass: 3.2, + stiffness: 100, + damping: 10, + velocity: 0, +}); const segments = 6; -const animeSync = (params: Parameters[0]) => { - return new Promise(resolve => { - anime({ ...params, complete: () => resolve(null) }); - }); -}; - export const AnimateIn = ({ article, paperProps, @@ -33,24 +32,31 @@ export const AnimateIn = ({ const rotateX = (1.2 * enterOptions.curve) / segments; useEffect(() => { - Promise.all([ - animeSync({ - targets: `[data-id="${id}"] .${paperStyles.segment}[data-direction="up"]`, - rotateX: [-rotateX, 0], - easing, + let aborted = false; + + waapi.animate( + `[data-id="${id}"] .${paperStyles.segment}[data-direction="up"]`, + { + rotateX: { from: -rotateX, to: 0 }, + ease, delay: enterOptions.delay, - }), - animeSync({ - targets: `[data-id="${id}"] .${paperStyles.segment}[data-direction="down"]`, - rotateX: [rotateX, 0], - easing, + onComplete: () => { + if (!aborted) onFinished?.(); + }, + } + ); + waapi.animate( + `[data-id="${id}"] .${paperStyles.segment}[data-direction="down"]`, + { + rotateX: { from: rotateX, to: 0 }, + ease, delay: enterOptions.delay, - }), - ]) - .then(() => { - onFinished?.(); - }) - .catch(console.error); + } + ); + + return () => { + aborted = true; + }; }, [enterOptions.delay, id, rotateX, onFinished]); const props = { diff --git a/packages/frontend/core/src/mobile/components/swipe-menu/index.tsx b/packages/frontend/core/src/mobile/components/swipe-menu/index.tsx index 5fe1484d6..b5a27f2d1 100644 --- a/packages/frontend/core/src/mobile/components/swipe-menu/index.tsx +++ b/packages/frontend/core/src/mobile/components/swipe-menu/index.tsx @@ -1,5 +1,5 @@ import { LiveData, useLiveData, useService } from '@toeverything/infra'; -import anime from 'animejs'; +import { animate as anime, eases } from 'animejs'; import clsx from 'clsx'; import { type HTMLAttributes, @@ -53,7 +53,7 @@ const animate: TickFunc<[number]> = (content, menu, options, to) => { { deltaX }, { set(target, key, value) { - if (key !== 'deltaX') return false; + if (key !== 'deltaX') return true; target.deltaX = value; tick(content, menu, { ...options, deltaX: value }); return true; @@ -61,11 +61,10 @@ const animate: TickFunc<[number]> = (content, menu, options, to) => { } ); if (deltaX === to) return; - anime({ - targets: proxy, + anime(proxy, { deltaX: to, duration: 230, - easing: 'easeInOutSine', + ease: eases.inOutSine, }); }; diff --git a/packages/frontend/core/src/mobile/dialogs/setting/swipe-dialog.tsx b/packages/frontend/core/src/mobile/dialogs/setting/swipe-dialog.tsx index fb797270a..9a5285888 100644 --- a/packages/frontend/core/src/mobile/dialogs/setting/swipe-dialog.tsx +++ b/packages/frontend/core/src/mobile/dialogs/setting/swipe-dialog.tsx @@ -6,7 +6,7 @@ import { import { PageHeader } from '@affine/core/mobile/components'; import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc'; import { assignInlineVars } from '@vanilla-extract/dynamic'; -import anime from 'animejs'; +import { animate } from 'animejs'; import { createContext, type PropsWithChildren, @@ -78,9 +78,8 @@ const getAnimeProxy = ( if (key === 'deltaX') { target.deltaX = value; tick(overlay, dialog, prev, value, overlay.clientWidth); - return true; } - return false; + return true; }, } ); @@ -91,25 +90,27 @@ const cancel = ( dialog: HTMLDivElement, prev: HTMLElement | null, deltaX: number, - complete?: () => void + onComplete?: () => void ) => { - anime({ - targets: getAnimeProxy( + animate( + getAnimeProxy( overlay, dialog, prev, Math.min(overlay.clientWidth, Math.max(0, deltaX)) ), - deltaX: 0, - easing: 'cubicBezier(.25,.36,.24,.97)', - duration: 320, - complete: () => { - complete?.(); - setTimeout(() => { - reset(overlay, dialog, prev); - }, 0); - }, - }); + { + deltaX: 0, + easing: 'cubicBezier(.25,.36,.24,.97)', + duration: 320, + onComplete: () => { + onComplete?.(); + setTimeout(() => { + reset(overlay, dialog, prev); + }, 0); + }, + } + ); }; const close = ( @@ -117,25 +118,27 @@ const close = ( dialog: HTMLDivElement, prev: HTMLElement | null, deltaX: number, - complete?: () => void + onComplete?: () => void ) => { - anime({ - targets: getAnimeProxy( + animate( + getAnimeProxy( overlay, dialog, prev, Math.min(overlay.clientWidth, Math.max(0, deltaX)) ), - deltaX: overlay.clientWidth, - easing: 'cubicBezier(.25,.36,.24,.97)', - duration: 320, - complete: () => { - complete?.(); - setTimeout(() => { - reset(overlay, dialog, prev); - }, 0); - }, - }); + { + deltaX: overlay.clientWidth, + easing: 'cubicBezier(.25,.36,.24,.97)', + duration: 320, + onComplete: () => { + onComplete?.(); + setTimeout(() => { + reset(overlay, dialog, prev); + }, 0); + }, + } + ); }; const SwipeDialogContext = createContext<{ diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/month.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/month.tsx index 80df2caa3..7fb234c35 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/month.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/month.tsx @@ -1,5 +1,5 @@ import { SwipeHelper } from '@affine/core/mobile/utils'; -import anime from 'animejs'; +import { animate, eases } from 'animejs'; import clsx from 'clsx'; import dayjs from 'dayjs'; import { @@ -72,14 +72,14 @@ export const MonthView = ({ viewportHeight }: MonthViewProps) => { const animateTo = useCallback( (dir: 0 | 1 | -1) => { + if (!swipeRef.current) return; setAnimating(true); - anime({ - targets: swipeRef.current, + animate(swipeRef.current, { translateX: -dir * width, duration: 300, - easing: 'easeInOutSine', - complete: () => { + ease: eases.inOutSine, + onComplete: () => { setSwipingDeltaX(0); setAnimating(false); // should recover swipe before change month diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/viewport.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/viewport.tsx index 1d21537a7..5fb16586a 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/viewport.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/viewport.tsx @@ -1,6 +1,6 @@ import { useGlobalEvent } from '@affine/core/mobile/hooks/use-global-events'; import { SwipeHelper } from '@affine/core/mobile/utils'; -import anime from 'animejs'; +import { animate, eases } from 'animejs'; import clsx from 'clsx'; import dayjs from 'dayjs'; import { @@ -59,7 +59,7 @@ export const ResizeViewport = ({ { value: draggedDistance }, { set(target, key, value) { - if (key !== 'value') return false; + if (key !== 'value') return true; setDragOffset(value); target.value = value; return true; @@ -68,12 +68,11 @@ export const ResizeViewport = ({ ); setIsAnimating(true); - anime({ - targets: dragOffsetProxy, + animate(dragOffsetProxy, { value: targetDragOffset, duration: 300, - easing: 'easeOutCubic', - complete: () => { + ease: eases.outCubic, + onComplete: () => { setMode(targetMode); setDragOffset(0); setIsDragging(false); diff --git a/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/week.tsx b/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/week.tsx index 7447dcc66..df6bc6ead 100644 --- a/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/week.tsx +++ b/packages/frontend/core/src/mobile/pages/workspace/detail/journal-date-picker/week.tsx @@ -1,6 +1,6 @@ import { SwipeHelper } from '@affine/core/mobile/utils'; import { useI18n } from '@affine/i18n'; -import anime from 'animejs'; +import { animate, eases } from 'animejs'; import clsx from 'clsx'; import dayjs from 'dayjs'; import { @@ -82,12 +82,12 @@ export const WeekRowSwipe = ({ start }: WeekRowProps) => { (dir: 0 | 1 | -1) => { setAnimating(true); - anime({ - targets: swipeRef.current, + if (!swipeRef.current) return; + animate(swipeRef.current, { translateX: -dir * width, - easing: 'easeInOutSine', + ease: eases.inOutSine, duration: 300, - complete: () => { + onComplete: () => { setSwipingDeltaX(0); setAnimating(false); if (dir !== 0) { diff --git a/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx b/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx index 9128fdc23..ddc7a9505 100644 --- a/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx +++ b/packages/frontend/core/src/modules/peek-view/view/modal-container.tsx @@ -1,6 +1,6 @@ import * as Dialog from '@radix-ui/react-dialog'; import { useLiveData, useService } from '@toeverything/infra'; -import anime, { type AnimeInstance, type AnimeParams } from 'animejs'; +import { eases, waapi, type WAAPIAnimation } from 'animejs'; import clsx from 'clsx'; import { createContext, @@ -18,6 +18,8 @@ import { EditorSettingService } from '../../editor-setting'; import type { PeekViewAnimation, PeekViewMode } from '../entities/peek-view'; import * as styles from './modal-container.css'; +type WAAPIAnimationParams = Parameters[1]; + const contentOptions: Dialog.DialogContentProps = { ['data-testid' as string]: 'peek-view-modal', onPointerDownOutside: e => { @@ -89,7 +91,7 @@ export const PeekViewModalContainer = forwardRef< const contentRef = useRef(null); const overlayRef = useRef(null); const controlsRef = useRef(null); - const prevAnimeMap = useRef>({}); + const prevAnimeMap = useRef>({}); const editorSettings = useService(EditorSettingService).editorSetting; const fullWidthLayout = useLiveData( editorSettings.settings$.selector(s => s.fullWidthLayout) @@ -98,11 +100,10 @@ export const PeekViewModalContainer = forwardRef< const animateControls = useCallback((animateIn = false) => { const controls = controlsRef.current; if (!controls) return; - anime({ - targets: controls, + waapi.animate(controls, { opacity: animateIn ? [0, 1] : [1, 0], translateX: animateIn ? [-32, 0] : [0, -32], - easing: 'easeOutQuad', + ease: eases.inOutSine, duration: 230, }); }, []); @@ -110,9 +111,9 @@ export const PeekViewModalContainer = forwardRef< async ( zoomIn?: boolean, paramsMap?: { - overlay?: AnimeParams; - content?: AnimeParams; - contentWrapper?: AnimeParams; + overlay?: WAAPIAnimationParams; + content?: WAAPIAnimationParams; + contentWrapper?: WAAPIAnimationParams; } ) => { // if target has no bounding client rect, @@ -168,32 +169,29 @@ export const PeekViewModalContainer = forwardRef< prevAnimeMap.current.content?.pause(); prevAnimeMap.current.contentWrapper?.pause(); - const overlayAnime = anime({ - targets: overlay, + const overlayAnime = waapi.animate(overlay, { opacity: zoomIn ? [0, 1] : [1, 0], - easing: 'easeOutQuad', + ease: eases.inOutSine, duration: 230, ...paramsMap?.overlay, }); const contentAnime = paramsMap?.content && - anime({ - targets: content, + waapi.animate(content, { ...paramsMap.content, }); - const contentWrapperAnime = anime({ - targets, + const contentWrapperAnime = waapi.animate(targets, { left: [fromRect.left, toRect.left], top: [fromRect.top, toRect.top], width: [fromRect.width, toRect.width], height: [fromRect.height, toRect.height], - easing: 'easeOutQuad', + ease: eases.inOutSine, duration: 230, ...paramsMap?.contentWrapper, - complete: (ins: AnimeInstance) => { - paramsMap?.contentWrapper?.complete?.(ins); + onComplete: (ins: WAAPIAnimation) => { + paramsMap?.contentWrapper?.onComplete?.(ins); setAnimeState('idle'); onAnimationEnd?.(); overlay.style.pointerEvents = ''; @@ -272,7 +270,7 @@ export const PeekViewModalContainer = forwardRef< content: { opacity: [1, 0], duration: 180, - easing: 'easeOutQuad', + easing: 'ease', }, }) .then(() => setVtOpen(false)) @@ -292,12 +290,11 @@ export const PeekViewModalContainer = forwardRef< resolve(); return; } - anime({ - targets: [overlay, contentClip], + waapi.animate([overlay, contentClip], { opacity: animateIn ? [0, 1] : [1, 0], - easing: 'easeOutQuad', + ease: eases.inOutSine, duration: 230, - complete: () => { + onComplete: () => { if (!animateIn) setVtOpen(false); setAnimeState('idle'); onAnimationEnd?.(); @@ -323,20 +320,18 @@ export const PeekViewModalContainer = forwardRef< return; } - anime({ - targets: [overlay], + waapi.animate([overlay], { opacity: animateIn ? [0, 1] : [1, 0], - easing: 'easeOutQuad', + ease: eases.inOutSine, duration: 230, }); - anime({ - targets: [contentClip], + waapi.animate([contentClip], { opacity: animateIn ? [0, 1] : [1, 0], y: animateIn ? ['-2%', '0%'] : ['0%', '-2%'], scale: animateIn ? [0.96, 1] : [1, 0.96], - easing: 'cubicBezier(0.42, 0, 0.58, 1)', + ease: eases.cubicBezier(0.42, 0, 0.58, 1), duration: 230, - complete: () => { + onComplete: () => { if (!animateIn) setVtOpen(false); setAnimeState('idle'); onAnimationEnd?.(); diff --git a/yarn.lock b/yarn.lock index a5027666b..3bac73584 100644 --- a/yarn.lock +++ b/yarn.lock @@ -424,7 +424,7 @@ __metadata: "@types/lodash-es": "npm:^4.17.12" "@vanilla-extract/css": "npm:^1.17.0" "@vanilla-extract/dynamic": "npm:^2.1.2" - animejs: "npm:^3.2.2" + animejs: "npm:^4.0.0" bytes: "npm:^3.1.2" clsx: "npm:^2.1.1" cmdk: "npm:^1.0.4" @@ -16439,10 +16439,10 @@ __metadata: languageName: node linkType: hard -"animejs@npm:^3.2.2": - version: 3.2.2 - resolution: "animejs@npm:3.2.2" - checksum: 10/7abdb56f415c666ba02f4e64fdbb10d457fed7e3711b0f006f97e48e5650097013397d890e8ceb31e9e06b73bf6dfd9202309d0dae0fc0b5190aa7c4e0ab7054 +"animejs@npm:^4.0.0": + version: 4.0.0 + resolution: "animejs@npm:4.0.0" + checksum: 10/2d9e67b4de9d2ba3d44c2e00900d9baae5ee6a7dbb01b2b78b93bdbf1da0656bf8ad88c22607248889495faad52caaac2f0ae0436a5d5b7828f45200a9f48064 languageName: node linkType: hard