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

JS动态创建元素的CSS Transition过渡动画失效问题

问题:Chrome扩展中HP条过渡动画失效

我正在开发一款用于浏览器游戏的Chrome扩展,用来展示队伍信息,目标是实现hp-bar变化时的平滑过渡动画。之前版本功能正常,但为了实现队员卡片按字母排序重构代码后,hp-bar的宽度和颜色显示都正常,唯独平滑过渡动画触发不了。

尝试过的解决方法:

  • 在appendChild前后调用setHp()
  • 在JS中为.hp-container设置transition属性
  • 在函数内直接设置过渡
  • 将CSS选择器从.hp-bar改为#cardOneHP, #cardTwoHp...

怀疑问题和JS创建元素的方式有关,但不知道具体怎么解决。


代码片段

JavaScript

const hpBarGreen = "#39ff7b";
const hpBarYellow = "#f3b200";
const hpBarRed = "#fb3041";

chrome.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
      if (request.data) {
        const party = request.data;

        const members = [];

        function createMemberObject(partyMember, index) {
          if (!partyMember) return null;

          return {
            id: `member${index + 1}`,
            originalName: partyMember.name,
            sortName: partyMember.name.replace("G-Max ", "").replace("Mega ", ""),
            form: partyMember.form,
            level: partyMember.level,
            typeOne: partyMember.types[0],
            typeTwo: partyMember.types[1],
            teraType: partyMember.teraType,
            status: partyMember.status,
            currentHp: partyMember.currentHP,
            maxHp: partyMember.maxHP
          };
        }

        for (let i = 0; i <= 5; i++) {
          const memberObject = createMemberObject(party[i], i)
          if (memberObject) {
            members.push(memberObject);
          }
        };

        function displayMembers(members) {
          const popup = document.querySelector(".popup");
          popup.innerHTML = "";

        members.forEach(member => {
          const card = document.createElement('div');
          card.className = 'pokemon-card';
          card.id = member.id;

          card.innerHTML = `
            <div class="sprite">
                <img class="pokemon-sprite" id="${member.id}Sprite" src="./images/pokemon/${
                  member.form ? `${member.sortName}-${member.form}` : `${member.sortName}`
                }.png" alt="">
                <img id="${member.id}Form" src="" alt="">
            </div>
            <div class="stats">
                <div class="line-one">
                    <div class="name" id="${member.id}Name">${member.sortName.toUpperCase()}</div>
                    <div class="level-container">
                        <p id="${member.id}LevelText">Lv.</p>
                        <p class="level-number" id="${member.id}Level">${member.level}</p>
                    </div>
                </div>
                <div class="line-two">
                  <div class="types">
                      <img id="${member.id}TypeOne" src="${
                        member.teraType ? `./images/tera-types/${member.teraType}.png` : `./images/types/${member.typeOne}.png`
                      }" alt="">
                      <img id="${member.id}TypeTwo" src="${
                        member.teraType ? "" : `./images/types/${member.typeTwo}.png`
                      }" alt="">
                  </div>
                </div>
                <div class="line-three">
                    <img id="${member.id}Status" class="status" src="./images/status/${member.status}.png" alt="">
                </div>
                <div class="line-four">
                    <div id="${member.id}HpBar" class="hp-container">
                        <div id="${member.id}ActiveHp" class="hp-bar"></div>
                    </div>
                </div>
            </div>
          `;

          popup.appendChild(card);
      });
    }
      members.sort((a, b) => a.sortName.localeCompare(b.sortName));
      displayMembers(members);

      const hpPercentage = (members[0].currentHp / members[0].maxHp) * 100;
      const hpBar = card.querySelector(`#${members.id}ActiveHp`);

      console.log(members[0].currentHp, members[0].maxHp);

      function setHp(percentage, hp) {
        hp.style.width = (percentage) + "%";
        if (percentage <= 25) {
          hp.style.backgroundColor = hpBarRed;
        } else if (percentage <= 50) {
          hp.style.backgroundColor = hpBarYellow;
        } else {
          hp.style.backgroundColor = hpBarGreen;
        }
      };
      
      setHp(hpPercentage, hpBar);
    }
  }
);

CSS

@font-face {
  font-family: False-Positive;
  src: url(./fonts/falsepos.ttf);
}

body {
  margin: 0;
  padding: 0;
  border: 0;
  font-family: False-Positive;
  color: #333333;
}

.hide {
  display: none;
}

.popup {
  width: 500px;
  height: 800px;
  background-color: #e5e4c5ff;
  display: grid;
  grid-template-rows: repeat(6, 1fr);
  align-items: center;
  justify-content: center;
}

.pokemon-card {
  position: relative;
  width: 300px;
  height: 100px;
  background-color: #ffffcc;
  display: flex;
  overflow: hidden;
  align-items: center;
  border: #777777 solid 2px;
}
  
.sprite {
  min-width: 100px;
  width: 100px;
  height: 100px;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  position: relative;
  align-items: center;
  justify-content: center;
}
  
.pokemon-sprite {
  grid-column: 1 / span 3;
  grid-row: 1 / span 3;
  max-width: 85%;
  margin-left: 5px;
}
  
#memberOneForm, #memberTwoForm, #memberThreeForm, #memberFourForm, #memberFiveForm, #memberSixForm {
  grid-column: 1;
  grid-row: 1;
  z-index: 1;
  width: 100%;
}
  
.stats {
  max-width: 100%;
  height: 100%;
  display: grid;
  grid-template-rows: 30px 16px 20px 15px;
  gap: 3px;
  padding-top: 5px;
}
  
.line-one, .line-two, .line-three, .line-four {
  display: flex;
  width: 200px;
}
  
.line-one {
  justify-content: space-between;
  align-items: center;
}
  
.line-three  {
  justify-content: end;
}
  
.name {
  min-width: 0;
  font-size: 30px;
}
  
p {
  margin: 0;
}

.level-container {
  display: flex;
  flex-direction: column;
  align-items: end;
  padding-right: 5px;
}

#level-text {
  font-size: 10px;
}

.level-number {
  font-size: 18px;
}

.types {
  width: 75%;
  height: 100%;
  display: flex;
  gap: 5px;
}

#type-one, #type-two {
  max-height: 100%;
  max-width: 100%;
}

.status {
  border-radius: 20px;
  margin-right: 3px;
  margin-top: 2px;
}

.hp-container {
  width: 100%;
  height: 100%;
  background-color: #555555;
  border-radius: 10px;
  border: 2px solid black;
  margin-right: 3px;
  overflow: hidden;
}

.hp-bar {
  width: 100%;
  height: 100%;
  transition: width 0.2s linear;
}

HTML

<div class='popup'></div>

解决办法

问题出在两个核心点:

1. 元素创建后立即修改样式,浏览器跳过过渡

当JS创建元素并同步修改其width时,浏览器会将这两个操作合并为一次重绘,不会触发过渡动画。需要让浏览器先完成元素的初始渲染,再执行样式修改。

2. HP条获取逻辑错误

原代码中card是displayMembers函数内的局部变量,外部无法访问;且members.id是错误的写法,应该遍历每个成员处理对应的HP条。

修改步骤

步骤1:重构displayMembers和setHp函数

把HP条的处理逻辑移到displayMembers内部,使用requestAnimationFrame等待浏览器渲染完成后再修改样式:

// 把setHp函数移到外部,方便全局调用
function setHp(percentage, hp) {
  hp.style.width = percentage + "%";
  if (percentage <= 25) {
    hp.style.backgroundColor = hpBarRed;
  } else if (percentage <= 50) {
    hp.style.backgroundColor = hpBarYellow;
  } else {
    hp.style.backgroundColor = hpBarGreen;
  }
};

function displayMembers(members) {
  const popup = document.querySelector(".popup");
  popup.innerHTML = "";

  members.forEach(member => {
    const card = document.createElement('div');
    card.className = 'pokemon-card';
    card.id = member.id;

    card.innerHTML = `
      <!-- 保持原有的HTML结构不变 -->
    `;

    popup.appendChild(card);

    // 计算当前成员的HP百分比
    const hpPercentage = (member.currentHp / member.maxHp) * 100;
    const hpBar = card.querySelector(`#${member.id}ActiveHp`);

    // 等待浏览器完成元素渲染后再修改样式,触发过渡
    requestAnimationFrame(() => {
      setHp(hpPercentage, hpBar);
    });
  });
}

步骤2:删除原代码中错误的HP处理逻辑

移除displayMembers(members);之后的这段无效代码:

const hpPercentage = (members[0].currentHp / members[0].maxHp) * 100;
const hpBar = card.querySelector(`#${members.id}ActiveHp`);

console.log(members[0].currentHp, members[0].maxHp);

function setHp(percentage, hp) {
  // ...原函数内容
};

setHp(hpPercentage, hpBar);

原理说明

requestAnimationFrame会在下一次浏览器重绘前执行回调,此时元素已经被添加到DOM并完成初始渲染,修改width属性会被浏览器识别为独立的样式变化,从而触发CSS过渡动画。同时修改后会遍历所有成员,每个卡片的HP条都能正确应用过渡效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 21:54:56