Modify the appearance of a single component among a collection of 20 instances

I'm looking to dynamically change the background color of my components based on the colors of the images inside them. Each component should have its own unique color, but currently all 20 instances on the page are getting the same color. I've tried using inline styling and also experimented with refs and unique IDs based on `pokemonid`, but haven't had success.

The color is generated when the image is loaded in the component through a specific function.

Here's an example of the component code named pokemon-card:

<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 background color is set by this method:

methods: {
    //get color for background based on pokemon image
     getColor() {
       const fac = new FastAverageColor();
       fac.getColorAsync(document.querySelector(`.card__front__img`))
         .then(color => {
           this.cardCircleColor = color.hex
         })
     },
   },
   mounted() {
     this.getColor()
   },

Corresponding CSS snippet applying the color 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 element:

<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>

Answer №1

Your query serves as a prime illustration of why it is advised not to directly manipulate the DOM while using Vue.


The issue lies in this snippet of code:

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

document.querySelector('.card__front__img')
always retrieves the first element that matches the specified selector across the entire DOM.

This essentially means that each instance of fac is targeting the same element and utilizing it as the source for colors. Consequently, identical input will yield similar output, repeating 20 times.

In lieu of this, it is recommended to adhere to Vue's guidelines on how to interact with the DOM. Consider using template refs.

For example:

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

By doing so, each component will reference its unique image ref, generating varying results if they load different images.


Additionally, ensure to heed @MichaelMano's recommendation and assign unique identifiers using :key within your v-for. Although not directly linked to the current bug you are encountering, overlooking this step can lead to subtle bugs like the one you are currently facing, which might require hours or even days to troubleshoot and rectify.


Note: If implementing my suggestion does not resolve the error, inspect the src of image at the time getColor() is executed, per component, and confirm they reflect the expected values (e.g.

console.log(this.$refs.image.getAttribute('src'))
)

For future queries: presenting a runnable minimal reproducible example would aid in providing more straightforward solutions.

Answer №2

Make sure to avoid using query selectors within Vue components, and instead utilize the ref property

Additionally, ensure that you use the :key directive in v-for

<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>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the most effective way to prevent a <pre> element from overflowing and instead horizontally scrolling when placed within a table cell that is not

I am faced with the following situation: <table> <tbody> <tr> <td> <pre style="overflow: scroll;"> (very long lines of text) Find my JSFiddle here: https://jsfiddle ...

Turn the flipcard hover effect into an onclick action

After successfully creating interactive flip cards using CSS hover effects, I am now faced with the challenge of adapting them for mobile devices. I'm struggling to figure out how to translate my hover effects into onclick events for a mobile-friendl ...

Resolved plugin issue through CSS adjustments

Take a look at this template 1) After referring to the above template, I developed a fixed plugin using JavaScript. 2) Clicking the icon will trigger the opening of a card. 3) Within the card, I designed a form using mdb bootstrap. Everything seems to ...

The inner div's CSS margin-top is causing the parent div to be pushed downwards

I am looking to move the child div downwards by 5px inside the parent div. Setting margin-top:5px; on the inner div causes both the inner and parent divs to be pushed down from the div above the parent div, rather than just moving the inner div down from t ...

choose particular column in every row from the table's header class name

Every <th> in a table has a class, while the <td> elements do not. Is there a way to style all corresponding <td> elements with the same styles as their <th> counterparts using only plain css without any script? Furthermore, since ...

unable to select a highlighted drop-down menu with focus

Upon loading the page, I am attempting to have the dropdown with the select tag highlighted. Despite trying to apply focus to all of the multiple classes within the select tag, it has not been successful. Can someone take a look at this for me? div.cs-s ...

"Is it possible to disable the transition animation in mat-sidenav of Angular Material without affecting the animations of

My Angular application features a mat-sidenav that is organized like this: <mat-sidenav-container> <mat-sidenav [mode]="'side'"> <!-- Sidenav content --> </mat-sidenav> <mat-sidenav-content ...

Vue: Dragover event experiencing delayed responses from elements

One part of my webpage consists of two columns, a left column and a much larger right column. The right column contains around 1500 rows of components, each row having 5 div cells. I have set up a dragover event on the parent element that encompasses these ...

Experiencing a problem with adjusting the layout on a backdrop picture

https://i.sstatic.net/s6eD5.png Encountering issues depicted in the image while attempting to shift the form on the background image. Problems include the inability to change the width of textboxes, dropdown lists, and buttons. Below are the CSS and regis ...

Sidebar Text Remains Visible Despite Collapsed Sidebar

I'm working on implementing a sidebar shrink effect when collapsing it using a combination of custom CSS, jQuery, and Bootstrap. The custom CSS and jQuery handle the shrinking effect, while Bootstrap is used for styling purposes. However, I am facing ...

Combining the background images of two separate <div> elements results in a deeper shadow effect than when they are individually displayed

Encountering an issue with overlapping background images that results in a darker shadow where they intersect, causing an uneven appearance. The problem arises with a flexible height box containing semi-transparent background images designed to create att ...

The dropdown height feature is experiencing issues in the Chrome browser

3 radio buttons and a single select box are displayed on the page. When clicking on the first radio button, the select box should show contents related to the first radio button. However, when selecting the second or third radio buttons, the select box hei ...

Disabling the scrollbar within angular elements

Trying to remove the two scrollbars from this code, but so far unsuccessful. Attempted using overflow:hidden without success filet.component.html <mat-drawer-container class="example-container" autosize> <button type="button&qu ...

What reasons could be preventing the state from updating correctly in Vuex?

Currently, I am working on a project where I am utilizing vuex along with axios to retrieve data from the backend. The issue arises when I try to access the state - even though the updated state is displayed successfully when I console-log it, there seems ...

Creating a strong line count in HTML with the added features of text wrapping and font size alteration

Many queries have been raised about line numbers, such as: Create line numbers on pre with CSS only or this: Display line number in textarea The issue I am facing is the need for line count for lines that are wrapped with multiple lines. section { bac ...

Achieving a full background with Bootstrap 4: the step-by-step guide

Is there a way to ensure that the background completely covers the container? I am working with Bootstrap 4 and have shared my CSS code for the background image below: <div id="form_wrapper" style="background-image:url('img/bg.png&ap ...

Tips for positioning text within a div element's bottom edge

I'm facing a challenge in creating a footer for a div element that contains a <p> tag. The issue arises when the font size varies, causing the footer to extend outside the box. How can I ensure that it aligns at the bottom of the page with the c ...

How to trigger a force reload on a VueJS route with a different query parameter?

Is there a method to refresh the page component when two pages utilize the same Component? I have encountered an issue where the router does not reload and the previous edit values persist. { path: "/products/new", component: ProductPage, meta: { ...

Inline-block display for file input button in Bootstrap 4 style

I have added an input file button and I am trying to make it full width using display:block. Even after hiding the input field, I am not seeing any difference. Here is the relevant code snippet: <link href="https://stackpath.bootstrapcdn.com/bootstra ...

Decomposing a Vuex module into distinct files with Nuxt: A step-by-step guide

In the official Nuxt documentation (here), it is mentioned that 'You can choose to divide a module file into separate files: state.js, actions.js, mutations.js, and getters.js'. While there are examples of breaking down the Vuex store at the roo ...