Created
December 30, 2023 01:20
-
-
Save halogenandtoast/35ac6897117087731c41599c71597180 to your computer and use it in GitHub Desktop.
Vue SFC showcasing animating between lists
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script setup lang="ts"> | |
| import gsap from 'gsap' | |
| import { ref } from 'vue' | |
| const CardStates = ["Above", "Below"] as const | |
| type CardState = typeof CardStates[number] | |
| interface Card { | |
| id: number | |
| image: string | |
| } | |
| const animationMatrix = ref([]) | |
| const nextId = ref(4) | |
| const above = ref([]) | |
| const below = ref([{ id: 1, color: "#FF0000" }, { id: 2, color: "#0000FF" }, { id: 3, color: "#00FF00" }]) | |
| function addCard() { | |
| const randomColor = Math.floor(Math.random() * 16777215).toString(16); | |
| const newCard = { id: nextId.value, color: `#${randomColor}` } | |
| nextId.value = nextId.value + 1 | |
| below.value = [newCard, ...below.value] | |
| } | |
| function onBeforeEnter(el) { | |
| el.style.opacity = 0 | |
| el.style.width = 0 | |
| } | |
| function onEnter(el, done) { | |
| const index = el.dataset.index | |
| const finalRect = el.getBoundingClientRect() | |
| if(!index) { | |
| const width = window.getComputedStyle(el).width | |
| gsap.to(el, { opacity: 1, width, onComplete: done }) | |
| return | |
| } | |
| const data = animationMatrix.value.find(([idx,]) => idx === index) | |
| animationMatrix.value = animationMatrix.value.filter(([idx,]) => idx !== index) | |
| if (!data) { | |
| gsap.to(el, { startAt: { height: 0, marginLeft: 50, marginTop: 70 }, ease: "power4.out", opacity: 1, width: 100, height: 140, marginLeft: 0, marginTop: 0, onComplete: done }) | |
| return | |
| } | |
| const [,rect] = data | |
| const startX = rect.left - finalRect.left | |
| const startY = rect.top - finalRect.top | |
| const c = el.cloneNode(true) | |
| c.style.position = "fixed" | |
| c.style.width = rect.width + "px" | |
| el.parentNode.insertBefore(c, el) | |
| const tl = gsap.timeline() | |
| tl. | |
| add("start"). | |
| to(el, { | |
| startAt: { opacity: 0, width: 0 }, | |
| width: rect.width, | |
| duration: 0.3 | |
| }, "start"). | |
| to(c, { | |
| startAt: { x: startX, y: startY, opacity: 1 }, | |
| y: 0, | |
| x: 0, | |
| onComplete: () => { | |
| c.remove() | |
| el.style.opacity = "1" | |
| done() | |
| }, | |
| duration: 0.3 | |
| }, "start") | |
| } | |
| function onLeave(el, done) { | |
| animationMatrix.value = [...animationMatrix.value, [el.dataset.index, el.getBoundingClientRect()]] | |
| gsap.to(el, { | |
| startAt: { opacity: 0 }, | |
| width: 0, | |
| margin: 0, | |
| onComplete: done, | |
| duration: 0.3 | |
| }) | |
| } | |
| function moveCard(card: string, moveTo: CardState) { | |
| switch (moveTo) { | |
| case 'Above': | |
| above.value = [card, ...above.value] | |
| below.value = below.value.filter((c) => c !== card) | |
| return | |
| case 'Below': | |
| below.value = [card, ...below.value] | |
| above.value = above.value.filter((c) => c !== card) | |
| return | |
| } | |
| } | |
| </script> | |
| <template> | |
| <div class="zones"> | |
| <div class="cards" key="above"> | |
| <transition-group @enter="onEnter" @leave="onLeave" @before-enter="onBeforeEnter"> | |
| <div :style="{ 'background-color': card.color }" @click.once="moveCard(card, 'Below')" class="card" v-for="card in above" :key="card.id" :data-index="card.id"></div> | |
| </transition-group> | |
| </div> | |
| <div class="cards" key="below"> | |
| <transition-group @enter="onEnter" @leave="onLeave" @before-enter="onBeforeEnter"> | |
| <div :style="{ 'background-color': card.color }" @click.once="moveCard(card, 'Above')" class="card" v-for="card in below" :key="card.id" :data-index="card.id"></div> | |
| </transition-group> | |
| </div> | |
| <button @click="addCard">Insert</button> | |
| </div> | |
| </template> | |
| <style scoped> | |
| .zones { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 100px; | |
| width: 100%; | |
| } | |
| .cards { | |
| display: flex; | |
| height: 160px; | |
| background: #AAA; | |
| width: 100%; | |
| padding: 10px; | |
| box-sizing: border-box; | |
| } | |
| .card { | |
| margin-right: 10px; | |
| box-sizing: border-box; | |
| width: 100px; | |
| height: 140px; | |
| border-radius: 4px; | |
| background: #AAA; | |
| } | |
| </style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment