docs: add transition example
This commit is contained in:
@@ -47,8 +47,9 @@ watchEffect(
|
|||||||
|
|
||||||
const files: any = {}
|
const files: any = {}
|
||||||
const imports = exampleImports[props.example]
|
const imports = exampleImports[props.example]
|
||||||
|
const additionalImports = 'additionalImports' in imports ? imports.additionalImports : {}
|
||||||
|
|
||||||
for (const example of Object.keys(imports)) {
|
for (const example of Object.keys(imports).filter((i) => i !== 'additionalImports')) {
|
||||||
if (example.includes('css')) {
|
if (example.includes('css')) {
|
||||||
css += `${imports[example as keyof typeof imports]}`
|
css += `${imports[example as keyof typeof imports]}`
|
||||||
} else {
|
} else {
|
||||||
@@ -71,6 +72,7 @@ onMounted(async () => {
|
|||||||
store.setImportMap({
|
store.setImportMap({
|
||||||
imports: {
|
imports: {
|
||||||
'@braks/vue-flow': `${location.origin}/vue-flow.es.js`,
|
'@braks/vue-flow': `${location.origin}/vue-flow.es.js`,
|
||||||
|
...additionalImports,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { InteractionApp, InteractionCSS, InteractionControls } from './interacti
|
|||||||
import { MultiApp, MultiCSS, MultiFlow } from './multi'
|
import { MultiApp, MultiCSS, MultiFlow } from './multi'
|
||||||
import { HorizontalApp, HorizontalElements } from './horizontal'
|
import { HorizontalApp, HorizontalElements } from './horizontal'
|
||||||
import { TeleportApp, TeleportCSS, TeleportSidebar, TeleportableNode, TeleportableUseTransition } from './teleport'
|
import { TeleportApp, TeleportCSS, TeleportSidebar, TeleportableNode, TeleportableUseTransition } from './teleport'
|
||||||
|
import { TransitionApp, TransitionCSS, TransitionEdge } from './transition'
|
||||||
|
|
||||||
export const exampleImports = {
|
export const exampleImports = {
|
||||||
basic: {
|
basic: {
|
||||||
@@ -97,4 +98,14 @@ export const exampleImports = {
|
|||||||
'useTransition.js': TeleportableUseTransition,
|
'useTransition.js': TeleportableUseTransition,
|
||||||
'style.css': TeleportCSS,
|
'style.css': TeleportCSS,
|
||||||
},
|
},
|
||||||
|
transition: {
|
||||||
|
'App.vue': TransitionApp,
|
||||||
|
'TransitionEdge.vue': TransitionEdge,
|
||||||
|
'style.css': TransitionCSS,
|
||||||
|
'additionalImports': {
|
||||||
|
'@vueuse/core': 'https://cdn.jsdelivr.net/npm/@vueuse/core@9.3.0/index.mjs',
|
||||||
|
'@vueuse/shared': 'https://cdn.jsdelivr.net/npm/@vueuse/shared@9.3.0/index.mjs',
|
||||||
|
'vue-demi': 'https://cdn.jsdelivr.net/npm/vue-demi@0.13.11/lib/index.mjs',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Handle, Position } from '@braks/vue-flow'
|
import { Handle, Position } from '@braks/vue-flow'
|
||||||
import { useTransition } from './useTransition.js'
|
import { useTeleport } from './useTransition.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
id: {
|
id: {
|
||||||
@@ -9,7 +9,7 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const { animation, transition, teleport, onClick } = useTransition(props.id)
|
const { animation, transition, teleport, onClick } = useTeleport(props.id)
|
||||||
|
|
||||||
const changeAnimation = () => {
|
const changeAnimation = () => {
|
||||||
animation.value = animation.value === 'fade' ? 'shrink' : 'fade'
|
animation.value = animation.value === 'fade' ? 'shrink' : 'fade'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export { default as TeleportApp } from './App.vue?raw'
|
export { default as TeleportApp } from './App.vue?raw'
|
||||||
export { default as TeleportSidebar } from './Sidebar.vue?raw'
|
export { default as TeleportSidebar } from './Sidebar.vue?raw'
|
||||||
export { default as TeleportableNode } from './TeleportableNode.vue?raw'
|
export { default as TeleportableNode } from './TeleportableNode.vue?raw'
|
||||||
export { default as TeleportableUseTransition } from './useTransition.js?raw'
|
export { default as TeleportableUseTransition } from './useTeleport.js?raw'
|
||||||
export { default as TeleportCSS } from './style.css'
|
export { default as TeleportCSS } from './style.css'
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import { nextTick, ref } from 'vue'
|
|||||||
* Animations that resize a node need to call the `updateNodeDimensions` function from store to update node handle positions
|
* Animations that resize a node need to call the `updateNodeDimensions` function from store to update node handle positions
|
||||||
* Otherwise edges do not connect properly
|
* Otherwise edges do not connect properly
|
||||||
*/
|
*/
|
||||||
export const useTransition = (id) => {
|
export const useTeleport = (id) => {
|
||||||
const animation = ref('fade')
|
const animation = ref('fade')
|
||||||
const transition = ref(false)
|
const transition = ref(false)
|
||||||
const teleport = ref(null)
|
const teleport = ref(null)
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { ConnectionMode, Position, VueFlow, useVueFlow } from '@braks/vue-flow'
|
||||||
|
import TransitionEdge from './TransitionEdge.vue'
|
||||||
|
|
||||||
|
const elements = ref([
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
type: 'input',
|
||||||
|
label: 'Dblclick me',
|
||||||
|
position: { x: 0, y: 0 },
|
||||||
|
sourcePosition: Position.Right,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
type: 'output',
|
||||||
|
label: 'Dblclick me',
|
||||||
|
position: { x: 1000, y: 1000 },
|
||||||
|
targetPosition: Position.Left,
|
||||||
|
},
|
||||||
|
|
||||||
|
{ id: 'e1-2', type: 'custom', source: '1', target: '2', animated: true, style: { stroke: '#fff' } },
|
||||||
|
])
|
||||||
|
const bgColor = ref('#1A192B')
|
||||||
|
const connectionLineStyle = { stroke: '#fff' }
|
||||||
|
const snapGrid = [16, 16]
|
||||||
|
|
||||||
|
const { onPaneReady } = useVueFlow({
|
||||||
|
connectionMode: ConnectionMode.Loose,
|
||||||
|
connectionLineStyle,
|
||||||
|
snapToGrid: true,
|
||||||
|
snapGrid,
|
||||||
|
defaultZoom: 1.5,
|
||||||
|
})
|
||||||
|
|
||||||
|
onPaneReady((i) => {
|
||||||
|
i.fitView({
|
||||||
|
nodes: ['1'],
|
||||||
|
})
|
||||||
|
console.log('flow loaded:', i)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<VueFlow v-model="elements" :style="{ backgroundColor: bgColor }">
|
||||||
|
<template #edge-custom="props">
|
||||||
|
<TransitionEdge v-bind="props" />
|
||||||
|
</template>
|
||||||
|
</VueFlow>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
<script setup>
|
||||||
|
import { TransitionPresets, useDebounceFn, useTransition, watchDebounced } from '@vueuse/core'
|
||||||
|
import { getBezierPath, useVueFlow } from '@braks/vue-flow'
|
||||||
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
id: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
source: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
target: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
sourceX: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
sourceY: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
targetX: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
targetY: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
sourcePosition: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
targetPosition: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
markerEnd: {
|
||||||
|
type: String,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
style: {
|
||||||
|
type: Object,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const curve = ref()
|
||||||
|
|
||||||
|
const dot = ref()
|
||||||
|
|
||||||
|
const transform = ref({ x: 0, y: 0 })
|
||||||
|
|
||||||
|
const showDot = ref(false)
|
||||||
|
|
||||||
|
const { onNodeDoubleClick, fitBounds, fitView } = useVueFlow()
|
||||||
|
|
||||||
|
const edgePath = computed(() =>
|
||||||
|
getBezierPath({
|
||||||
|
sourceX: props.sourceX,
|
||||||
|
sourceY: props.sourceY,
|
||||||
|
sourcePosition: props.sourcePosition,
|
||||||
|
targetX: props.targetX,
|
||||||
|
targetY: props.targetY,
|
||||||
|
targetPosition: props.targetPosition,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const debouncedFitBounds = useDebounceFn(fitBounds, 1, { maxWait: 1 })
|
||||||
|
|
||||||
|
onNodeDoubleClick(({ node }) => {
|
||||||
|
const isSource = props.source === node.id
|
||||||
|
const isTarget = props.target === node.id
|
||||||
|
|
||||||
|
if (!showDot.value && (isSource || isTarget)) {
|
||||||
|
showDot.value = true
|
||||||
|
let totalLength = curve.value.getTotalLength()
|
||||||
|
const initialPos = ref(isSource ? 0 : totalLength)
|
||||||
|
let stopHandle
|
||||||
|
|
||||||
|
const output = useTransition(initialPos, {
|
||||||
|
duration: 4000,
|
||||||
|
transition: TransitionPresets.easeOutCubic,
|
||||||
|
onFinished: () => {
|
||||||
|
stopHandle?.()
|
||||||
|
showDot.value = false
|
||||||
|
fitView({
|
||||||
|
nodes: [isSource ? props.target : props.source],
|
||||||
|
duration: 500,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
transform.value = curve.value.getPointAtLength(output.value)
|
||||||
|
|
||||||
|
debouncedFitBounds(
|
||||||
|
{
|
||||||
|
width: 100,
|
||||||
|
height: 200,
|
||||||
|
x: transform.value.x - 100,
|
||||||
|
y: transform.value.y - 100,
|
||||||
|
},
|
||||||
|
{ duration: 500 },
|
||||||
|
)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
initialPos.value = isSource ? totalLength : 0
|
||||||
|
|
||||||
|
stopHandle = watchDebounced(
|
||||||
|
output,
|
||||||
|
(next) => {
|
||||||
|
if (!showDot.value) return
|
||||||
|
|
||||||
|
const nextLength = curve.value.getTotalLength()
|
||||||
|
|
||||||
|
if (totalLength !== nextLength) {
|
||||||
|
totalLength = nextLength
|
||||||
|
initialPos.value = isSource ? totalLength : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
transform.value = curve.value.getPointAtLength(next)
|
||||||
|
|
||||||
|
debouncedFitBounds({
|
||||||
|
width: 100,
|
||||||
|
height: 200,
|
||||||
|
x: transform.value.x - 100,
|
||||||
|
y: transform.value.y - 100,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ debounce: 1 },
|
||||||
|
)
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
inheritAttrs: false,
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<path :id="id" ref="curve" :style="style" class="vue-flow__edge-path" :d="edgePath" :marker-end="markerEnd" />
|
||||||
|
<Transition name="fade">
|
||||||
|
<circle
|
||||||
|
v-if="showDot"
|
||||||
|
ref="dot"
|
||||||
|
r="5"
|
||||||
|
cy="0"
|
||||||
|
cx="0"
|
||||||
|
:transform="`translate(${transform.x}, ${transform.y})`"
|
||||||
|
style="fill: #fdd023"
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as TransitionApp } from './App.vue?raw'
|
||||||
|
export { default as TransitionEdge } from './TransitionEdge.vue?raw'
|
||||||
|
export { default as TransitionCSS } from './style.css'
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 300ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
@@ -62,9 +62,9 @@ export default defineConfigWithTheme<DefaultTheme.Config>({
|
|||||||
Components({
|
Components({
|
||||||
dirs: [resolve(__dirname, './../../components')],
|
dirs: [resolve(__dirname, './../../components')],
|
||||||
deep: true,
|
deep: true,
|
||||||
// allow auto load markdown components under `./src/components/`
|
// allow to autoload markdown components under `./src/components/`
|
||||||
extensions: ['vue', 'md'],
|
extensions: ['vue', 'md'],
|
||||||
// allow auto import and register components used in markdown
|
// allow auto-import and register components used in markdown
|
||||||
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
|
include: [/\.vue$/, /\.vue\?vue/, /\.md$/],
|
||||||
dts: resolve(__dirname, '../components.d.ts'),
|
dts: resolve(__dirname, '../components.d.ts'),
|
||||||
resolvers: [IconsResolver()],
|
resolvers: [IconsResolver()],
|
||||||
@@ -173,6 +173,7 @@ export default defineConfigWithTheme<DefaultTheme.Config>({
|
|||||||
{ text: 'Horizontal Flow', link: '/examples/horizontal' },
|
{ text: 'Horizontal Flow', link: '/examples/horizontal' },
|
||||||
{ text: 'Interactions', link: '/examples/interaction' },
|
{ text: 'Interactions', link: '/examples/interaction' },
|
||||||
{ text: 'Teleport', link: '/examples/teleport' },
|
{ text: 'Teleport', link: '/examples/teleport' },
|
||||||
|
{ text: 'Transition', link: '/examples/transition' },
|
||||||
{ text: 'Multiple Flows', link: '/examples/multi' },
|
{ text: 'Multiple Flows', link: '/examples/multi' },
|
||||||
{ text: 'Pinia', link: '/examples/pinia' },
|
{ text: 'Pinia', link: '/examples/pinia' },
|
||||||
{ text: 'Stress', link: '/examples/stress' },
|
{ text: 'Stress', link: '/examples/stress' },
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
layout: page
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Transition
|
||||||
|
|
||||||
|
Vue Flow allows you to use transitions while zooming and panning around the Viewport.
|
||||||
|
This example shows you how to use these transitions to animate actions on your graph.
|
||||||
|
|
||||||
|
<div class="mt-6">
|
||||||
|
<ClientOnly>
|
||||||
|
<Suspense>
|
||||||
|
<Repl example="transition"></Repl>
|
||||||
|
</Suspense>
|
||||||
|
</ClientOnly>
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user