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

为已应用CSS变换的元素添加抖动动画时丢失原有变换的问题

卡片抖动丢失翻转状态问题修复

问题背景

我正在制作卡片交互效果作为多个项目的基础,在实现卡片抖动效果时遇到问题:已翻转的卡片在触发抖动动画后,会丢失之前的翻转状态。

操作步骤:

  • 点击两张卡片完成翻转
  • 选中其中一张(会上移)
  • 再点击另一张触发抖动(表示未被选中),此时卡片会显示背面而非正面

我尝试过调整z-index、opacity、display属性,也尝试在动画中重复设置rotateY(180deg),最接近的方案是使用:not选择器,但仅能抖动卡片内的文本而非整个卡片。

问题代码

HTML

<body>
  <form onsubmit="return false">
    <label for="selectLimit"> Select Limit:</label>
    <input id="numSelectLimit" name="selectLimit" type="number" min="1" max="100" value="1" oninput="onInput()">
  </form>

  <div class="card">
    <div class="cardInner" onclick="clickFlip4(this)">
      <div class="cardBack">
        <p>BACK</p>
      </div>
      <div class="cardFront">
        <p>FRONT</p>
      </div>
    </div>
  </div>
  <div class="card">
    <div class="cardInner" onclick="clickFlip4(this)">
      <div class="cardBack">
        <p>BACK</p>
      </div>
      <div class="cardFront">
        <p>FRONT</p>
      </div>
    </div>
  </div>
</body>

CSS

.card {
  background-color: transparent;
  width: 275px;
  height: 400px;
  perspective: 1000px;
  display: inline-block;
  padding: 65px 3px;
}

.cardInner {
  position: relative;
  width: 100%;
  height: 100%;
  text-align: center;
  transition: transform 0.8s;
  transform-style: preserve-3d;
  border-radius: 15px;
}

.cardFlipped {
  transform: rotateY(180deg);
}

.cardBack,
.cardFront {
  position: absolute;
  width: 100%;
  height: 100%;
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  border-radius: 15px;
  border: 3px solid #000000;
}

.cardBack {
  background-color: #ffffff;
  color: black;
}

.cardFront {
  background-color: dodgerblue;
  color: white;
  transform: rotateY(180deg);
}

.selected,
.selected2,
.selected3 {
  margin-top: -60px;
}

form,
label,
input {
  font: inherit;
}

.shake {
  animation: horizontal-shaking 0.5s;
}

@keyframes horizontal-shaking {
  0% { transform: translateX(0) }
  20% { transform: translateX(5px) }
  40% { transform: translateX(-5px) }
  60% { transform: translateX(5px) }
  80% { transform: translateX(-5px) }
  100% { transform: translateX(0) }
}

JavaScript

let selectLimit = parseInt(document.getElementById("numSelectLimit").value);
let currentlySelected = [];

function onInput() {
  selectLimit = parseInt(document.getElementById("numSelectLimit").value);
}

function clickFlip4(element) {
  element.classList.toggle("cardFlipped");
  element.setAttribute("onclick", "clickSelect3(this)");
}

function clickSelect3(element) {
  if (element.classList.contains("selected3")) {
    var newArray = [];
    for (var i = 0; i < currentlySelected.length; i++) {
      if (currentlySelected[i] === element) continue;
      else newArray[newArray.length] = element;
    }
    currentlySelected = newArray;
    element.classList.toggle("selected3");
  } else {
    if (currentlySelected.length !== selectLimit) {
      currentlySelected[currentlySelected.length] = element;
      element.classList.toggle("selected3");
    } else {
      element.classList.toggle("shake");
      setTimeout(function() {
        element.classList.toggle("shake");
      }, 500);
    }
  }
}

解决方案

问题根源:抖动动画的transform会覆盖.cardFlipped类中的rotateY(180deg)——CSS动画的transform优先级更高,且会重置之前的变换值。

方案1:适配翻转状态的双动画

为已翻转的卡片单独定义包含旋转变换的抖动动画:

/* 已翻转卡片的抖动动画 */
.cardFlipped.shake {
  animation: flipped-horizontal-shaking 0.5s;
}

/* 未翻转卡片的抖动动画 */
@keyframes horizontal-shaking {
  0% { transform: translateX(0); }
  20% { transform: translateX(5px); }
  40% { transform: translateX(-5px); }
  60% { transform: translateX(5px); }
  80% { transform: translateX(-5px); }
  100% { transform: translateX(0); }
}

/* 已翻转卡片的抖动动画,保留rotateY(180deg) */
@keyframes flipped-horizontal-shaking {
  0% { transform: rotateY(180deg) translateX(0); }
  20% { transform: rotateY(180deg) translateX(5px); }
  40% { transform: rotateY(180deg) translateX(-5px); }
  60% { transform: rotateY(180deg) translateX(5px); }
  80% { transform: rotateY(180deg) translateX(-5px); }
  100% { transform: rotateY(180deg) translateX(0); }
}

方案2:CSS变量动态复用变换值

更灵活的方式,用CSS变量存储翻转状态,动画中自动复用:

  1. 修改卡片基础样式和翻转类:
.cardInner {
  --card-rotation: none;
  position: relative;
  width: 100%;
  height: 100%;
  text-align: center;
  transition: transform 0.8s;
  transform-style: preserve-3d;
  border-radius: 15px;
}

.cardFlipped {
  --card-rotation: rotateY(180deg);
  transform: var(--card-rotation);
}
  1. 修改抖动动画:
.shake {
  animation: horizontal-shaking 0.5s;
}

@keyframes horizontal-shaking {
  0% { transform: var(--card-rotation) translateX(0); }
  20% { transform: var(--card-rotation) translateX(5px); }
  40% { transform: var(--card-rotation) translateX(-5px); }
  60% { transform: var(--card-rotation) translateX(5px); }
  80% { transform: var(--card-rotation) translateX(-5px); }
  100% { transform: var(--card-rotation) translateX(0); }
}

额外优化:简化JS数组操作

移除选中元素的逻辑可以简化为:

function clickSelect3(element) {
  if (element.classList.contains("selected3")) {
    // 用filter简化数组过滤
    currentlySelected = currentlySelected.filter(item => item !== element);
    element.classList.toggle("selected3");
  } else {
    if (currentlySelected.length !== selectLimit) {
      currentlySelected.push(element);
      element.classList.toggle("selected3");
    } else {
      element.classList.toggle("shake");
      setTimeout(() => {
        element.classList.toggle("shake");
      }, 500);
    }
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 18:43:15