<!-- * 变形 动画 * https://quasar.dev/vue-directives/morph#morph-api --> <script setup lang="ts"> import { onMounted, onBeforeUnmount, reactive } from 'vue'; // import { useMessage } from 'src/common/hooks'; // const { info } = useMessage(); const step = 110; const padding = 10; const state = reactive({ top: 10, left: 10, }); onMounted(() => { document.addEventListener('keydown', onKeydown); }); function onKeydown(e: any) { // 方向键--上 if (e.keyCode == 38) { if (state.top <= 10) { state.top = 10; } else { state.top -= padding + step; } } // 方向键--下 if (e.keyCode == 40) { if (state.top >= 250) { state.top = 250; } else { state.top += padding + step; } } // 方向键--左 if (e.keyCode == 37) { if (state.left <= 10) { state.left = 10; } else { state.left -= padding + step; } } // 方向键--右 if (e.keyCode == 39) { if (state.left >= 250) { state.left = 250; } else { state.left += padding + step; } } } onBeforeUnmount(() => { document.removeEventListener('keydown', onKeydown); }); </script> <template> <div class="fit relative-position"> <div class="btns"> <q-btn color="primary" label="变形" /> </div> <div class="relative-position" style="height: 400px"> <div class="bg-box"> <div class="bg-item"></div> <div class="bg-item"></div> <div class="bg-item"></div> <div class="bg-item"></div> <div class="bg-item"></div> <div class="bg-item"></div> <div class="bg-item"></div> <div class="bg-item"></div> <div class="bg-item"></div> </div> <div class="item" :style="{ transform: `translate(${state.left}px, ${state.top}px)` }" > <span>3</span> </div> </div> <div class="example2"> <div class="box"> <div class="balance-box"></div> </div> </div> </div> </template> <style lang="scss" scoped> .btns { margin-bottom: $padding-md; } .bg-box { position: absolute; top: 0; left: 0; border-radius: 10px; display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 1fr); grid-row-gap: 10px; grid-column-gap: 10px; width: 370px; padding: 10px; background-color: #a39391; .bg-item { width: 110px; height: 110px; background-color: #ecd6c7; border-radius: 10px; } } .item { position: absolute; top: 0; left: 0; transition: all 0.35s; width: 110px; height: 110px; background-color: #e79686; border-radius: 10px; display: flex; justify-content: center; align-items: center; color: white; font-size: 40px; } .example2 { .box { width: 100%; height: 100px; position: relative; box-sizing: content-box; display: flex; justify-content: center; align-items: center; } .balance-box { height: 24px; width: 100%; border-radius: 24px; background-color: #d3e397; transition: all 0.5s; animation-name: balance-box; animation-duration: 4s; animation-iteration-count: infinite; // 使动画永远持续下去 animation-timing-function: cubic-bezier(0.26, 0.68, 0.26, 0.75); } } @keyframes balance-box { 0% { transform: rotate(-5deg); } 50% { transform: rotate(5deg); } 100% { transform: rotate(-5deg); } } </style>