2

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?

jban28
  • 23
  • 5
  • This works when I try it out, I assume it has to be your `showImg()`. Can you add that code to your question? – Moritz Ringler May 10 '23 at 17:19
  • @MoritzRingler thanks, I have included the whole SFC now. If I swapped the image for text it worked OK, just seems to be images that don't work. – jban28 May 10 '23 at 18:27

1 Answers1

1

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.

Moritz Ringler
  • 9,772
  • 9
  • 21
  • 34
  • That's odd, works there for me too. I guess the only difference is that my image and transition are also wrapped in a router link, but I don't see what difference that would make? And I am rendering the whole component using v-for for a gallery of many images, but again I don't see why that would matter because each component should be independent. Thanks for trying! – jban28 May 10 '23 at 18:51
  • 1
    Hmm, if you remove the RouterLink, does it work? Pretty sure it's not that. My bet is on the missing watcher. I have added it to the playground link, maybe it helps – Moritz Ringler May 10 '23 at 19:08
  • The watcher fixed it! Thanks! Can you explain why this is the case? I am fairly new to Vue and haven't really come across watchers before. I assumed that each time a component is used it is independent of any other instances of the component. – jban28 May 10 '23 at 19:30
  • 1
    First off, great that it works! When you change a prop (like `imgUrl`), the component is not re-initialized, so without the watcher, you now have a new URL, but `show.showing` is still true from the last URL, so the img is shown immediately, without the fade and the load. The watcher detects the change and resets `show.showing`, so it works again. In general, whenever a prop triggers a change in a component, you need to reset it with a watcher, unless you use a new component (which is usually not what you want to do). – Moritz Ringler May 10 '23 at 19:51
  • Btw. whenever an answer helped you, please don't forget to upvote it and/or mark it as the accepted answer - points points points lol, it's how the site works – Moritz Ringler May 10 '23 at 19:52
  • Thanks, marked the answer as accepted. Won't let me upvote as I'm new but I'll come back to it once it lets me – jban28 May 10 '23 at 20:21