卡牌大师:一个可拖拽的前端小项目
在前端开发的世界里,交互效果总是能给用户带来更好的体验。今天,我将分享一个简单的可拖拽卡牌项目,通过这个项目,你可以学习如何使用GSAP库实现复杂的动画效果,并结合HTML和CSS完成一个有趣的前端小应用。
项目背景
这个项目名为“卡牌大师”,灵感来源于常见的卡牌游戏。用户可以通过拖拽屏幕上的卡牌,将其移动到不同的方向,如上、下、左、右等。每张卡牌在拖拽过程中会根据方向自动调整旋转角度,并最终移动到指定的位置。
技术栈
- HTML: 用于构建页面结构。
- CSS: 用于样式设计,包括布局、动画效果等。
- JavaScript: 用于实现拖拽功能和动画控制。
- GSAP: 一个强大的动画库,用于实现平滑的动画效果。
<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();
});
关键点解析
-
HTML 结构:
- 使用
<div>元素构建卡牌容器和卡牌本身。 - 通过
id和class属性进行样式和脚本的关联。
- 使用
-
CSS 样式:
- 使用 Flexbox 布局确保卡牌居中显示。
- 通过
z-index控制卡牌的层级关系。 - 使用
transform和transition实现卡牌的初始位置和拖拽过程中的动画效果。
-
JavaScript 逻辑:
- 使用 GSAP 的
Draggable插件实现卡牌的拖拽功能。 - 通过
onPress、onDrag和onDragEnd回调函数控制拖拽过程中的行为。 - 使用
getDragDirection函数判断拖拽方向,并根据方向移动卡牌到目标位置。
- 使用 GSAP 的
-
动画效果:
- 使用 GSAP 的
to方法实现平滑的动画效果。 - 通过
rotation属性实现卡牌在拖拽过程中的旋转效果。
- 使用 GSAP 的
总结
通过这个项目,我们可以学习到如何使用 GSAP 实现复杂的动画效果,以及如何结合 HTML 和 CSS 完成一个交互性强的前端应用。希望这个项目能给你带来启发,激发你在前端开发中的创造力。如果你有任何问题或建议,欢迎在评论区留言交流!
希望这篇文章对你有所帮助!如果有任何问题或需要进一步的解释,请随时告诉我。