diff --git a/packages/varlet-shared/package.json b/packages/varlet-shared/package.json index a513a18a26f..c7380024c32 100644 --- a/packages/varlet-shared/package.json +++ b/packages/varlet-shared/package.json @@ -37,7 +37,7 @@ "build": "tsup src/index.ts --format esm,cjs --out-dir=lib --dts --clean" }, "dependencies": { - "rattail": "1.0.0" + "rattail": "1.0.12" }, "devDependencies": { "@types/node": "^18.7.18", diff --git a/packages/varlet-shared/src/index.ts b/packages/varlet-shared/src/index.ts index 939ca93a374..a39a5949eed 100644 --- a/packages/varlet-shared/src/index.ts +++ b/packages/varlet-shared/src/index.ts @@ -1,2 +1,3 @@ -export * from './is.js' +export * from './is' +export * from './motion' export * from 'rattail' diff --git a/packages/varlet-shared/src/motion.ts b/packages/varlet-shared/src/motion.ts new file mode 100644 index 00000000000..c1b7f73d519 --- /dev/null +++ b/packages/varlet-shared/src/motion.ts @@ -0,0 +1,110 @@ +import { requestAnimationFrame, clamp, cancelAnimationFrame } from 'rattail' + +export interface MotionOptions { + from: number + to: number + duration?: number + frame?: ({ value, done }: { value: number; done: boolean }) => void + timingFunction?: (v: number) => number + onStateChange?: (state: MotionState) => void +} + +export type MotionState = 'running' | 'paused' | 'pending' | 'finished' + +export interface Motion { + state: MotionState + start: () => void + pause: () => void + reset: () => void +} + +export function motion(options: MotionOptions) { + const { + from, + to, + duration = 300, + frame = () => {}, + timingFunction = (value) => value, + onStateChange = () => {}, + } = options + + let state: MotionState = 'pending' + let value = from + const distance = to - from + let ticker: number | undefined = undefined + let startTime: number | undefined = undefined + let pausedTime: number | undefined = undefined + let sleepTime: number = 0 + + function start() { + if (state === 'running' || state === 'finished') { + return + } + + setState('running') + + const now = performance.now() + startTime = startTime != null ? startTime : now + sleepTime += pausedTime != null ? now - pausedTime : 0 + pausedTime = undefined + tick() + + function tick() { + ticker = requestAnimationFrame(() => { + if (state !== 'running') { + return + } + + const now = performance.now() + const executionTime = now - startTime! - sleepTime + const progress = clamp(executionTime / duration, 0, 1) + value = distance * timingFunction(progress) + from + + if (progress >= 1) { + setState('finished') + frame({ value: to, done: true }) + return + } + + frame({ value, done: false }) + tick() + }) + } + } + + function pause() { + if (state !== 'running') { + return + } + + setState('paused') + pausedTime = performance.now() + cancelAnimationFrame(ticker!) + } + + function reset() { + cancelAnimationFrame(ticker!) + setState('pending') + value = from + ticker = undefined + startTime = undefined + pausedTime = undefined + sleepTime = 0 + } + + function getState() { + return state + } + + function setState(_state: MotionState) { + state = _state + onStateChange(_state) + } + + return { + start, + pause, + reset, + getState, + } +} diff --git a/packages/varlet-ui/src/count-to/CountTo.vue b/packages/varlet-ui/src/count-to/CountTo.vue new file mode 100644 index 00000000000..0c9252cd063 --- /dev/null +++ b/packages/varlet-ui/src/count-to/CountTo.vue @@ -0,0 +1,51 @@ + + + + + diff --git a/packages/varlet-ui/src/count-to/__tests__/index.spec.js b/packages/varlet-ui/src/count-to/__tests__/index.spec.js new file mode 100644 index 00000000000..6bfa7e199a2 --- /dev/null +++ b/packages/varlet-ui/src/count-to/__tests__/index.spec.js @@ -0,0 +1,8 @@ +import CountTo from '..' +import { createApp } from 'vue' +import { expect, test } from 'vitest' + +test('test count-to plugin', () => { + const app = createApp({}).use(CountTo) + expect(app.component(CountTo.name)).toBeTruthy() +}) diff --git a/packages/varlet-ui/src/count-to/countTo.less b/packages/varlet-ui/src/count-to/countTo.less new file mode 100644 index 00000000000..84273c1554b --- /dev/null +++ b/packages/varlet-ui/src/count-to/countTo.less @@ -0,0 +1,2 @@ +.var-count-to { +} diff --git a/packages/varlet-ui/src/count-to/docs/en-US.md b/packages/varlet-ui/src/count-to/docs/en-US.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/varlet-ui/src/count-to/docs/zh-CN.md b/packages/varlet-ui/src/count-to/docs/zh-CN.md new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/varlet-ui/src/count-to/example/index.vue b/packages/varlet-ui/src/count-to/example/index.vue new file mode 100644 index 00000000000..6acdf74149a --- /dev/null +++ b/packages/varlet-ui/src/count-to/example/index.vue @@ -0,0 +1,11 @@ + + + diff --git a/packages/varlet-ui/src/count-to/example/locale/en-US.ts b/packages/varlet-ui/src/count-to/example/locale/en-US.ts new file mode 100644 index 00000000000..cbec61de2fa --- /dev/null +++ b/packages/varlet-ui/src/count-to/example/locale/en-US.ts @@ -0,0 +1,3 @@ +export default { + // Example locale +} diff --git a/packages/varlet-ui/src/count-to/example/locale/index.ts b/packages/varlet-ui/src/count-to/example/locale/index.ts new file mode 100644 index 00000000000..8855457aa5c --- /dev/null +++ b/packages/varlet-ui/src/count-to/example/locale/index.ts @@ -0,0 +1,17 @@ +import { Locale } from '@varlet/ui' +import zhCN from './zh-CN' +import enUS from './en-US' + +const { add, use: exampleUse, t, merge } = Locale.useLocale() + +const use = (lang: string) => { + Locale.use(lang) + exampleUse(lang) +} + +Locale.add('zh-CN', Locale.zhCN) +Locale.add('en-US', Locale.enUS) +add('zh-CN', zhCN) +add('en-US', enUS) + +export { add, t, merge, use } diff --git a/packages/varlet-ui/src/count-to/example/locale/zh-CN.ts b/packages/varlet-ui/src/count-to/example/locale/zh-CN.ts new file mode 100644 index 00000000000..cbec61de2fa --- /dev/null +++ b/packages/varlet-ui/src/count-to/example/locale/zh-CN.ts @@ -0,0 +1,3 @@ +export default { + // Example locale +} diff --git a/packages/varlet-ui/src/count-to/index.ts b/packages/varlet-ui/src/count-to/index.ts new file mode 100644 index 00000000000..d3b8ea52e48 --- /dev/null +++ b/packages/varlet-ui/src/count-to/index.ts @@ -0,0 +1,12 @@ +import CountTo from './CountTo.vue' +import { withInstall, withPropsDefaultsSetter } from '../utils/components' +import { props as countToProps } from './props' + +withInstall(CountTo) +withPropsDefaultsSetter(CountTo, countToProps) + +export { countToProps } + +export const _CountToComponent = CountTo + +export default CountTo diff --git a/packages/varlet-ui/src/count-to/props.ts b/packages/varlet-ui/src/count-to/props.ts new file mode 100644 index 00000000000..455f4337f46 --- /dev/null +++ b/packages/varlet-ui/src/count-to/props.ts @@ -0,0 +1 @@ +export const props = {} diff --git a/packages/varlet-ui/tsconfig.json b/packages/varlet-ui/tsconfig.json index 482daeeb6e0..703da671663 100644 --- a/packages/varlet-ui/tsconfig.json +++ b/packages/varlet-ui/tsconfig.json @@ -8,5 +8,5 @@ "allowJs": true, "types": ["vitest/globals"] }, - "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "types/countTo.d.ts"] } diff --git a/packages/varlet-ui/types/countTo.d.ts b/packages/varlet-ui/types/countTo.d.ts new file mode 100644 index 00000000000..091866ad7c4 --- /dev/null +++ b/packages/varlet-ui/types/countTo.d.ts @@ -0,0 +1,13 @@ +import { VarComponent, BasicAttributes, SetPropsDefaults } from './varComponent' + +export declare const countToProps: Record + +export interface CountToProps extends BasicAttributes {} + +export class CountTo extends VarComponent { + static setPropsDefaults: SetPropsDefaults + + $props: CountToProps +} + +export class _CountToComponent extends CountTo {} diff --git a/packages/varlet-ui/varlet.config.mjs b/packages/varlet-ui/varlet.config.mjs index 07b6452e1f5..b69a40c80ac 100644 --- a/packages/varlet-ui/varlet.config.mjs +++ b/packages/varlet-ui/varlet.config.mjs @@ -263,6 +263,14 @@ export default defineConfig({ }, type: 1, }, + { + text: { + 'zh-CN': 'CountTo 数字动画', + 'en-US': 'CountTo', + }, + doc: 'count-to', + type: 2, + }, { text: { 'zh-CN': 'Skeleton 骨架屏', diff --git a/packages/varlet-use/src/index.ts b/packages/varlet-use/src/index.ts index 32c378af620..1b7134e81a0 100644 --- a/packages/varlet-use/src/index.ts +++ b/packages/varlet-use/src/index.ts @@ -1,13 +1,14 @@ -export * from './useEventListener.js' -export * from './useClickOutside.js' -export * from './onSmartMounted.js' -export * from './onSmartUnmounted.js' -export * from './useParent.js' -export * from './useChildren.js' -export * from './onWindowResize.js' -export * from './useInitialized.js' -export * from './useTouch.js' -export * from './useId.js' -export * from './useClientId.js' -export * from './useWindowSize.js' -export * from './useVModel.js' +export * from './useEventListener' +export * from './useClickOutside' +export * from './onSmartMounted' +export * from './onSmartUnmounted' +export * from './useParent' +export * from './useChildren' +export * from './onWindowResize' +export * from './useInitialized' +export * from './useTouch' +export * from './useId' +export * from './useClientId' +export * from './useWindowSize' +export * from './useVModel' +export * from './useMotion' diff --git a/packages/varlet-use/src/useMotion.ts b/packages/varlet-use/src/useMotion.ts new file mode 100644 index 00000000000..9c2c0d13f4f --- /dev/null +++ b/packages/varlet-use/src/useMotion.ts @@ -0,0 +1,40 @@ +import { MotionState, motion } from '@varlet/shared' +import { ref } from 'vue' + +export interface UseMotionOptions { + from: number + to: number + duration?: number + timingFunction?: (v: number) => number + onFinished?: (value: number) => void +} + +export function useMotion(options: UseMotionOptions) { + const value = ref(options.from) + const state = ref('pending') + const { start, reset, pause, getState } = motion({ + ...options, + frame: ({ value: newValue, done }) => { + value.value = newValue + + if (done) { + options.onFinished?.(value.value) + } + }, + onStateChange(newState) { + state.value = newState + }, + }) + + return { + value, + state, + start, + pause, + getState, + reset: () => { + value.value = options.from + reset() + }, + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 105db3c7921..a5a625b92b6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -274,8 +274,8 @@ importers: packages/varlet-shared: dependencies: rattail: - specifier: 1.0.0 - version: 1.0.0 + specifier: 1.0.12 + version: 1.0.12 devDependencies: '@types/node': specifier: ^18.7.18 @@ -4242,8 +4242,8 @@ packages: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} - rattail@1.0.0: - resolution: {integrity: sha512-ccYN21TuJWxP4/XI0BfYcc9NFE+wvNk5tT4J4oyYMG3vUryiW218NZdXAbtp6C1L4IZLjdCW/dFz1Ym4WwBGFA==} + rattail@1.0.12: + resolution: {integrity: sha512-LOWNF0ZXzJ9O7MOfkBZ6lGJYjqk/zrBLSJ9Du+XjNpf6COfyMwbkKnQEIArmpG8iSDX9DK0ww+yE3N35IYIuHw==} engines: {pnpm: '>=9.0'} rc@1.2.8: @@ -9302,7 +9302,7 @@ snapshots: range-parser@1.2.1: {} - rattail@1.0.0: + rattail@1.0.12: dependencies: mitt: 3.0.1