Fix card header expansion glitch on card collapse

This commit fixes an issue where the card's header would improperly
expand to full height during card collapse, leading to a less smooth
user experience. Previously, this was caused by the indiscriminate use
of `transition: all` in the `.card__expander`, which included unwanted
properties in the transition during collapse, such as height. This is
solved by using Vue transitions to apply transition only during
expansion.

Changes:

- Introduce a new Vue component, `CardExpandAnimation`:
  - Centralizes the animation process, applying the same animation to
    both the card and its arrow for consistency.
  - Resolves the glitch by adjusting classes exclusively during the
    enter animation phase, avoiding unintended side effects during leave
    animation phase.
  - Adopts a Vue-idiomatic approach for transition management, improving
    code readability and maintainability.
  - Improves separation of concerns by isolating animation logic from
    the component's core functionality, facilitating easier updates or
    replacements.
- Remove unnecessary transitions to enhance code simplicity and
  performance:
  - Remove `transition: all` on `.card__expander`, which was identified
    as the cause of the issue.
  - Remove unnecessary `transition: all` on `.card`.
  - Adjust transitions to specifically target and affect the transform
    property (instead of `all`) to optimize animation behavior and
    eliminate potential side-effects.

These changes not only fix the issue at hand but also contribute to a
more maintainable and performant codebase by clarifying animation logic
and reducing unnecessary CSS transitions.
This commit is contained in:
undergroundwires
2024-04-03 09:51:09 +02:00
parent bc7e1faa1c
commit 5d940b57ef
4 changed files with 52 additions and 40 deletions

View File

@@ -8,7 +8,7 @@
import { defineComponent } from 'vue';
export default defineComponent({
name: 'InstructionSteps', // Define component name for empty component for Vue build and ESLint compatibility.
// Empty component for ESLint compatibility, workaround for https://github.com/vuejs/vue-eslint-parser/issues/125.
});
</script>

View File

@@ -8,6 +8,6 @@
import { defineComponent } from 'vue';
export default defineComponent({
name: 'InstructionSteps', // Define component name for empty component for Vue build and ESLint compatibility.
// Empty component for ESLint compatibility, workaround for https://github.com/vuejs/vue-eslint-parser/issues/125.
});
</script>

View File

@@ -0,0 +1,28 @@
<template>
<transition name="card-expand-collapse-transition">
<slot />
</transition>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
// Empty component for ESLint compatibility, workaround for https://github.com/vuejs/vue-eslint-parser/issues/125.
});
</script>
<style scoped lang="scss">
@use "@/presentation/assets/styles/main" as *;
.card-expand-collapse-transition-enter-active {
transition:
opacity 0.3s ease-in-out,
max-height 0.3s ease-in-out;
}
.card-expand-collapse-transition-enter-from {
opacity: 0; // Fade-in
max-height: 0; // Expand
}
</style>

View File

@@ -3,7 +3,6 @@
ref="cardElement"
class="card"
:class="{
'is-collapsed': !isExpanded,
'is-inactive': activeCategoryId && activeCategoryId !== categoryId,
'is-expanded': isExpanded,
}"
@@ -29,20 +28,26 @@
:category-id="categoryId"
/>
</div>
<div class="card__expander" @click.stop>
<div class="card__expander__close-button">
<FlatButton
icon="xmark"
@click="collapse()"
/>
<CardExpandTransition>
<div
v-show="isExpanded"
class="card__expander"
@click.stop
>
<div class="card__expander__close-button">
<FlatButton
icon="xmark"
@click="collapse()"
/>
</div>
<div class="card__expander__content">
<ScriptsTree
:category-id="categoryId"
:has-top-padding="false"
/>
</div>
</div>
<div class="card__expander__content">
<ScriptsTree
:category-id="categoryId"
:has-top-padding="false"
/>
</div>
</div>
</CardExpandTransition>
</div>
</template>
@@ -56,6 +61,7 @@ import { injectKey } from '@/presentation/injectionSymbols';
import ScriptsTree from '@/presentation/components/Scripts/View/Tree/ScriptsTree.vue';
import { sleep } from '@/infrastructure/Threading/AsyncSleep';
import CardSelectionIndicator from './CardSelectionIndicator.vue';
import CardExpandTransition from './CardExpandTransition.vue';
export default defineComponent({
components: {
@@ -63,6 +69,7 @@ export default defineComponent({
AppIcon,
CardSelectionIndicator,
FlatButton,
CardExpandTransition,
},
props: {
categoryId: {
@@ -134,8 +141,6 @@ $expanded-margin-top : 30px;
$card-horizontal-gap : $card-gap;
.card {
transition: all 0.2s ease-in-out;
&__inner {
padding-top: $card-inner-padding;
padding-right: $card-inner-padding;
@@ -149,7 +154,7 @@ $card-horizontal-gap : $card-gap;
width: 100%;
text-transform: uppercase;
text-align: center;
transition: all 0.2s ease-in-out;
transition: transform 0.2s ease-in-out;
display:flex;
flex-direction: column;
@@ -160,9 +165,6 @@ $card-horizontal-gap : $card-gap;
color: $color-on-secondary;
transform: scale(1.05);
}
&:after {
transition: all 0.3s ease-in-out;
}
.card__inner__title {
display: flex;
flex-direction: column;
@@ -185,7 +187,6 @@ $card-horizontal-gap : $card-gap;
}
}
.card__expander {
transition: all 0.2s ease-in-out;
position: relative;
background-color: $color-primary-darker;
color: $color-on-primary;
@@ -214,22 +215,6 @@ $card-horizontal-gap : $card-gap;
}
}
&.is-collapsed {
.card__inner {
&:after {
content: "";
opacity: 0;
}
}
.card__expander {
max-height: 0;
min-height: 0;
overflow: hidden;
opacity: 0;
}
}
&.is-expanded {
.card__inner {
height: auto;
@@ -249,7 +234,6 @@ $card-horizontal-gap : $card-gap;
.card__expander {
margin-top: $expanded-margin-top;
opacity: 1;
}
@include hover-or-touch {