卡牌大师

182 阅读4分钟

卡牌大师:一个可拖拽的前端小项目

在前端开发的世界里,交互效果总是能给用户带来更好的体验。今天,我将分享一个简单的可拖拽卡牌项目,通过这个项目,你可以学习如何使用GSAP库实现复杂的动画效果,并结合HTML和CSS完成一个有趣的前端小应用。

项目背景

这个项目名为“卡牌大师”,灵感来源于常见的卡牌游戏。用户可以通过拖拽屏幕上的卡牌,将其移动到不同的方向,如上、下、左、右等。每张卡牌在拖拽过程中会根据方向自动调整旋转角度,并最终移动到指定的位置。

技术栈

  • HTML: 用于构建页面结构。
  • CSS: 用于样式设计,包括布局、动画效果等。
  • JavaScript: 用于实现拖拽功能和动画控制。
  • GSAP: 一个强大的动画库,用于实现平滑的动画效果。

image.png

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.10.4/dist/gsap.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.10.4/dist/Draggable.min.js"></script>
  <body>
    <div class="title">落魄前端,在线发牌</div>
    <div class="container" id="container">
      <div class="draggable" id="draggable1">Drag me!</div>
      <div class="draggable" id="draggable2">Drag me too!</div>
      <div class="draggable" id="draggable3">Drag me 3!</div>
      <div class="draggable" id="draggable4">Drag me 4!</div>
      <div class="draggable" id="draggable5">Drag me 5!</div>
      <div class="draggable" id="draggable6">Drag me 6!</div>
      <div class="draggable" id="draggable7">Drag me 7!</div>
    </div>
  </body>
 
    /* styles.css */
    body {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 100vh;
      margin: 0;
      background-color: #333;
      position: relative;
    }
    .title {
      position: absolute;
      top: 100px;
      z-index: 2;
      font-size: 28px;
      color: transparent;
      background-image: radial-gradient(
        circle,
        red,
        orange,
        yellow,
        green,
        blue,
        indigo,
        violet
      );
      background-size: 100%;
      background-clip: text;
      animation: slide 10s linear infinite both;
    }
    @keyframes slide {
      0% {
        background-position-x: 0px;
      }
      100% {
        background-position-x: 300px;
      }
    }
    .container {
      width: 375px;
      height: 100%;
      overflow: hidden;
      position: relative;
      display: flex;
      justify-content: center;
      align-items: center;
      background-color: #f1f1f1;
    }

    .draggable {
      width: 200px;
      height: 240px;
      border: 2px solid #fff;
      background-color: #3498db;
      color: white;
      display: flex;
      justify-content: center;
      align-items: center;
      border-radius: 10px;
      cursor: pointer;
      user-select: none;
      position: absolute;
      opacity: 0;
      transition: all 0.05s ease-in;
      transform: translate3d(0, 0, -180px) scale(1);
      box-shadow: 0 10px 20px rgba(0, 0, 0, 0.09), 0 6px 6px rgba(0, 0, 0, 0.03);
    }
    .draggable:nth-child(1) {
      z-index: 3; /* 最上面的卡片 */
      transform: translate3d(0, 0, 0) scale(1);
      /* 初始位置 */
      opacity: 1;
    }

    .draggable:nth-child(2) {
      z-index: 2; /* 中间的卡片 */
      transform: translate3d(0px, 10px, -10px) scale(0.96);
      /* 向右下方偏移 */
      opacity: 1;
    }

    .draggable:nth-child(3) {
      z-index: 1; /* 最下面的卡片 */
      transform: translate3d(0px, 20px, -20px) scale(0.92);
      /* 向右下方偏移更多 */
      opacity: 1;
    }
    .draggable:nth-child(4) {
      z-index: 1; /* 最下面的卡片 */
      transform: translate3d(0px, 20px, -30px) scale(0.92);
      /* 向右下方偏移更多 */
      opacity: 1;
    }
      document.addEventListener("DOMContentLoaded", function () {
        // 初始化 Draggable
        gsap.registerPlugin(Draggable);

        // 定义每个方向的目标位置
        const targetPositions = {
          上: { x: 0, y: -500 },
          下: { x: 0, y: 500 },
          左: { x: -500, y: 0 },
          右: { x: 500, y: 0 },
          左上: { x: -500, y: -500 },
          右上: { x: 500, y: -500 },
          左下: { x: -500, y: 500 },
          右下: { x: 500, y: 500 },
          静止: { x: 0, y: 0 },
        };
        const containerDom = document.getElementById("container");

        const aniFlag = {
          value: false,
        };
        // 创建可拖拽元素
        function createDraggableElements() {
          if (aniFlag.value) return;
          aniFlag.value = true;
          Draggable.create(".draggable", {
            type: "x,y",
            bounds: ".container",
            onPress: function () {
              // 拖拽开始时,克隆元素
              this.clone = this.target.cloneNode(true);
            },
            onDrag: function () {
              // 计算旋转角度
              const angle = Math.atan2(this.y, this.x) * (180 / Math.PI);
              gsap.to(this.target, { rotation: angle });

              // 判断拖拽方向
              const direction = getDragDirection(this.x, this.y);
              console.log(
                `Dragging ${this.target.id} to (${this.x}, ${
                  this.y
                }) with angle ${angle.toFixed(2)}° and direction: ${direction}`
              );
            },
            onDragEnd: function () {
              // 判断拖拽方向并移动到目标位置
              const direction = getDragDirection(this.x, this.y);
              const targetPosition = targetPositions[direction];
              gsap.to(this.target, {
                x: targetPosition.x,
                y: targetPosition.y,
                duration: 0.5,
                onComplete: () => {
                  // 动画结束后的回调函数
                  console.log("Animation completed");
                  // 在这里添加你需要执行的逻辑
                  if (direction !== "静止") {
                    aniFlag.value = false;

                    //   // 移除原始元素
                    containerDom.removeChild(this.target);
                    console.dir(this.clone, "33");
                    this.clone.style.transform = "";
                    this.clone.style.zIndex = "";
                    // 追加克隆元素
                    containerDom.appendChild(this.clone);
                    // 重新创建克隆元素的 Draggable 实例
                    createDraggableElements();
                  } else {
                    // 如果是静止状态,复原操作
                    gsap.to(this.target, {
                      x: 0,
                      y: 0,
                      rotation: 0,
                      duration: 0.5,
                      onComplete: () => {
                        // 拖动结束时重置旋转角度
                        aniFlag.value = false;
                        gsap.to(this.target, { rotation: 0, duration: 0 });
                      },
                    });
                  }
                },
              });

              console.log(
                `Drag ended for ${this.target.id} and moved to ${direction}`
              );
            },
          });
        }

        // 判断拖拽方向的函数
        function getDragDirection(x, y) {
          const threshold = 40; // 判断方向的阈值
          if (Math.abs(x) < threshold && Math.abs(y) < threshold) {
            return "静止";
          }

          if (y < -threshold) {
            if (x < -threshold) {
              return "左上";
            } else if (x > threshold) {
              return "右上";
            } else {
              return "上";
            }
          } else if (y > threshold) {
            if (x < -threshold) {
              return "左下";
            } else if (x > threshold) {
              return "右下";
            } else {
              return "下";
            }
          } else {
            if (x < -threshold) {
              return "左";
            } else if (x > threshold) {
              return "右";
            }
          }
        }

        // 初始化拖拽元素
        createDraggableElements();
      });

