Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support SVGPath #243

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions playgrounds/vite/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Delay from './demos/Delay.vue'
import Editor from './demos/Editor.vue'
import Transitions from './demos/Transitions.vue'
import Sandbox from './demos/Sandbox.vue'
import SVGPath from './demos/SVGPath.vue'

const sandbox = false
</script>
Expand Down Expand Up @@ -33,6 +34,12 @@ const sandbox = false
</h1>

<Transitions class="mt-6 mb-6" />

<h1 class="flex items-center justify-center text-3xl font-bold">
SVGPath
</h1>

<SVGPath class="mt-6 mb-6" />
</template>

<Sandbox v-else />
Expand Down
133 changes: 133 additions & 0 deletions playgrounds/vite/src/demos/SVGPath.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<script setup="props" lang="ts">
import { computed, onMounted, ref } from 'vue'
import {
MotionComponent as Motion,
useElementStyle,
useMotion,
} from '@vueuse/motion'
import DemoBox from '../components/DemoBox.vue'
import svgpath from '../examples/svgpath'

const codeText = computed(() => svgpath())
const lines = ref([
{ x1: 5, y1: 25, x2: 11, y2: 9 },
{ x1: 7, y1: 27, x2: 13, y2: 11 },
// { x1: 9, y1: 29, x2: 15, y2: 23 },
])

const varients = ref([
{
pathLength: 1,
pathSpacing: 1,
pathOffset: 1,
transition: {
duration: 300,
},
},

{
pathLength: 1,
pathSpacing: 1,
pathOffset: [1, 2],
transition: {
duration: 1000,
},
},
])

const target = ref<HTMLElement>()
// @ts-expect-error MaybeRef<PermissiveTarget>
const instance = useMotion(target, {
initial: varients.value[0],
enter: varients.value[1],
})

const targetSvg = ref<SVGElement>()
// @ts-expect-error MaybeRef<PermissiveTarget>
const { style } = useElementStyle(targetSvg)
onMounted(() => {
style.pathLength = 2
style.pathOffset = '3.5'
style.pathSpacing = 2
})

function enter() {
const varient = varients.value[1]
instance.apply(varient)
}
function leave() {
const varient = varients.value[0]
instance.apply(varient)
}
</script>

<template>
<DemoBox :text="codeText">
<template #demoElement>
<svg
xmlns="http://www.w3.org/2000/svg"
width="500"
height="500"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
pathLength="1"
stroke-dashoffset="2px"
stroke-dasharray="1px 1px"
@mouseenter="enter"
@mouseleave="leave"
>
<Motion
is="line"
ref="target"
:x1="lines[0].x1 + 3"
:x2="lines[0].x2 + 3"
:y1="lines[0].y1 - 3"
:y2="lines[0].y2 - 3"
/>

<line
v-motion
stroke="yellow"
:x1="lines[0].x1 + 5"
:x2="lines[0].x2 + 5"
:y1="lines[0].y1 - 3"
:y2="lines[0].y2 - 3"
:initial="{
pathLength: 1,
pathSpacing: 1,
pathOffset: 2,
transition: {
duration: 300,
},
}"
:enter="{
pathLength: 1,
pathSpacing: 1,
pathOffset: 2,
transition: {
duration: 300,
},
}"
:hovered="{
pathLength: 1,
pathSpacing: 1,
pathOffset: [2, 1],
}"
/>

<line
ref="targetSvg"
stroke="green"
:x1="lines[1].x1 + 5"
:x2="lines[1].x2 + 5"
:y1="lines[1].y1 - 5"
:y2="lines[1].y2 - 5"
/>
</svg>
</template>
</DemoBox>
</template>
132 changes: 132 additions & 0 deletions playgrounds/vite/src/examples/svgpath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
export default () => `
<script setup="props" lang="ts">
import { computed, onMounted, ref } from 'vue'
import {
MotionComponent as Motion,
useElementStyle,
useMotion,
} from '@vueuse/motion'

const lines = ref([
{ x1: 5, y1: 25, x2: 11, y2: 9 },
{ x1: 7, y1: 27, x2: 13, y2: 11 },
// { x1: 9, y1: 29, x2: 15, y2: 23 },
])

const varients = ref([
{
pathLength: 1,
pathSpacing: 1,
pathOffset: 1,
transition: {
duration: 300,
},
},

{
pathLength: 1,
pathSpacing: 1,
pathOffset: [1, 2],
transition: {
duration: 1000,
},
},
])

const target = ref<HTMLElement>()
// @ts-expect-error MaybeRef<PermissiveTarget>
const instance = useMotion(target, {
initial: varients.value[0],
enter: varients.value[1],
})

const targetSvg = ref<SVGElement>()
// @ts-expect-error MaybeRef<PermissiveTarget>
const { style } = useElementStyle(targetSvg)
onMounted(() => {
style.pathLength = 2
style.pathOffset = '3.5'
style.pathSpacing = 2
})

function enter() {
const varient = varients.value[1]
instance.apply(varient)
}
function leave() {
const varient = varients.value[0]
instance.apply(varient)
}
</script>

<template>
<DemoBox :text="codeText">
<template #demoElement>
<svg
xmlns="http://www.w3.org/2000/svg"
width="500"
height="500"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
pathLength="1"
stroke-dashoffset="2px"
stroke-dasharray="1px 1px"
@mouseenter="enter"
@mouseleave="leave"
>
<Motion
is="line"
ref="target"
:x1="lines[0].x1 + 3"
:x2="lines[0].x2 + 3"
:y1="lines[0].y1 - 3"
:y2="lines[0].y2 - 3"
/>

<line
v-motion
stroke="yellow"
:x1="lines[0].x1 + 5"
:x2="lines[0].x2 + 5"
:y1="lines[0].y1 - 3"
:y2="lines[0].y2 - 3"
:initial="{
pathLength: 1,
pathSpacing: 1,
pathOffset: 2,
transition: {
duration: 300,
},
}"
:enter="{
pathLength: 1,
pathSpacing: 1,
pathOffset: 2,
transition: {
duration: 300,
},
}"
:hovered="{
pathLength: 1,
pathSpacing: 1,
pathOffset: [2, 1],
}"
/>

<line
ref="targetSvg"
stroke="green"
:x1="lines[1].x1 + 5"
:x2="lines[1].x2 + 5"
:y1="lines[1].y1 - 5"
:y2="lines[1].y2 - 5"
/>
</svg>
</template>
</DemoBox>
</template>
`
11 changes: 5 additions & 6 deletions src/reactiveStyle.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import type { Ref } from 'vue'
import { reactive, ref, watch } from 'vue'
import type { StyleProperties } from './types'
import type { SVGPathProperties, StyleProperties } from './types'
import { getValueAsType, getValueType } from './utils/style'

/**
* Reactive style object implementing all native CSS properties.
*
* @param props
*/
export function reactiveStyle(props: StyleProperties = {}) {
export function reactiveStyle(props: StyleProperties | SVGPathProperties = {}) {
// Reactive StyleProperties object
const state = reactive<StyleProperties>({
const state = reactive<StyleProperties | SVGPathProperties>({
...props,
})

const style = ref({}) as Ref<StyleProperties>
const style = ref({}) as Ref<StyleProperties | SVGPathProperties>

// Reactive DOM Element compatible `style` object bound to state
watch(
state,
() => {
// Init result object
const result: StyleProperties = {}
const result: StyleProperties | SVGPathProperties = {}

for (const [key, value] of Object.entries(state)) {
// Get value type for key
Expand Down
31 changes: 27 additions & 4 deletions src/useElementStyle.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { MaybeRef } from '@vueuse/core'
import { watch } from 'vue'
import { reactiveStyle } from './reactiveStyle'
import type { MotionTarget, PermissiveTarget, StyleProperties } from './types'
import type { MotionTarget, PermissiveTarget, SVGPathProperties, StyleProperties } from './types'
import { usePermissiveTarget } from './usePermissiveTarget'
import { valueTypes } from './utils/style'
import { getSVGPath, isSVGElement, isSVGPathProp, setSVGPath, valueTypes } from './utils/style'
import { isTransformOriginProp, isTransformProp } from './utils/transform'

/**
Expand All @@ -13,7 +13,7 @@ import { isTransformOriginProp, isTransformProp } from './utils/transform'
*/
export function useElementStyle(target: MaybeRef<PermissiveTarget>, onInit?: (initData: Partial<StyleProperties>) => void) {
// Transform cache available before the element is mounted
let _cache: StyleProperties | undefined
let _cache: StyleProperties | SVGPathProperties | undefined
// Local target cache as we need to resolve the element from PermissiveTarget
let _target: MotionTarget
// Create a reactive style object
Expand All @@ -22,10 +22,19 @@ export function useElementStyle(target: MaybeRef<PermissiveTarget>, onInit?: (in
usePermissiveTarget(target, (el) => {
_target = el

if (isSVGElement(_target)) {
const { pathLength, pathSpacing, pathOffset } = getSVGPath(_target as SVGElement)
if (pathLength !== undefined) {
(state as SVGPathProperties).pathLength = pathLength;
(state as SVGPathProperties).pathSpacing = pathSpacing;
(state as SVGPathProperties).pathOffset = pathOffset
}
}

// Loop on style keys
for (const key of Object.keys(valueTypes)) {
// @ts-expect-error - Fix errors later for typescript 5
if (el.style[key] === null || el.style[key] === '' || isTransformProp(key) || isTransformOriginProp(key))
if (el.style[key] === null || el.style[key] === '' || isTransformProp(key) || isTransformOriginProp(key) || isSVGPathProp(key))
continue

// Append a defined key to the local StyleProperties state object
Expand All @@ -35,6 +44,13 @@ export function useElementStyle(target: MaybeRef<PermissiveTarget>, onInit?: (in

// If cache is present, init the target with the current cached value
if (_cache) {
if (isSVGElement(_target)) {
const { pathLength, pathOffset, pathSpacing } = _cache as SVGPathProperties
if (pathLength !== undefined) {
setSVGPath((_target as SVGElement), pathLength, pathSpacing, pathOffset)
}
}

// @ts-expect-error - Fix errors later for typescript 5
Object.entries(_cache).forEach(([key, value]) => (el.style[key] = value))
}
Expand All @@ -53,6 +69,13 @@ export function useElementStyle(target: MaybeRef<PermissiveTarget>, onInit?: (in
return
}

if (isSVGElement(_target)) {
const { pathLength, pathOffset, pathSpacing } = newVal as SVGPathProperties
if (pathLength !== undefined) {
setSVGPath((_target as SVGElement), pathLength, pathSpacing, pathOffset)
}
}

// Append the state object to the target style properties
// @ts-expect-error - Fix errors later for typescript 5
for (const key in newVal) _target.style[key] = newVal[key]
Expand Down
Loading
Loading