Skip to content

Commit

Permalink
Merge pull request #196 from tmg0/refactor/directive
Browse files Browse the repository at this point in the history
Refactor: directive usage
  • Loading branch information
tmg0 authored Oct 11, 2024
2 parents 642a187 + edd0701 commit 2f4d837
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 110 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
},
"dependencies": {
"@vueuse/core": "^11.1.0",
"defu": "^6.1.4"
"defu": "^6.1.4",
"scule": "^1.3.0"
},
"devDependencies": {
"@antfu/eslint-config": "^3.7.3",
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/components/hero.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type { Transition } from '../types'
import { computed, defineComponent, type ExtractPropTypes, type PropType, ref } from 'vue'
import { useHero } from '../composables/use-hero'


const props = {
as: { type: String as PropType<keyof HTMLElementTagNameMap>, default: 'div' },
layoutId: { type: [String, Number], default: undefined },
transition: { type: Object as PropType<Partial<Transition>>, default: undefined },
ignore: { type: Array as PropType<string[]>, default: () => [] },
ignore: { type: Array as PropType<string[] | undefined>, default: () => [] },
}

export type HeroProps = ExtractPropTypes<typeof props>
Expand Down
43 changes: 26 additions & 17 deletions packages/core/src/composables/use-hero.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { defu } from 'defu'
import { computed, type MaybeRef, unref } from 'vue'
import { type HeroContext, useHeroContext } from '../composables/use-hero-context'

export interface UseHeroProps extends Omit<HeroProps, 'as'> {
export interface UseHeroProps extends Omit<HeroProps, 'as' | 'ignore'> {
ignore?: string[]
style?: Record<string, any>
onComplete?: () => void
}
Expand All @@ -29,26 +30,28 @@ export function useHero(target: MaybeRef<HTMLElement | SVGElement | undefined>,
const bounding: Record<string, number> = { x: 0, y: 0, width: 0, height: 0 }
const { layouts, props: ctxProps } = ctx ?? useHeroContext()
const { height, width, x, y, update } = useElementBounding(target)
const props = unref(options)
const style = computed(() => props?.style ?? {})
const transition = computed(() => defu(props.transition ?? {}, ctxProps.value.transition ?? {}, defaultTransition))
const props = computed(() => unref(options))
const style = computed(() => props.value?.style ?? {})
const transition = computed(() => defu(props.value.transition ?? {}, ctxProps.value.transition ?? {}, defaultTransition))

const previous = computed({
get() {
if (!props.layoutId)
if (!props.value.layoutId)
return {}
return layouts.value[props.layoutId] ?? {}
return layouts.value[props.value.layoutId] ?? {}
},
set(value) {
if (!props.layoutId)
if (!props.value.layoutId)
return
layouts.value[props.layoutId] = value
layouts.value[props.value.layoutId] = value
},
})

tryOnMounted(setupAnimation)
tryOnMounted(setup)
tryOnBeforeUnmount(clean)

function setupAnimation() {
function setup() {
update()
bounding.x = x.value + width.value / 2
bounding.y = y.value + height.value / 2
bounding.width = width.value
Expand All @@ -64,7 +67,7 @@ export function useHero(target: MaybeRef<HTMLElement | SVGElement | undefined>,

const _transition = {
...unref(transition),
onComplete: props.onComplete,
onComplete: props.value.onComplete,
}

const size = { width: bounding.width, height: bounding.height }
Expand All @@ -73,13 +76,13 @@ export function useHero(target: MaybeRef<HTMLElement | SVGElement | undefined>,
const initial = { ...unref(previous), x: _x, y: _y, scaleX: scale.x, scaleY: scale.y, ...size }
const enter = { ...style.value, x: 0, y: 0, scaleX: 1, scaleY: 1, ...size, transition: _transition }

motionInstance = useMotion(target, {
initial: omit(initial, props.ignore as any),
enter: omit(enter, props.ignore as any),
motionInstance = useMotion(unref(target), {
initial: omit(initial, props.value.ignore as any),
enter: omit(enter, props.value.ignore as any),
})
}

tryOnBeforeUnmount(() => {
function clean() {
update()
bounding.x = x.value + width.value / 2
bounding.y = y.value + height.value / 2
Expand All @@ -94,7 +97,13 @@ export function useHero(target: MaybeRef<HTMLElement | SVGElement | undefined>,
if (transform.scaleY)
_props.height = _props.height * (transform.scaleY as number ?? 1)
previous.value = _props
})
}

return { bounding, x, y }
return {
bounding,
x,
y,
setup,
clean,
}
}
102 changes: 14 additions & 88 deletions packages/core/src/directive/index.ts
Original file line number Diff line number Diff line change
@@ -1,101 +1,27 @@
import { useElementBounding } from '@vueuse/core'
import { useElementTransform, useMotion } from '@vueuse/motion'
import { defu } from 'defu'
import { computed, ref, unref } from 'vue'
import { defaultTransition, omit, type UseHeroProps } from '../composables/use-hero'
import { useHeroContext } from '../composables/use-hero-context'
import { camelCase } from 'scule'
import { ref } from 'vue'
import { useHero } from '../composables/use-hero'

export function directive() {
let motionInstance: any

let width = 0
let height = 0
let x = 0
let y = 0

const props = ref({})
const domRef = ref<HTMLElement | SVGElement>()
const bounding: Record<string, number> = { x: 0, y: 0, width: 0, height: 0 }
const { layouts, props: ctxProps } = useHeroContext()
const props = ref<Partial<UseHeroProps>>({})

const style = computed(() => props.value.style ?? {})
const transition = computed(() => defu(props.value.transition ?? {}, ctxProps.value.transition ?? {}, defaultTransition))

const previous = computed({
get() {
if (!props.value.layoutId)
return {}
return layouts.value[props.value.layoutId] ?? {}
},
set(value) {
if (!props.value.layoutId)
return
layouts.value[props.value.layoutId] = value
},
})

function register() {
bounding.x = x + width / 2
bounding.y = y + height / 2
bounding.width = width
bounding.height = height

let _y = 0
if (previous.value.y)
_y = previous.value.y - bounding.y

let _x = 0
if (previous.value.x)
_x = previous.value.x - bounding.x

const _transition = {
...unref(transition),
onComplete: props.value.onComplete,
}

const size = { width, height }
const scale = { x: previous.value.width / size.width, y: previous.value.height / size.height }

const initial = { ...unref(previous), x: _x, y: _y, scaleX: scale.x, scaleY: scale.y, ...size }
const enter = { ...style.value, x: 0, y: 0, scaleX: 1, scaleY: 1, ...size, transition: _transition }

motionInstance = useMotion(domRef, {
initial: omit(initial, props.value.ignore as any),
enter: omit(enter, props.value.ignore as any),
})
}

function updateBounding(dom: HTMLElement | SVGElement) {
const b = useElementBounding(dom)
width = b.width.value
height = b.height.value
x = b.x.value
y = b.y.value
}
const { setup, clean } = useHero(domRef, props)

return {
mounted(dom: HTMLElement | SVGElement, _: any, vnode: any) {
props.value = Object.entries(vnode.props).reduce((t: Record<string, any>, [k, v]) => {
const key = camelCase(k)
t[key] = v
return t
}, {})

domRef.value = dom
props.value = { ...vnode.props, layoutId: vnode.props['layout-id'] }
updateBounding(dom)
register()
setup()
},

beforeUnmount(dom: HTMLElement | SVGElement) {
updateBounding(dom)
bounding.x = x + width / 2
bounding.y = y + height / 2
const { transform } = useElementTransform(domRef)
bounding.x = bounding.x + (transform.x as number ?? 0)
bounding.y = bounding.y + (transform.y as number ?? 0)
bounding.z = bounding.z + (transform.x as number ?? 0)
const motionProperties = motionInstance ? motionInstance.motionProperties : style.value
const _props = { ...motionProperties, ...bounding }
if (transform.scaleX)
_props.width = _props.width * (transform.scaleX as number ?? 1)
if (transform.scaleY)
_props.height = _props.height * (transform.scaleY as number ?? 1)
previous.value = _props
beforeUnmount() {
clean()
},
}
}
4 changes: 2 additions & 2 deletions playgrounds/vite/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ function toggleSize() {
<HeroProvider :transition="{ type: 'spring' }">
<div class="bg-black/90 w-full min-h-screen text-white">
<div class="px-6 py-12 lg:py-24 lg:px-0 lg:w-[768px] mx-auto flex flex-col gap-6">
<div class="text-6xl font-semibold flex gap-4">
<div class="text-6xl font-bold flex gap-4">
<span>Hero</span>
<span class="bg-clip-text text-transparent bg-gradient-to-r from-purple-500 to-pink-500">Motion.</span>
</div>

<div class="text-lg leading-relaxed lg:text-2xl">
<div class="text-lg leading-relaxed lg:text-2xl font-extralight">
🌊 A shared layout animations for vue like framer motion, use layoutId prop and components will animate from one to another.
</div>

Expand Down
3 changes: 3 additions & 0 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 2f4d837

Please sign in to comment.