SOURCE

console 命令行工具 X clear

                    
>
console
class CardAnimation {
  options = {
    // number of visible items in the stack
    visible: 3,

    // infinite navigation
    infinite: true,
  };
  constructor({ containner, options }) {
    this.containner = containner;
    this.options = Object.assign({}, this.options);
    Object.assign(this.options, options);
    this.items = [].slice.call(this.containner.children);
    this.itemsTotal = this.items.length;
    if (
      (this.options.infinite && this.options.visible >= this.itemsTotal) ||
      (!this.options.infinite && this.options.visible > this.itemsTotal) ||
      this.options.visible <= 0
    ) {
      this.options.visible = 1;
    }
    this.current = 0;
    this.isAnimating = false;
    this._init();
  }
  _init() {
    for (let i = 0; i < this.items.length; i++) {
      const element = this.items[i];
      if (i < this.options.visible) {
        element.style.transform = `rotateZ(${i * 5}deg)`;
        element.style.zIndex = i === 0 ? i + 1 : i - 1;
        element.style.opacity = 1;
      } else {
        element.style.transform = `rotateZ(${this.options.visible * 5}deg)`;
      }
    }
    this.addClass(this.items[this.current], "current-card");
    this.containner.onclick = this.clickHandler.bind(this);
    
    // this.containner.ontouchend = this.clickHandler.bind(this);
  }
  addClass(node, className) {
    node.classList.add(className);
  }
  removeClass(node, className) {
    node.classList.remove(className);
  }
  clickHandler(event) {
    // 判断是否有动画,细节留着
    if (this.isAnimating) return;
    this.isAnimating = true;
    // 移掉当前类名
    const currentItem = this.items[this.current];
    this.removeClass(currentItem, "current-card");

    // 添加动画,给动画绑结束事件
    this.addClass(currentItem, "card-out");

    const dealTransitionEnd = () => {
      currentItem.style.opacity = 0;
      currentItem.style.zIndex = -1;
      currentItem.style.WebkitTransform =
        currentItem.style.transform = `rotateZ( ${parseInt(
          this.options.visible * 5
        )}deg)`;

      this.removeClass(currentItem, "card-out");
      // currentItem.style.zIndex = this.options.visible + 1;
      // 此处设置动画结束信号量
      this.isAnimating = false;
    };

    // set other style!
    for (let i = 0; i < this.items.length; i++) {
      if (i >= this.options.visible) break;

      const pos =
        this.current + i < this.itemsTotal - 1
          ? this.current + i + 1
          : i - (this.itemsTotal - this.current - 1);
      const item = this.items[pos];
      // 同步代码结束时,将其他元素位置跟进
      setTimeout(
        ((item, index) => () => {
          item.style.pointerEvents = "auto";
          item.style.opacity = 1;
          item.style.zIndex = parseInt(this.options.visible - index);
          item.style.transform = `rotateZ(${index * 5}deg)`;
        })(item, i),
        500 * i
      );
    }
    currentItem.onanimationend = dealTransitionEnd.bind(this);
    // update current
    this.current = this.current < this.itemsTotal - 1 ? this.current + 1 : 0;
    this.addClass(this.items[this.current], "current-card");
  }
}

new CardAnimation({
  containner: document.querySelector(".introCards"),
  options: {
    visible: 2,
    infinite: true,
  },
});
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="style.css" />
  </head>

  <body>
    <div class="introCards">
      <div class="introCard"></div>
      <div class="introCard"></div>
      <div class="introCard"></div>
      <div class="introCard"></div>
    </div>
  </body>
  <script src="script.js"></script>
</html>
/* 基础样式 */
html,
body {
  height: 100%;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.introCards {
  height: 600px;
  width: 500px;
  /* background-color: gray; */
  position: relative;
}
.introCard {
  top: 30px;
  height: 300px;
  width: 400px;
  position: absolute;
  transform-origin: bottom right;
  opacity: 0;
  box-shadow: 4px 4px 20px 5px rgb(0 0 0 / 30%);
  transition: all 0.5s ease-in-out;
  border-radius: 10px;
  /* z-index: -1; */
}

.introCard:nth-child(1) {
  background-color: #0984e3;
}

.introCard:nth-child(2) {
  background-color: #6c5ce7;
}

.introCard:nth-child(3) {
  background-color: #ff7675;
}

.introCard:nth-child(4) {
  background-color: #ffeaa7;
}

/* 进入和退出动画 */

@keyframes cardOutKf {
  to {
    opacity: 0;
    -webkit-transform: rotateZ(90deg);
    transform: rotateZ(-90deg);
  }
}

.card-out {
  /* opacity: 1 !important; */

  -webkit-animation: cardOutKf 0.5s forwards;
  animation: cardOutKf 0.5s forwards;
  -webkit-transform-origin: right bottom;
  transform-origin: right bottom;
}

.current-card {
  /* opacity: 1; */
  /* transform: rotate(0deg); */
  /* z-index: 10; */
  cursor:pointer;
}