html
抛硬币
css
.tip-button {
background: none;
border: 0;
/* border-radius: 0.25rem 0.25rem 0 0; */
cursor: pointer;
font-family: "Quicksand", sans-serif;
font-size: 0.75rem;
font-weight: 600;
height: 2.6rem;
margin-bottom: -4rem;
outline: 0;
position: relative;
top: 0;
transform-origin: 0% 100%;
transition: transform 50ms ease-in-out;
width: 9.5rem;
-webkit-tap-highlight-color: transparent;
}
/* 按钮滚动 */
.tip-button:active {
transform: rotate(4deg);
}
.tip-button.clicked {
animation: 150ms ease-in-out 1 shake;
pointer-events: none;
}
.tip-button.clicked .tip-button__text {
opacity: 0;
transition: opacity 100ms linear 200ms;
}
.tip-button.clicked::before {
height: 0.5rem;
width: 60%;
}
.tip-button.clicked .coin {
transition: margin-bottom 1s linear 200ms;
margin-bottom: 0;
}
.tip-button.shrink-landing::before {
transition: width 200ms ease-in;
width: 0;
}
.tip-button.coin-landed::after {
opacity: 1;
transform: scale(1);
transform-origin: 50% 100%;
}
.tip-button.coin-landed .coin-wrapper {
background: radial-gradient(circle at 35% 97%,
rgba(3, 16, 50, 0.4) 0.04rem,
transparent 0.04rem),
radial-gradient(circle at 45% 92%,
rgba(3, 16, 50, 0.4) 0.04rem,
transparent 0.02rem),
radial-gradient(circle at 55% 98%,
rgba(3, 16, 50, 0.4) 0.04rem,
transparent 0.04rem),
radial-gradient(circle at 65% 96%,
rgba(3, 16, 50, 0.4) 0.06rem,
transparent 0.06rem);
background-position: center bottom;
background-size: 100%;
bottom: -1rem;
opacity: 0;
transform: scale(2) translateY(-10px);
}
.tip-button__text {
color: #fff;
margin-right: 1.8rem;
opacity: 1;
position: relative;
transition: opacity 100ms linear 500ms;
z-index: 3;
}
.tip-button::before {
background: #031032;
border-radius: 0.25rem;
bottom: 0;
content: "";
display: block;
height: 100%;
left: 50%;
position: absolute;
transform: translateX(-50%);
transition: height 250ms ease-in-out 400ms, width 250ms ease-in-out 300ms;
width: 100%;
z-index: 2;
}
.tip-button::after {
bottom: -1rem;
color: #031032;
content: attr(content);
height: 110%;
left: 0;
opacity: 0;
position: absolute;
pointer-events: none;
text-align: center;
transform: scale(0);
transform-origin: 50% 20%;
transition: transform 200ms cubic-bezier(0, 0, 0.35, 1.43);
width: 100%;
z-index: 1;
}
.coin-wrapper {
background: none;
bottom: 0;
height: 18rem;
left: 0;
opacity: 1;
overflow: hidden;
pointer-events: none;
position: absolute;
transform: none;
transform-origin: 50% 100%;
transition: opacity 200ms linear 100ms, transform 300ms ease-out;
width: 100%;
}
.coin {
--front-y-multiplier: 0;
--back-y-multiplier: 0;
--coin-y-multiplier: 0;
--coin-x-multiplier: 0;
--coin-scale-multiplier: 0;
--coin-rotation-multiplier: 0;
--shine-opacity-multiplier: 0.4;
--shine-bg-multiplier: 50%;
bottom: calc(var(--coin-y-multiplier) * 1rem - 3.5rem);
height: 3.5rem;
margin-bottom: 3.05rem;
position: absolute;
right: calc(var(--coin-x-multiplier) * 34% + 16%);
transform: translateX(50%) scale(calc(0.4 + var(--coin-scale-multiplier))) rotate(calc(var(--coin-rotation-multiplier) * -1deg));
transition: opacity 100ms linear 200ms;
width: 3.5rem;
z-index: 3;
}
.coin__front,
.coin__middle,
.coin__back,
.coin::before,
.coin__front::after,
.coin__back::after {
border-radius: 50%;
box-sizing: border-box;
height: 100%;
left: 0;
position: absolute;
width: 100%;
z-index: 3;
}
.coin__front {
background: radial-gradient(circle at 50% 50%,
transparent 50%,
rgba(115, 124, 153, 0.4) 54%,
#c2cadf 54%),
linear-gradient(210deg, #8590b3 32%, transparent 32%),
linear-gradient(150deg, #8590b3 32%, transparent 32%),
linear-gradient(to right,
#8590b3 22%,
transparent 22%,
transparent 78%,
#8590b3 78%),
linear-gradient(to bottom,
#fcfaf9 44%,
transparent 44%,
transparent 65%,
#fcfaf9 65%,
#fcfaf9 71%,
#8590b3 71%),
linear-gradient(to right,
transparent 28%,
#fcfaf9 28%,
#fcfaf9 34%,
#8590b3 34%,
#8590b3 40%,
#fcfaf9 40%,
#fcfaf9 47%,
#8590b3 47%,
#8590b3 53%,
#fcfaf9 53%,
#fcfaf9 60%,
#8590b3 60%,
#8590b3 66%,
#fcfaf9 66%,
#fcfaf9 72%,
transparent 72%);
background-color: #8590b3;
background-size: 100% 100%;
transform: translateY(calc(var(--front-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--front-scale-multiplier));
}
.coin__front::after {
background: rgba(0, 0, 0, 0.2);
content: "";
opacity: var(--front-y-multiplier);
}
.coin__middle {
background: #737c99;
transform: translateY(calc(var(--middle-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--middle-scale-multiplier));
}
.coin__back {
background: radial-gradient(circle at 50% 50%,
transparent 50%,
rgba(115, 124, 153, 0.4) 54%,
#c2cadf 54%),
radial-gradient(circle at 50% 40%, #fcfaf9 23%, transparent 23%),
radial-gradient(circle at 50% 100%, #fcfaf9 35%, transparent 35%);
background-color: #8590b3;
background-size: 100% 100%;
transform: translateY(calc(var(--back-y-multiplier) * 0.3181818182rem / 2)) scaleY(var(--back-scale-multiplier));
}
.coin__back::after {
background: rgba(0, 0, 0, 0.2);
content: "";
opacity: var(--back-y-multiplier);
}
.coin::before {
background: radial-gradient(circle at 25% 65%,
transparent 50%,
rgba(255, 255, 255, 0.9) 90%),
linear-gradient(55deg,
transparent calc(var(--shine-bg-multiplier) + 0%),
#e9f4ff calc(var(--shine-bg-multiplier) + 0%),
transparent calc(var(--shine-bg-multiplier) + 50%));
content: "";
opacity: var(--shine-opacity-multiplier);
transform: translateY(calc(var(--middle-y-multiplier) * 0.3181818182rem / -2)) scaleY(var(--middle-scale-multiplier)) rotate(calc(var(--coin-rotation-multiplier) * 1deg));
z-index: 10;
}
.coin::after {
background: #737c99;
content: "";
height: 0.3181818182rem;
left: 0;
position: absolute;
top: 50%;
transform: translateY(-50%);
width: 100%;
z-index: 2;
}
@keyframes shake {
0% {
transform: rotate(4deg);
}
66% {
transform: rotate(-4deg);
}
100% {
transform: rotate();
}
}
/********* BODY STYLES *********/
html,
body {
height: 100%;
}
body {
align-items: center;
background: #f4f7ff;
display: flex;
justify-content: center;
-webkit-font-smoothing: antialiased;
}
js
const tipButtons = document.querySelectorAll(".tip-button");
// Loop through all buttons (allows for multiple buttons on page)
//循环浏览所有按钮(允许页面上有多个按钮)
tipButtons.forEach((button) => {
let coin = button.querySelector(".coin");
//数字越大,动画越慢
coin.maxMoveLoopCount = 90;
// 为button绑定click事件
button.addEventListener("click", () => {
const f1 = () => parseInt(Math.random() * 5) + 1;
// 等概率发生器
const f2 = () => {
let c = 0;
do {
c = f1();
} while (c === 3);
// 1、2 => 0 4、5 => 1
return c > 3 ? true : false;
};
const flag = f2();
if (flag) {
button.setAttribute("content", "正面");
} else {
button.setAttribute("content", "反面");
}
// 如果正在被点击
if (button.clicked) return;
// 新增clicked的class
button.classList.add("clicked");
// Wait to start flipping th coin because of the button tilt animation
setTimeout(() => {
// 定义随机反转的速度
coin.sideRotationCount = Math.floor(Math.random() * 5) * 90;
// 最大翻转角度
coin.maxFlipAngle = (Math.floor(Math.random() * 4) + 3) * Math.PI;
button.clicked = true;
flipCoin();
}, 50);
});
const flipCoin = () => {
coin.moveLoopCount = 0; // 移动循环计数
flipCoinLoop();
};
const flipCoinLoop = () => {
coin.moveLoopCount++;
let percentageCompleted = coin.moveLoopCount / coin.maxMoveLoopCount;
// 获取投掷角度
coin.angle = -coin.maxFlipAngle * Math.pow(percentageCompleted - 1, 2) +
coin.maxFlipAngle;
// Calculate the scale and position of the coin moving through the air
// 计算硬币在空中移动的比例和位置
coin.style.setProperty(
"--coin-y-multiplier",
-11 * Math.pow(percentageCompleted * 2 - 1, 4) + 11
);
coin.style.setProperty("--coin-x-multiplier", percentageCompleted);
coin.style.setProperty(
"--coin-scale-multiplier",
percentageCompleted * 0.6
);
coin.style.setProperty(
"--coin-rotation-multiplier",
percentageCompleted * coin.sideRotationCount
);
// Calculate the scale and position values for the different coin faces
// The math uses sin/cos wave functions to similate the circular motion of 3D spin
//计算不同硬币表面的比例和位置值
//数学使用sin/cos波函数来模拟三维自旋的圆周运动
// 前面样式
coin.style.setProperty(
"--front-scale-multiplier",
Math.max(Math.cos(coin.angle), 0)
);
// console.log("===== : 前面", Math.sin(coin.angle));
coin.style.setProperty("--front-y-multiplier", Math.sin(coin.angle));
// 中间样式
coin.style.setProperty(
"--middle-scale-multiplier",
Math.abs(Math.cos(coin.angle), 0)
);
coin.style.setProperty(
"--middle-y-multiplier",
Math.cos((coin.angle + Math.PI / 2) % Math.PI)
);
// 背面样式
coin.style.setProperty(
"--back-scale-multiplier",
Math.max(Math.cos(coin.angle - Math.PI), 0)
);
// console.log("===== : 背面", Math.sin(coin.angle - Math.PI));
coin.style.setProperty(
"--back-y-multiplier",
Math.sin(coin.angle - Math.PI)
);
// 计算光照样式
coin.style.setProperty(
"--shine-opacity-multiplier",
4 * Math.sin((coin.angle + Math.PI / 2) % Math.PI) - 3.2
);
coin.style.setProperty(
"--shine-bg-multiplier",
-40 * (Math.cos((coin.angle + Math.PI / 2) % Math.PI) - 0.5) + "%"
);
// Repeat animation loop
if (coin.moveLoopCount < coin.maxMoveLoopCount) {
if (coin.moveLoopCount === coin.maxMoveLoopCount - 6)
button.classList.add("shrink-landing");
window.requestAnimationFrame(flipCoinLoop);
} else {
button.classList.add("coin-landed");
coin.style.setProperty("opacity", 0);
setTimeout(() => {
button.classList.remove("clicked", "shrink-landing", "coin-landed");
setTimeout(() => {
resetCoin();
}, 300);
}, 1500);
}
};
// 重置按钮
const resetCoin = () => {
coin.style.setProperty("--coin-x-multiplier", 0);
coin.style.setProperty("--coin-scale-multiplier", 0);
coin.style.setProperty("--coin-rotation-multiplier", 0);
coin.style.setProperty("--shine-opacity-multiplier", 0.4);
coin.style.setProperty("--shine-bg-multiplier", "50%");
coin.style.setProperty("opacity", 1);
// Delay to give the reset animation some time before you can click again
setTimeout(() => {
button.clicked = false;
}, 300);
};
});