Search code examples
vue.jsvuejs3css-transitionsvue-transitionsihtmlimgelement

How do transitions work on Images in Vue JS 3?


I have been unable to apply a fade in transition to an image using Vue JS 3. The transition works fine with text, but the image just loads and renders as it would normally without a transition applied.

I have been trying to create an image loader, which will show a loading spinner until the image is loaded, then fade the loading spinner out and the image in once the image loads. Currently the loading spinner fades out as expected, but the image does not fade in.

Here is what I have in the image element:

<script setup>
  import FadeTransition from '../transitions/fade-transition.vue';
  import { reactive } from 'vue'

  const props = defineProps({
    imgUrl: String,
    route: String
  })

  let getURL = function () {
    return new URL(props.imgUrl, import.meta.url);
  }

  let show = reactive({showing: false});
  let showImg = function () {
    show.showing = true;
  }
</script>

<template>
  <div class="frame">
    <FadeTransition><div v-show="!show.showing" class="loader"></div></FadeTransition>
    <RouterLink :to="props.route"><FadeTransition><img v-show="show.showing" class="image" :src="getURL()" @load="showImg()"/></FadeTransition></RouterLink>
  </div>
</template>

<style scoped>
  .frame {
    width: 33.3333%;
    aspect-ratio: 1/1;
    margin: 0px;
    /*inline-block leaves space for descenders (e.g: letter y or g) so space between rows is wrong*/
    display: inline-flex;
    position: relative;
    padding: 6px;
  }

  .image {
    width: calc(100% - 12px);
    height: calc(100% - 12px);
    position: absolute;
  }

  .loader {
    border: 4px solid var(--grey);
    border-top: 4px solid var(--darkGrey);
    border-radius: 50%;
    width: 30px;
    height: 30px;
    animation: spin 5s linear infinite;
    position: absolute;
    left: calc(50% - 15px);
    top: calc(50% - 15px);
  }

  @keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
  }
</style>

and the FadeTransition component is:

<script>
</script>

<template>
  <Transition name="fade">
    <slot></slot> 
  </Transition>
</template>

<style>
  .fade-enter-active, 
  .fade-leave-active {
    transition: opacity 0.5s ease;
  }

  .fade-enter-from, 
  .fade-leave-to {
    opacity: 0;
  }
</style>

I have tried swapping the image for a text block, which works correctly, leading me to conclude that the problem I have is that the transition is not being correctly applied to the image.

How can I get the transition to apply correctly to the image?


Solution

  • Hmm, all I can tell you is that it seems to work perfectly in the playground

    If imgUrl is a prop, I would add a watcher that resets the component when the URL changes:

    watch(
      () => props.imgUrl,
      () => {
        show.showing = false
      },
      {immediate: true}
    )
    

    Otherwise, the loading animation and the fade will not be triggered when the URL changes after the initial load.

    The rest looks perfectly fine.

    Please let me know if I am missing something.