0

I've bound a computed property to the src of an image tag in Vue.js. The code appears to correct, but does not work consistently which is baffling to me. Additionally, switching to the /about page and back to the main books page always properly displays the images.

Any information as to what could be causing this issue would be wonderful!

A hosted version of the app is available here: https://books.surge.sh/

The relevant code for the book-item component.

The full Github repo.

The code generating the book component and image src is as follows:

<template>
  <article class="book-item">
    <img :src="imgSrc" class="image">
    <div class="meta">
      <div class="name">{{ book.title }}</div>*
      <div class="author">{{ book.author }}</div>
    </div>
    <div class="description">
      <p v-html="book.description"></p>
    </div>
  </article>
</template>

<script>
export default {
  props: ['book'],
  computed: {
    imgSrc() {
      return `/static/img/${this.book.image}`;
    }
  }
};
</script>

Partially displayed book covers on initial load:

Partially displayed images.

2
  • Seems like an issue with your CSS because if you open up chrome devtools and inspect the element you can always see the src tag and it links properly. When I simply set your img to display: block it already triggers and renders it. Commented Jul 28, 2017 at 7:13
  • *Update: Any layout change in any element will cause it to render as long as the content is repainted in any way. This might be a good start. Commented Jul 28, 2017 at 7:15

2 Answers 2

1

The problem was with .image 'width' style. You should assign that .image class after your component renders. Do this:

<template>
  <article class="book-item">
    <img :src="imgSrc" :class="{image: componentLoaded}"> <!-- 1. change class -->
    <div class="meta">
      <div class="name">{{ book.title }}</div>*
      <div class="author">{{ book.author }}</div>
    </div>
    <div class="description">
      <p v-html="book.description"></p>
    </div>
  </article>
</template>

<script>
export default {
  props: ['book'],
  data() {
    return {
      componentLoaded: false           // 2. add this 'state' data
    };
  },
  computed: {
    imgSrc() {
      return `/static/img/${this.book.image}`;
    }
  },
  mounted() {
    // 3. This will assign class to your image
    // after component did mounted (nextTick() did not helped)
    setTimeout(() => {
      this.componentLoaded = true;
    }, 1);
  }
};
</script>

Good luck ! I created a pull request here

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks so much for your help! I found out a way to get it working with just CSS changes, but it was super helpful to realize that the issue was related to the image's height / width CSS properties.
0

As @Stephan-v pointed out, the issue was with the img height and width CSS properties. The .image class had been set to width: 150px; height: 100%; but since the image src isn't set until Vue's lifecycle hooks complete, the CSS hadn't properly applying until something forced a re-render, such as switching out of the route and back.

I solved this by creating a container div around the img like so:

<div class="image-container">
  <img :src="'/static/img/' + image" class="image">
</div>

Then setting the container size using min-height and min-width, with only the child img width property set to a hard value:

> .image-container {
  min-width: 150px;
  min-height: 100%;

  img { width: 150px; }
}

Doing so prevents any "dynamic" CSS values (percent width or height rules) from applying to a image with a dynamically set src.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.