Extract the Height of a Lazy Loaded Image Using Gridsome and Sanity

7 replies
Last updated: Jun 2, 2021
I use Gridsome to build static pages with Sanity as the source. In my building used https://www.sanity.io/docs/image-url to load images. I also use vue-lazyload.
I would like a placeholder the same size as the picture before the picture is finished loading. The width is usually 100% of the container. But the height of the image is dynamic.

Is there any possibility to extract the height of the image before the image is loaded?
AI Update

Yes! Sanity automatically stores image dimensions in the image metadata, which you can query to calculate the height before the image loads. This is perfect for creating properly-sized placeholders with vue-lazyload.

How Sanity Image Metadata Works

When you upload an image to Sanity, it automatically extracts metadata including dimensions. The dimensions field (which includes width, height, and aspectRatio) is always included and cannot be disabled—it's available by default for every image.

Querying Image Dimensions

In your Gridsome source configuration, you can access image metadata through GraphQL. If you're using gridsome-source-sanity, the plugin should expose the metadata fields. Your GraphQL query would look like:

query {
  allSanityPost {
    edges {
      node {
        image {
          asset {
            url
            metadata {
              dimensions {
                width
                height
                aspectRatio
              }
            }
          }
        }
      }
    }
  }
}

Creating Dynamic Height Placeholders

With the aspect ratio, you can calculate the correct placeholder height using the padding-bottom trick. Here's a Vue component example that works with vue-lazyload:

<template>
  <div 
    class="image-container" 
    :style="{ paddingBottom: `${(1 / aspectRatio) * 100}%` }"
  >
    <img 
      v-lazy="imageUrl" 
      :alt="alt"
      class="lazy-image"
    />
  </div>
</template>

<script>
export default {
  props: {
    image: Object,
    alt: String
  },
  computed: {
    aspectRatio() {
      return this.image.asset.metadata.dimensions.aspectRatio;
    },
    imageUrl() {
      return this.image.asset.url;
    }
  }
}
</script>

<style scoped>
.image-container {
  position: relative;
  width: 100%;
  overflow: hidden;
}
.lazy-image {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
</style>

This creates a container that maintains the correct aspect ratio before the image loads, preventing layout shift.

Alternative: Using Width and Height Directly

If you prefer to set explicit dimensions, you can calculate the height based on your container width:

<template>
  <div class="image-wrapper">
    <div :style="placeholderStyle">
      <img v-lazy="imageUrl" :alt="alt" />
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      containerWidth: 0
    }
  },
  computed: {
    placeholderStyle() {
      const { width, height } = this.image.asset.metadata.dimensions;
      const calculatedHeight = (height / width) * this.containerWidth;
      return {
        height: `${calculatedHeight}px`
      };
    }
  },
  mounted() {
    this.containerWidth = this.$el.offsetWidth;
  }
}
</script>

Bonus: LQIP for Better Placeholders

Sanity also generates LQIP (Low Quality Image Placeholder) data automatically. You can query this alongside dimensions for an even smoother loading experience:

image {
  asset {
    url
    metadata {
      dimensions {
        aspectRatio
      }
      lqip
    }
  }
}

Then use the lqip as a background image while the full image loads:

<div 
  class="image-container" 
  :style="{ 
    paddingBottom: `${(1 / aspectRatio) * 100}%`,
    backgroundImage: `url(${image.asset.metadata.lqip})`,
    backgroundSize: 'cover'
  }"
>
  <img v-lazy="imageUrl" :alt="alt" />
</div>

This gives users immediate visual feedback with a blurred preview instead of just an empty box, creating the smoothest possible loading experience!

If you know the image URL then sanity images always have the image size in the url. e.g.
<https://cdn.sanity.io/images/{projectid}/{dataset}/{hash}-6720x4480.jpg>
Great, I have not noticed that the size of the image is part of the image name. It can solve my challenge with reflow.
Thanks!
you can also GROQ the metadata like image width/height/aspectRatio https://www.sanity.io/docs/image-type#df3f768ce073

"metadata": {
    "dimensions": {
      "aspectRatio": 1.000733137829912,
      "height": 1364,
      "width": 1365
    },
...
probably a nicer way of doing it
I was looking at that but didn’t know how 🙂
always fun to poke around in the image asset properties with Vision

*[_type == "sanity.imageAsset" ] { 
    metadata { 
        dimensions
    }
}[0]

A follow-up question: I made it work for a photo field. I also have a text field (_rawBody). How can I change the query so that images included in the text field also include metadata?
I use the Sanity Gridsome template
https://www.sanity.io/create?template=sanity-io/sanity-template-gridsome-blog if it is helpful to know.

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?