vue.jsvuejs3vue-componentcomputed-properties

Vue 3 Using computed object to get image info works until I render the value in template, then becomes NaN


I have an app that displays image arrays. Before the image is displayed I need to adjust cropping based upon the image dimensions. When I load the image in a vue child component, I use a computed function (arrow or regular) and in the browser's inspect vue extension the object value are there and correct. But when I attempt to use a computed object's parameter, the result are zero or Nan. I used both the standard computed function and the arrow computed function. Both results are the same.

script setup>
import { computed } from 'vue';

const prop = defineProps({
    image: String,
    numberOfImages: {
        type: Number,
        default: 0,
    },
});

const getDimensions = computed(function () {
    get: {
        const newImg = new Image();
        newImg.src = image_path(prop.image);
        newImg.onload = function () {
        }
        // returning an object
        return {
            nHeight: newImg.naturalHeight,
            nWidth: newImg.naturalWidth,
            nAspect: newImg.naturalWidth / newImg.naturalHeight,
            nPortrait: (newImg.naturalWidth > newImg.naturalHeight) ? false : true,
        };
    };
});
</script>

<template>
       <div>
            <span v-if="getDimensions.nPortrait">This is a portrait Image<br /></span>
            <span v-else>This is a NOT portrait Image<br /></span>

            <span>This Image Height:
                {{ getDimensions.nHeight }}
            </span>
            <img class="w-full h-full
                        object-fill
                        object-center
                        rounded-lg"
            :src="image_path(prop.image)" alt=""
            >
        </div>
</template>

The code seems to work fine until I add the span tags. Then the results are zero or Nan

I first tried it as an arrow function but no difference in result. I guess being a newbie - I just don't have a clue whats happening here to cause the NaN


Solution

  • You are accessing the properties of newImg before it has loaded its src, so the dimensions are all empty. Note that the computed will update when prop.image changes, but not when newImage changes, as it is not a reactive reference.

    To resolve the issue, turn the computed into a watcher and use the onload to wait for the image:

    const dimensions = ref({})
    
    const loadImageDimensions = async (imageSrc) => {
      if (!imageSrc){
        return {}
      }
      const newImg = new Image()
      newImg.src = imageSrc
      await new Promise(resolve => newImg.onload = resolve)
      return {
        nHeight: newImg.naturalHeight,
        nWidth: newImg.naturalWidth,
        nAspect: newImg.naturalWidth / newImg.naturalHeight,
        nPortrait: (newImg.naturalWidth <= newImg.naturalHeight),
      }
    }
    watch(
     () => props.image,
     async () => dimensions.value = await loadImageDimensions(props.image),
     { immediate: true }
    )
    

    Note that you should not use a computed for dimensions since it is set through an async function, and computed properties should not have side-effects.

    Here it is in a playground