You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于MVC架构的Pokemon卡片背景色无法渲染问题排查

问题描述

采用MVC架构开发宝可梦项目,通过PokeAPI获取数据,尝试从typeColor常量中渲染卡片背景色,控制台显示styleCard的颜色值已修改,但UI中背景色未生效。项目包含model.js、controller.js、resultView.js和View.js四个文件,代码如下:

model.js

export const state = {
    pokemon: {},
    search: {
        query: '',
        results: [],
        page: 1,
        resultsPerPage: 100,
    },
};

const fetchJson = (url) =>
    fetch(url)
        .then((r) => r.json())
        .catch(console.log);
export const loadPokemon = async () => {
    const [arrayA, arrayB] = await Promise.all([
        Promise.all(
            new Array(1008)
                .fill(0)
                .map((_, i) => fetchJson(`https://pokeapi.co/api/v2/pokemon/${i + 1}`))
        ),
        Promise.all(
            new Array(151)
                .fill(0)
                .map((_, i) =>
                    fetchJson(`https://pokeapi.co/api/v2/pokemon-species/${i + 1}`)
                )
        ),
    ]);

    state.pokemon = arrayA.map((poke) => {
        return {
            name: poke.name,
            image: poke.sprites.front_default,
            types: poke.types,
        };
    });

    console.log(arrayA);
    console.log(arrayB);

};
export const typeColor = {
    bug: '#26de81',
    dragon: '#ffeaa7',
    electric: '#fed330',
    fairy: '#FF0069',
    fighting: '#30336b',
    fire: '#f0932b',
    flying: '#81ecec',
    grass: '#00b894',
    ground: '#EFB549',
    ghost: '#a55eea',
    ice: '#74b9ff',
    normal: '#95afc0',
    poison: '#6c5ce7',
    psychic: '#a29bfe',
    rock: '#2d3436',
    water: '#0190FF',
};

controller.js

import * as model from './model.js';
import resultView from './views/resultsView.js';
import View from './Views/View.js';

const controlPokemon = async function () {
    try {
        await model.loadPokemon();

        resultView.render(model.state.pokemon);

        const typeData = model.state.pokemon.map((poke) => {
            return poke.types[0].type.name;
        });
        console.log(typeData);
        const themeColor = model.typeColor[typeData[0]];

        resultView.styleCard(themeColor);
    } catch (err) {
        console.error(err);
    }
};

const init = function () {
    resultView.addHandlerRender(controlPokemon);
};
init();

resultView.js

import View from './View.js';

class ResultsView extends View {
    _parentElement = document.querySelector('.results');
    _card = document.querySelector('.card');

    _generateMarkup() {
        console.log(this._data);
        return this._data.map(this._generateMarkupPreview).join('');
    }

    addHandlerRender(handler) {
        ['load'].forEach((ev) => window.addEventListener(ev, handler));
    }

    styleCard(color){
        return (this._card.style.background = `radial-gradient(circle at 50% 0%, ${color} 36%, #ffffff 36%)`);
    };

    _generateMarkupPreview(result) {
        return `
    <li class="preview">
        <a class="preview__link preview__link--active" href="#2343"></a>
            <div class="card">
            <p class="hp"><span>HP</span>80</p>
            <figure class="preview__fig">
            <img class="fig" src="${result.image}" />
            </figure>
                        <h4 class="txt-description">${result.name[0].toUpperCase() + result.name.slice(1)
            }</h4>
            <div class="types">
            <span>${result.types[0]?.type.name[0].toUpperCase() +
                    result.types[0].type.name.slice(1)
                }</span>
            <span>${result.types[1]?.type.name}</span>
            </div>
            <div class="stats">
                    <div>
                        <h3>60</h3>
                        <p>Attack</p>
                </div>
                <div>
                        <h3>60</h3>
                            <p>Defense</p>
                </div>
                <div>
                    <h3>60</h3>
                    <p>Speed</p>
                </div>
            </div>
        </div>
    </li>`;
    }
}