关键点解析

  1. HTML 结构:

    • 使用 <div> 元素构建卡牌容器和卡牌本身。
    • 通过 id 和 class 属性进行样式和脚本的关联。
  2. CSS 样式:

    • 使用 Flexbox 布局确保卡牌居中显示。
    • 通过 z-index 控制卡牌的层级关系。
    • 使用 transform 和 transition 实现卡牌的初始位置和拖拽过程中的动画效果。
  3. JavaScript 逻辑:

    • 使用 GSAP 的 Draggable 插件实现卡牌的拖拽功能。
    • 通过 onPressonDrag 和 onDragEnd 回调函数控制拖拽过程中的行为。
    • 使用 getDragDirection 函数判断拖拽方向,并根据方向移动卡牌到目标位置。
  4. 动画效果:

    • 使用 GSAP 的 to 方法实现平滑的动画效果。
    • 通过 rotation 属性实现卡牌在拖拽过程中的旋转效果。

总结

通过这个项目,我们可以学习到如何使用 GSAP 实现复杂的动画效果,以及如何结合 HTML 和 CSS 完成一个交互性强的前端应用。希望这个项目能给你带来启发,激发你在前端开发中的创造力。如果你有任何问题或建议,欢迎在评论区留言交流!


希望这篇文章对你有所帮助!如果有任何问题或需要进一步的解释,请随时告诉我。