Skip to content

Commit eda9972

Browse files
authored
feat(useTransition): transitions and onStarted/onFinished callbacks (vueuse#268)
1 parent ca68052 commit eda9972

File tree

4 files changed

+111
-10
lines changed

4 files changed

+111
-10
lines changed

packages/core/useTransition/index.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,18 @@ useTransition(baseNumber, {
6464
transition: easeOutElastic,
6565
})
6666
```
67+
68+
To choreograph behavior around a transition, define `onStarted` or `onFinished` callbacks.
69+
70+
```js
71+
useTransition(baseNumber, {
72+
duration: 1000,
73+
transition: easeOutElastic,
74+
onStarted() {
75+
// called after the transition starts
76+
},
77+
onFinished() {
78+
// called after the transition ends
79+
},
80+
})
81+
```

packages/core/useTransition/index.test.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,41 @@ describe('useTransition', () => {
8686
expect(vm.transitionedValue).toBe(1)
8787
})
8888

89+
it('supports dynamic transitions', async() => {
90+
const linear = jest.fn(n => n)
91+
const easeInQuad = jest.fn(n => n * n)
92+
const vm = useSetup(() => {
93+
const baseValue = ref(0)
94+
const transition = ref(linear)
95+
96+
const transitionedValue = useTransition(baseValue, {
97+
duration: 100,
98+
transition,
99+
})
100+
101+
return {
102+
baseValue,
103+
transition,
104+
transitionedValue,
105+
}
106+
})
107+
108+
expect(linear).not.toHaveBeenCalled()
109+
expect(easeInQuad).not.toHaveBeenCalled()
110+
111+
vm.baseValue++
112+
await vm.$nextTick()
113+
114+
expect(linear).toHaveBeenCalled()
115+
expect(easeInQuad).not.toHaveBeenCalled()
116+
117+
vm.transition = easeInQuad
118+
vm.baseValue++
119+
await vm.$nextTick()
120+
121+
expect(easeInQuad).toHaveBeenCalled()
122+
})
123+
89124
it('support dynamic transition durations', async() => {
90125
const vm = useSetup(() => {
91126
const baseValue = ref(0)
@@ -119,4 +154,38 @@ describe('useTransition', () => {
119154
await promiseTimeout(100)
120155
expect(vm.transitionedValue).toBe(2)
121156
})
157+
158+
it('calls onStarted and onFinished callbacks', async() => {
159+
const onStarted = jest.fn()
160+
const onFinished = jest.fn()
161+
162+
const vm = useSetup(() => {
163+
const baseValue = ref(0)
164+
165+
const transitionedValue = useTransition(baseValue, {
166+
duration: 100,
167+
onFinished,
168+
onStarted,
169+
transition: n => n,
170+
})
171+
172+
return {
173+
baseValue,
174+
transitionedValue,
175+
}
176+
})
177+
178+
expect(onStarted).not.toHaveBeenCalled()
179+
expect(onFinished).not.toHaveBeenCalled()
180+
181+
vm.baseValue = 1
182+
await vm.$nextTick()
183+
184+
expect(onStarted).toHaveBeenCalled()
185+
expect(onFinished).not.toHaveBeenCalled()
186+
187+
await promiseTimeout(150)
188+
expect(onStarted.mock.calls.length).toBe(1)
189+
expect(onFinished.mock.calls.length).toBe(1)
190+
})
122191
})

packages/core/useTransition/index.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
11
import { useRafFn } from '../useRafFn'
2-
import { Ref, ref, unref, watch } from 'vue-demi'
3-
import { clamp, isFunction, MaybeRef } from '@vueuse/shared'
2+
import { computed, Ref, ref, unref, watch } from 'vue-demi'
3+
import { clamp, isFunction, MaybeRef, noop } from '@vueuse/shared'
44

5+
/**
6+
* Cubic bezier points
7+
*/
58
type CubicBezierPoints = [number, number, number, number]
69

7-
type EasingFunction = (x: number) => number
10+
/**
11+
* Easing function
12+
*/
13+
type EasingFunction = (n: number) => number
814

15+
/**
16+
* Transition options
17+
*/
918
interface TransitionOptions {
1019
duration?: MaybeRef<number>
11-
transition?: EasingFunction | CubicBezierPoints
20+
onFinished?: () => unknown
21+
onStarted?: () => unknown
22+
transition?: MaybeRef<EasingFunction | CubicBezierPoints>
1223
}
1324

1425
/**
@@ -81,14 +92,17 @@ export const TransitionPresets: Record<string, CubicBezierPoints> = {
8192
export function useTransition(source: Ref<number>, options: TransitionOptions = {}) {
8293
const {
8394
duration = 500,
95+
onFinished = noop,
96+
onStarted = noop,
8497
transition = (n: number) => n,
8598
} = options
8699

87100
const output = ref(source.value)
88101

89-
const getValue = isFunction<EasingFunction>(transition)
90-
? transition
91-
: createEasingFunction(transition)
102+
const currentTransition = computed(() => {
103+
const t = unref(transition)
104+
return isFunction(t) ? t : createEasingFunction(t)
105+
})
92106

93107
let currentDuration = 0
94108
let diff = 0
@@ -100,10 +114,12 @@ export function useTransition(source: Ref<number>, options: TransitionOptions =
100114
const now = Date.now()
101115
const progress = clamp(1 - ((endAt - now) / currentDuration), 0, 1)
102116

103-
output.value = startValue + (diff * getValue(progress))
117+
output.value = startValue + (diff * currentTransition.value(progress))
104118

105-
if (progress >= 1)
119+
if (progress >= 1) {
106120
pause()
121+
onFinished()
122+
}
107123
}, { immediate: false })
108124

109125
watch(source, () => {
@@ -116,6 +132,7 @@ export function useTransition(source: Ref<number>, options: TransitionOptions =
116132
endAt = startAt + currentDuration
117133

118134
resume()
135+
onStarted()
119136
})
120137

121138
return output

packages/shared/utils/is.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export const assert = (condition: boolean, ...infos: any[]) => {
55
}
66
const toString = Object.prototype.toString
77
export const isBoolean = (val: any): val is boolean => typeof val === 'boolean'
8-
export const isFunction = <T = Function> (val: any): val is T => typeof val === 'function'
8+
export const isFunction = <T extends Function> (val: any): val is T => typeof val === 'function'
99
export const isNumber = (val: any): val is number => typeof val === 'number'
1010
export const isString = (val: unknown): val is string => typeof val === 'string'
1111
export const isObject = (val: any): val is object =>

0 commit comments

Comments
 (0)