Skip to content

Commit

Permalink
perf: pause DOM patching while transitioning
Browse files Browse the repository at this point in the history
Signed-off-by: Fernando Fernández <ferferga@hotmail.com>
  • Loading branch information
ferferga committed Sep 6, 2024
1 parent 6c7263a commit 9dc36c0
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 6 deletions.
12 changes: 10 additions & 2 deletions frontend/src/components/lib/JTransition.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
:is="props.group ? TransitionGroup : Transition"
class="j-transition"
v-bind="$attrs"
:name="prefersNoMotion || disabled || isSlow ? undefined : `j-transition-${props.name}`">
:name="prefersNoMotion || disabled || isSlow ? undefined : `j-transition-${props.name}`"
@before-leave="leaving = true"
@after-leave="onNoLeave"
@leave-cancelled="onNoLeave">
<slot />
</component>
</template>

<script setup lang="ts">
import { Transition, TransitionGroup, type TransitionProps } from 'vue';
import { Transition, TransitionGroup, type TransitionProps, shallowRef } from 'vue';
import { prefersNoMotion, isSlow } from '@/store';
import { usePausableEffect } from '@/composables/use-pausable-effect';
interface Props {
name?: 'fade' | 'rotated-zoom' | 'slide-y' | 'slide-y-reverse' | 'slide-x' | 'slide-x-reverse';
Expand All @@ -27,6 +31,10 @@ interface Props {
export type JTransitionProps = TransitionProps & Props;
const props = withDefaults(defineProps<Props>(), { name: 'fade' });
const leaving = shallowRef(false);
const onNoLeave = () => leaving.value = false;
usePausableEffect(leaving);
</script>

<!-- TODO: Set scoped and remove .j-transition* prefix after: https://github.com/vuejs/core/issues/5148 -->
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/composables/page-title.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { computed, onBeforeUnmount, onMounted, shallowRef, toRef, toValue } from 'vue';
import { useTitle as _useTitle, watchImmediate, type ReadonlyRefOrGetter } from '@vueuse/core';
import { computed, onBeforeUnmount, onMounted, shallowRef, toRef, toValue, type MaybeRefOrGetter } from 'vue';
import { useTitle as _useTitle, watchImmediate } from '@vueuse/core';
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import { isNil } from '@/utils/validation';

Expand All @@ -21,7 +21,7 @@ _useTitle(_fullTitle);
*
* Value will be set to default (undefined) when the component consuming this composable is unmounted.
*/
export function usePageTitle(title?: ReadonlyRefOrGetter<Nullish<string>>) {
export function usePageTitle(title?: MaybeRefOrGetter<Nullish<string>>) {
onMounted(() => {
if (!isNil(title)) {
watchImmediate(toRef(title), val => _title.value = val ?? undefined);
Expand All @@ -36,6 +36,6 @@ export function usePageTitle(title?: ReadonlyRefOrGetter<Nullish<string>>) {
/**
* Same as useTitle, but is a shorthand for items only.
*/
export function useItemPageTitle(item: ReadonlyRefOrGetter<Nullish<BaseItemDto>>) {
export function useItemPageTitle(item: MaybeRefOrGetter<Nullish<BaseItemDto>>) {
usePageTitle(() => toValue(item)?.Name);
};
15 changes: 15 additions & 0 deletions frontend/src/composables/use-pausable-effect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { getCurrentScope, toRef, watch, type MaybeRefOrGetter } from 'vue';

/**
* When the passed argument is truthy, the effect scope of the current component will be paused
* until the argument becomes falsy again.
*
* This is useful for components that need to pause the DOM patching
*/
export function usePausableEffect(signal: MaybeRefOrGetter<boolean>) {
const scope = getCurrentScope();

if (scope) {
watch(toRef(signal), val => val ? scope.pause() : scope.resume(), { immediate: true, flush: 'sync' });
}
}

0 comments on commit 9dc36c0

Please sign in to comment.