skip to Main Content

I want to change the background of my components based on the colors of the image inside the component. There are 20 instances of the component on the page, now all 20 instances get the same color, however they should all have their unique color. I don’t know how to do this. I tried using inline styling but that doesn’t work either.

The color is made when the image is loaded in the component. By this function in the component.

I tried it out with refs but it still changes all 20 cards instead of the 1. And I tried setting unique IDs based on pokemonid, but that also did not work.

Here is the code.

The component is called pokemon-card.

Component:

<template>
  <div class="card">
    <div class="card__front" :style="{'background' : `radial-gradient(circle at 50% 0%, ${cardCircleColor} 36%, #fff 36%)`}">
      <div class="card__front__cardTop">
        <div class="card__front__cardTop__id"><span></span>{{ id }}</div>
      </div>
      <img class="card__front__img"
           :src="'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/' + id + '.png'"
           alt="pokemonImage" crossorigin="anonymous">
      <div class="card__front__name">
        <h3>{{ name }}</h3>
      </div>
    </div>
    <div class="card__back">
      <router-link :to="`/pokemonDetails/${id}`" class="card__back__button">Details</router-link>
    </div>
  </div>
</template>

The color is made when the image is loaded in the component. By this function in the component

methods: {
    //gets color for background based on pokemon image
    getColor() {
      const fac = new FastAverageColor();
      fac.getColorAsync(document.querySelector(`.card__front__img`))
        .then(color => {
          // document.querySelector(`.card__front`).style.background = `radial-gradient(circle at 50% 0%, ${color.hex} 36%, #fff 36%)`
          this.cardCircleColor = color.hex
        })
    },
  },
  mounted() {
    this.getColor()
  },

Relevant CSS of component (the color should be applied to radial-gradient (circle at 50% 0%, # 36%, #fff 36%);)

&__front {
  transition: all 0.8s;
  //background: radial-gradient(circle at 50% 0%, #90aac1 36%, #fff 36%);
  //background-color: $cardBg_color;

Parent

<template>
  <div class="container">
    <div class="cardContainer">
        <pokemon-card v-for="data in pokemonData.results" :name="data.name" :id="data.id"></pokemon-card>
    </div>

    <div class="PaginationcontrolContainer">
      <div class="paginationControls">
        <div @click="decreasePaginationCounter" class="paginationControls__btn"> &lt </div>
        <input @change="manuallyChangingPaginationIndex" v-model="paginationIndex" type="number">
        <div @click="increasePaginationCounter" class="paginationControls__btn"> > </div>
      </div>
    </div>

  </div>
</template>

2

Answers


  1. First, dont use query selectors inside vue, use ref

    Then use that ref to reference the image. Also in v-for use :key

    <script>
    import { ref } from 'vue';
    export default {
      setup() {
        const cardImage = ref();
        const cardCircleColor = ref();
    
        return {
          cardImage,
          cardCircleColor,
        }
      },
      mounted() {
        this.getColor();
      },
      methods: {
        getColor() {
          const fac = new FastAverageColor();
          fac.getColorAsync(this.cardImage)
              .then(color => {
                this.cardCircleColor = color.hex
              })
        },
      },
    }
    </script>
    <template>
      <div class="card">
        <div class="card__front" :style="{'background' : `radial-gradient(circle at 50% 0%, ${cardCircleColor} 36%, #fff 36%)`}">
          <div class="card__front__cardTop">
            <div class="card__front__cardTop__id"><span></span>{{ id }}</div>
          </div>
          <img
            ref="cardImage"
            class="card__front__img"
            :src="'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/' + id + '.png'"
            alt="pokemonImage" crossorigin="anonymous">
          <div class="card__front__name">
            <h3>{{ name }}</h3>
          </div>
        </div>
        <div class="card__back">
          <router-link :to="`/pokemonDetails/${id}`" class="card__back__button">Details</router-link>
        </div>
      </div>
    </template>
    
    Login or Signup to reply.
  2. Your question is a good example of why one should not manipulate DOM directly when using Vue.


    The problem is here:

    fac.getColorAsync(document.querySelector(`.card__front__img`))
      .then(...)
    

    document.querySelector('.card__front__img') will always return the first element matching the selector found in the entire DOM.

    Which means that each of the separate fac instances are finding the same exact element and using it as color source. The same input will generate the same output, 20 separate times.

    Instead, you should follow Vue’s guidelines on how to interact with DOM. Use template refs.

    Specifically:

    <template>
      <!-- ... -->
        <img class="card__front__img" ref="image">
    </template>
    
      // ...
      getColor() {
        //...
        fac.getColorAsync(this.$refs.image).then(...)
      }
    

    Now each component will reference its own image ref and, provided they load different images, results should vary.


    Also, make sure you follow @MichaelMano’s advice and :key your v-for‘s with unique identifiers. Although it’s not directly connected with the bug you’re currently experiencing, it’s a common source of subtle bugs, just like yours, which could consume hours, sometimes days, to understand and fix.


    Note: if my suggestion doesn’t fix the error, you should check image‘s src at the time you’re running getColor(), on each component, and make sure they’re what they should be (e.g. console.log(this.$refs.image.getAttribute('src')))

    Note for future questions: your question would have been easier to answer if you provided a runnable minimal reproducible example.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search