Skip to content

Commit

Permalink
feat: support motion, useMotion
Browse files Browse the repository at this point in the history
  • Loading branch information
haoziqaq committed Nov 19, 2024
1 parent 81eee1e commit 312968e
Show file tree
Hide file tree
Showing 20 changed files with 302 additions and 21 deletions.
2 changes: 1 addition & 1 deletion packages/varlet-shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 2 additions & 1 deletion packages/varlet-shared/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './is.js'
export * from './is'
export * from './motion'
export * from 'rattail'
110 changes: 110 additions & 0 deletions packages/varlet-shared/src/motion.ts
Original file line number Diff line number Diff line change
@@ -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,
}
}
51 changes: 51 additions & 0 deletions packages/varlet-ui/src/count-to/CountTo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<template>
<div :class="n()">
<div :style="{ transform: `translateX(${value}px)` }">{{ value }}: {{ state }}</div>
<button @click="start">Start</button>
<button @click="pause">Pause</button>
<button @click="reset">Reset</button>
</div>
</template>

<script lang="ts">
import { computed, defineComponent } from 'vue'
import { props } from './props'
import { createNamespace } from '../utils/components'
import { useMotion } from '@varlet/use'
import { floor } from '@varlet/shared'
const { name, n } = createNamespace('count-to')
export default defineComponent({
name,
props,
setup() {
const {
value: _value,
state,
start,
pause,
reset,
} = useMotion({
from: 0,
to: 200,
duration: 2000,
})
const value = computed(() => floor(_value.value))
return {
n,
start,
pause,
reset,
state,
value,
}
},
})
</script>

<style lang="less">
@import './countTo';
</style>
8 changes: 8 additions & 0 deletions packages/varlet-ui/src/count-to/__tests__/index.spec.js
Original file line number Diff line number Diff line change
@@ -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()
})
2 changes: 2 additions & 0 deletions packages/varlet-ui/src/count-to/countTo.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.var-count-to {
}
Empty file.
Empty file.
11 changes: 11 additions & 0 deletions packages/varlet-ui/src/count-to/example/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup>
import { watchLang, AppType } from '@varlet/cli/client'
import { use } from './locale'
watchLang(use)
</script>

<template>
<app-type>Mobile phone example code</app-type>
<var-count-to />
</template>
3 changes: 3 additions & 0 deletions packages/varlet-ui/src/count-to/example/locale/en-US.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
// Example locale
}
17 changes: 17 additions & 0 deletions packages/varlet-ui/src/count-to/example/locale/index.ts
Original file line number Diff line number Diff line change
@@ -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 }
3 changes: 3 additions & 0 deletions packages/varlet-ui/src/count-to/example/locale/zh-CN.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
// Example locale
}
12 changes: 12 additions & 0 deletions packages/varlet-ui/src/count-to/index.ts
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions packages/varlet-ui/src/count-to/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const props = {}
2 changes: 1 addition & 1 deletion packages/varlet-ui/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
13 changes: 13 additions & 0 deletions packages/varlet-ui/types/countTo.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { VarComponent, BasicAttributes, SetPropsDefaults } from './varComponent'

export declare const countToProps: Record<keyof CountToProps, any>

export interface CountToProps extends BasicAttributes {}

export class CountTo extends VarComponent {
static setPropsDefaults: SetPropsDefaults<CountToProps>

$props: CountToProps
}

export class _CountToComponent extends CountTo {}
8 changes: 8 additions & 0 deletions packages/varlet-ui/varlet.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 骨架屏',
Expand Down
27 changes: 14 additions & 13 deletions packages/varlet-use/src/index.ts
Original file line number Diff line number Diff line change
@@ -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'
40 changes: 40 additions & 0 deletions packages/varlet-use/src/useMotion.ts
Original file line number Diff line number Diff line change
@@ -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<MotionState>('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()
},
}
}
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 312968e

Please sign in to comment.