export default new ResultsView();

View.js

export default class View {
    _data;

    render(data) {
        if (!data || (Array.isArray(data) && data.length === 0))
            return this.renderError();

        this._data = data;
        const markup = this._generateMarkup();
        this._clear();
        this._parentElement.insertAdjacentHTML('afterbegin', markup);
    }

    _clear() {
        this._parentElement.innerHTML = '';
    }
}
问题原因
  1. 提前获取DOM元素导致无效:ResultsView类中的_card = document.querySelector('.card')在类实例化时就执行了,此时页面还未渲染任何卡片,document.querySelector('.card')返回null。后续调用styleCard时,操作的是null元素的样式,自然不会生效。
  2. 逻辑缺陷:仅给第一个卡片设置颜色:当前控制器中只取了第一个宝可梦的类型颜色,就算DOM操作生效,也只会给一个卡片设置颜色,不符合每个卡片对应自身类型颜色的需求。
  3. 渲染流程覆盖旧元素:render方法会先清空父元素再插入新的HTML,就算之前获取到了_card元素,也会被清空移除,后续操作的元素已经不存在于DOM中。
解决方案

最优方案是在生成卡片HTML时直接注入背景色样式,避免后续DOM操作,同时保证每个卡片对应自身的类型颜色:

步骤1:修改resultView.js的_generateMarkupPreview方法

引入typeColor常量,根据当前宝可梦的主类型获取颜色,直接写入卡片的style属性:

import View from './View.js';
import { typeColor } from '../model.js'; // 引入typeColor

class ResultsView extends View {
    _parentElement = document.querySelector('.results');

    _generateMarkup() {
        return this._data.map(this._generateMarkupPreview).join('');
    }

    addHandlerRender(handler) {
        ['load'].forEach((ev) => window.addEventListener(ev, handler));
    }

    // 移除无用的styleCard方法和_card属性

    _generateMarkupPreview(result) {
        const mainType = result.types[0].type.name;
        const cardColor = typeColor[mainType];
        return `
    <li class="preview">
        <a class="preview__link preview__link--active" href="#2343"></a>
            <div class="card" style="background: radial-gradient(circle at 50% 0%, ${cardColor} 36%, #ffffff 36%)">
            <p class="hp"><span>HP</span>80</p>
            <figure class="preview__fig">
            <img class="fig" src="${result.image}" />
            </figure>
                        <h4 class="txt-description">${result.name[0].toUpperCase() + result.name.slice(1)
            }</h4>
            <div class="types">
            <span>${result.types[0]?.type.name[0].toUpperCase() +
                    result.types[0].type.name.slice(1)
                }</span>
            <span>${result.types[1]?.type.name}</span>
            </div>
            <div class="stats">
                    <div>
                        <h3>60</h3>
                        <p>Attack</p>
                </div>
                <div>
                        <h3>60</h3>
                            <p>Defense</p>
                </div>
                <div>
                    <h3>60</h3>
                    <p>Speed</p>
                </div>
            </div>
        </div>
    </li>`;
    }
}

export default new ResultsView();

步骤2:简化controller.js代码

移除原有的styleCard调用逻辑,因为现在渲染时已经直接注入样式:

import * as model from './model.js';
import resultView from './views/resultsView.js';

const controlPokemon = async function () {
    try {
        await model.loadPokemon();
        resultView.render(model.state.pokemon);
    } catch (err) {
        console.error(err);
    }
};

const init = function () {
    resultView.addHandlerRender(controlPokemon);
};
init();

这样修改后,每个卡片会根据自身的主类型自动设置对应的背景色,无需后续DOM操作,从根源解决了DOM元素获取无效的问题,同时满足所有卡片的样式需求。

内容的提问来源于stack exchange,提问作者Alazy99

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 07:07:39