<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>花式扑克洗牌器</title>
<style>
body {
font-family: "Inter", sans-serif;
margin: 0;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vw;
background-color: #f0f0f0;
}
#root {
width: 100%;
height: 100%;
}
.form-radio {
appearance: none;
width: 16px;
height: 16px;
border: 2px solid white;
border-radius: 50%;
outline: none;
cursor: pointer;
}
.form-radio:checked {
background-color: white;
box-shadow: inset 0 0 0 3px #10b981;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lucide@0.487.0/dist/cjs/lucide.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script>
function CardShuffleAnimation() {
const [cards, setCards] = React.useState([]);
const [isShuffling, setIsShuffling] = React.useState(false);
const [isAnimating, setIsAnimating] = React.useState(true);
const [animationSpeed, setAnimationSpeed] = React.useState(1000);
const [shuffleStyle, setShuffleStyle] = React.useState("explosion");
const suits = ["♥", "♦", "♠", "♣"];
const values = [
"A",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"J",
"Q",
"K",
];
React.useEffect(() => {
const deck = [];
suits.forEach((suit) => {
values.forEach((value) => {
deck.push({
id: `${value}-${suit}`,
value,
suit,
color:
suit === "♥" || suit === "♦"
? "text-red-600"
: "text-gray-900",
rotation: Math.random() * 4 - 2,
position: Math.random() * 10 - 5,
zIndex: deck.length,
scale: 1,
translateY: 0,
});
});
});
setCards(deck);
}, []);
const shuffleDeck = () => {
if (isShuffling) return;
setIsShuffling(true);
const shuffleFunctions = {
explosion: performExplosionShuffle,
cascade: performCascadeShuffle,
riffle: performRiffleShuffle,
arc: performArcShuffle,
};
if (shuffleFunctions[shuffleStyle]) {
shuffleFunctions[shuffleStyle]();
}
};
const performExplosionShuffle = () => {
let shuffledCards = [...cards];
shuffledCards = shuffledCards.map((card) => ({
...card,
rotation: Math.random() * 720 - 360,
position: Math.random() * 100 - 50,
zIndex: Math.floor(Math.random() * 52),
scale: 1.2,
translateY: 0,
}));
setCards(shuffledCards);
setTimeout(() => {
shuffledCards = shuffledCards.map((card) => ({
...card,
rotation: Math.random() * 180 - 90,
position: Math.random() * 50 - 25,
scale: 1.1,
translateY: 0,
}));
setCards(shuffledCards);
setTimeout(() => {
shuffledCards = [...shuffledCards]
.sort(() => Math.random() - 0.5)
.map((card, index) => ({
...card,
rotation: Math.random() * 30 - 15,
position: Math.random() * 25 - 12.5,
zIndex: index,
scale: 1.05,
translateY: 0,
}));
setCards(shuffledCards);
setTimeout(() => {
shuffledCards = shuffledCards.map((card, index) => ({
...card,
rotation: Math.random() * 4 - 2,
position: Math.random() * 10 - 5,
zIndex: index,
scale: 1,
translateY: 0,
}));
setCards(shuffledCards);
setTimeout(() => setIsShuffling(false), animationSpeed / 2);
}, animationSpeed);
}, animationSpeed);
}, animationSpeed);
};
const performCascadeShuffle = () => {
let stackedCards = [...cards];
stackedCards = stackedCards.map((card, index) => ({
...card,
rotation: 0,
position: 0,
zIndex: 52 - index,
translateY: 0,
scale: 1,
}));
setCards(stackedCards);
setTimeout(() => {
const splitCards = stackedCards.map((card, index) => {
const xOffset = index % 2 === 0 ? -20 : 20;
const rotation = index % 2 === 0 ? -5 : 5;
const yOffset = index * 2;
return {
...card,
position: xOffset,
rotation: rotation,
translateY: yOffset,
zIndex: 26 - Math.floor(index / 2),
};
});
setCards(splitCards);
setTimeout(() => {
const cascadedCards = [];
let leftIndex = 0;
let rightIndex = 0;
let cardIndex = 0;
const leftHalf = splitCards.filter((_, index) => index % 2 === 0);
const rightHalf = splitCards.filter(
(_, index) => index % 2 !== 0
);
while (
leftIndex < leftHalf.length ||
rightIndex < rightHalf.length
) {
if (leftIndex < leftHalf.length) {
cascadedCards.push({
...leftHalf[leftIndex],
position: 0,
rotation: 0,
translateY: 0,
zIndex: cardIndex++,
transition: `all ${animationSpeed * 0.7}ms ease-in-out`,
});
leftIndex++;
}
if (rightIndex < rightHalf.length) {
cascadedCards.push({
...rightHalf[rightIndex],
position: 0,
rotation: 0,
translateY: 0,
zIndex: cardIndex++,
transition: `all ${animationSpeed * 0.7}ms ease-in-out`,
});
rightIndex++;
}
}
setCards(cascadedCards);
setTimeout(() => {
setIsShuffling(false);
}, animationSpeed);
}, animationSpeed);
}, animationSpeed);
};
const performRiffleShuffle = () => {
const leftHalf = [...cards.slice(0, 26)];
const rightHalf = [...cards.slice(26)];
const initialCards = [
...leftHalf.map((card, index) => ({
...card,
rotation: -20,
position: -15,
zIndex: index,
scale: 1,
translateY: -10,
})),
...rightHalf.map((card, index) => ({
...card,
rotation: 20,
position: 15,
zIndex: index + 26,
scale: 1,
translateY: -10,
})),
];
setCards(initialCards);
setTimeout(() => {
const bentCards = [
...leftHalf.map((card, index) => ({
...card,
rotation: -30,
position: -5,
zIndex: index,
scale: 1,
translateY: index * 0.5,
})),
...rightHalf.map((card, index) => ({
...card,
rotation: 30,
position: 5,
zIndex: index + 26,
scale: 1,
translateY: index * 0.5,
})),
];
setCards(bentCards);
setTimeout(() => {
const riffledCards = [];
let leftIndex = 0;
let rightIndex = 0;
let cardIndex = 0;
let fromLeft = true;
let previousPosition = 0;
while (
leftIndex < leftHalf.length ||
rightIndex < rightHalf.length
) {
const groupSize = Math.floor(Math.random() * 3) + 1;
if (fromLeft && leftIndex < leftHalf.length) {
for (
let i = 0;
i < groupSize && leftIndex < leftHalf.length;
i++
) {
const card = leftHalf[leftIndex];
const newPosition = Math.random() * 3 - 1.5;
riffledCards.push({
...card,
translateY: 0,
position: newPosition,
zIndex: cardIndex++,
});
leftIndex++;
previousPosition = newPosition;
}
} else if (rightIndex < rightHalf.length) {
for (
let i = 0;
i < groupSize && rightIndex < rightHalf.length;
i++
) {
const card = rightHalf[rightIndex];
const newPosition = Math.random() * 3 - 1.5;
riffledCards.push({
...card,
translateY: 0,
position: newPosition,
zIndex: cardIndex++,
});
rightIndex++;
previousPosition = newPosition;
}
} else if (leftIndex < leftHalf.length) {
const card = leftHalf[leftIndex];
const newPosition = Math.random() * 3 - 1.5;
riffledCards.push({
...card,
translateY: 0,
position: newPosition,
zIndex: cardIndex++,
});
leftIndex++;
previousPosition = newPosition;
} else if (rightIndex < rightHalf.length) {
const card = rightHalf[rightIndex];
const newPosition = Math.random() * 3 - 1.5;
riffledCards.push({
...card,
translateY: 0,
position: newPosition,
zIndex: cardIndex++,
});
rightIndex++;
previousPosition = newPosition;
}
fromLeft = !fromLeft;
}
const animatedRiffle = riffledCards.map((card) => ({
...card,
rotation: Math.random() * 1 - 0.5,
scale: 1,
}));
setCards(animatedRiffle);
setTimeout(() => {
const squaredCards = animatedRiffle.map((card, index) => ({
...card,
rotation: 0,
position: 0,
zIndex: index,
scale: 1,
}));
setCards(squaredCards);
setTimeout(() => setIsShuffling(false), animationSpeed / 2);
}, animationSpeed);
}, animationSpeed);
}, animationSpeed);
};
const performArcShuffle = () => {
let splitCards = [...cards];
const half = Math.ceil(splitCards.length / 2);
const leftCards = splitCards.slice(0, half);
const rightCards = splitCards.slice(half);
const initialLeftPositions = leftCards.map((card, index) => ({
...card,
position: -150 - index * 5,
rotation: -40,
zIndex: index,
translateY: -20 + index * 2,
scale: 1.3,
}));
const initialRightPositions = rightCards.map((card, index) => ({
...card,
position: 150 + index * 5,
rotation: 40,
zIndex: half + index,
translateY: -20 + index * 2,
scale: 1.3,
}));
splitCards = [...initialLeftPositions, ...initialRightPositions];
setCards(splitCards);
setTimeout(() => {
const combinedCards = splitCards.map((card, index) => {
const isLeft = index < leftCards.length;
const xOffset = isLeft
? -50 + index * 3
: 50 - (index - leftCards.length) * 3;
const rotationDir = isLeft ? -20 : 20;
return {
...card,
position: xOffset,
rotation: rotationDir,
translateY: -50 * Math.sin(index / 10),
zIndex: isLeft
? 26 - Math.floor(index / 2)
: 26 - Math.floor((52 - index) / 2),
scale: 1.3,
};
});
setCards(combinedCards);
setTimeout(() => {
const finalCards = combinedCards.map((card, index) => ({
...card,
position: 0,
rotation: 0,
translateY: 0,
zIndex: index,
scale: 1,
}));
setCards(finalCards);
setTimeout(() => setIsShuffling(false), animationSpeed / 2);
}, animationSpeed);
}, animationSpeed);
};
const resetDeck = () => {
const orderedDeck = [];
suits.forEach((suit) => {
values.forEach((value) => {
orderedDeck.push({
id: `${value}-${suit}`,
value,
suit,
color:
suit === "♥" || suit === "♦"
? "text-red-600"
: "text-gray-900",
rotation: 0,
position: 0,
zIndex: orderedDeck.length,
scale: 1,
translateY: 0,
});
});
});
setCards(orderedDeck);
};
const toggleAnimation = () => {
setIsAnimating(!isAnimating);
};
const handleShuffleStyleChange = (e) => {
setShuffleStyle(e.target.value);
};
return React.createElement(
"div",
{
style: {
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
width: "100%",
height: "100%",
background: "linear-gradient(to bottom right, #047857, #065f46)",
padding: "1rem",
borderRadius: "0.5rem",
boxShadow:
"0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)",
},
} ,
React.createElement(
"h1",
{
style: {
fontSize: "1.5rem",
fontWeight: "bold",
color: "white",
marginBottom: "1rem",
},
},
"✨ 花式扑克洗牌器 ✨"
) ,
React.createElement(
"div",
{
style: {
position: "relative",
height: "16rem",
width: "16rem",
marginBottom: "1.5rem",
},
},
cards.map((card ) =>
React.createElement(
"div",
{
key: card.id,
style: {
position: "absolute",
backgroundColor: "white",
borderRadius: "0.5rem",
boxShadow:
"0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08)",
width: "4rem",
height: "6rem",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
padding: "0.25rem",
transform: `translate(${card.position}px, ${card.translateY}px) rotate(${card.rotation}deg) scale(${card.scale})`,
zIndex: card.zIndex,
left: "calc(50% - 2rem)",
top: "calc(50% - 3rem)",
transition: isAnimating
? `all ${animationSpeed}ms ease-in-out`
: "none",
},
} ,
React.createElement(
"div",
{
style: {
fontSize: "1.125rem",
fontWeight: "bold",
color:
card.color === "text-red-600" ? "#dc2626" : "#111827",
alignSelf: "flex-start",
},
},
card.value
) ,
React.createElement(
"div",
{
style: {
fontSize: "1.5rem",
color:
card.color === "text-red-600" ? "#dc2626" : "#111827",
alignSelf: "center",
},
},
card.suit
) ,
React.createElement(
"div",
{
style: {
fontSize: "1.125rem",
fontWeight: "bold",
color:
card.color === "text-red-600" ? "#dc2626" : "#111827",
alignSelf: "flex-end",
transform: "rotate(180deg)",
},
},
card.value
)
)
)
) ,
React.createElement(
"div",
{ style: { marginBottom: "1rem" } } ,
React.createElement(
"label",
{
style: {
display: "block",
color: "white",
fontSize: "0.875rem",
fontWeight: "bold",
marginBottom: "0.5rem",
},
},
"洗牌样式:"
) ,
React.createElement(
"div",
{ style: { display: "flex", gap: "0.5rem" } } ,
React.createElement(
"label",
{
style: { display: "inline-flex", alignItems: "center" },
} ,
React.createElement("input", {
type: "radio",
className: "form-radio h-5 w-5 text-green-600",
name: "shuffleStyle",
value: "explosion",
checked: shuffleStyle === "explosion",
onChange: handleShuffleStyleChange,
}) ,
React.createElement(
"span",
{ style: { marginLeft: "0.5rem", color: "white" } },
"爆炸式"
)
) ,
React.createElement(
"label",
{
style: { display: "inline-flex", alignItems: "center" },
} ,
React.createElement("input", {
type: "radio",
className: "form-radio h-5 w-5 text-green-600",
name: "shuffleStyle",
value: "cascade",
checked: shuffleStyle === "cascade",
onChange: handleShuffleStyleChange,
}) ,
React.createElement(
"span",
{ style: { marginLeft: "0.5rem", color: "white" } },
"瀑布式"
)
) ,
React.createElement(
"label",
{
style: { display: "inline-flex", alignItems: "center" },
} ,
React.createElement("input", {
type: "radio",
className: "form-radio h-5 w-5 text-green-600",
name: "shuffleStyle",
value: "riffle",
checked: shuffleStyle === "riffle",
onChange: handleShuffleStyleChange,
}) ,
React.createElement(
"span",
{ style: { marginLeft: "0.5rem", color: "white" } },
"交叉式"
)
) ,
React.createElement(
"label",
{
style: { display: "inline-flex", alignItems: "center" },
} ,
React.createElement("input", {
type: "radio",
className: "form-radio h-5 w-5 text-green-600",
name: "shuffleStyle",
value: "arc",
checked: shuffleStyle === "arc",
onChange: handleShuffleStyleChange,
}) ,
React.createElement(
"span",
{ style: { marginLeft: "0.5rem", color: "white" } },
"弧形"
)
)
)
) ,
React.createElement(
"div",
{ style: { display: "flex", gap: "1rem" } } ,
React.createElement(
"button",
{
onClick: shuffleDeck,
disabled: isShuffling,
className:
"bg-white text-green-600 font-bold py-2 px-4 rounded-full shadow-md flex items-center disabled:opacity-50",
},
isShuffling ? "洗牌中..." : "洗牌"
) ,
React.createElement(
"button",
{
onClick: resetDeck,
className:
"bg-white text-green-600 font-bold py-2 px-4 rounded-full shadow-md flex items-center cursor-pointer",
},
"重置"
)
)
);
}
ReactDOM.render(
React.createElement(CardShuffleAnimation, null),
document.getElementById("root")
);
</script>
</body>
</html>