如何基于 Tailwind CSS 实现卡片翻转效果?

发布日期:2023-08-27 修改时间:2023-08-27 阅读所需:4 分钟
tailwind css
react
css

最近重新复习了一下在 CSS 中有关于 Transform(变换)相关的属性以及用法,这里就简单介绍一下如何通过核心代码来实现一个卡片正反面翻转效果。

flippable card

关键要点

容器处理

  • 设置相对定位 position: relative;
  • 设置透视距离 perspective: 1000px; 并开启 3d 变换效果 transform-style: preserve-3d;
  • 设置过渡动画及延时 transition: transform 700ms; 让翻转效果更佳明显;
  • 设置 Y 轴角度旋转,如当鼠标悬停或点击时翻转 180 度 transform: rotateY(180deg);

卡片正面(Front Face)和反面(Back Face)的处理

  • 正反面子元素统一设置绝对定位 position: absolute;
  • 为正反面子元素设置在透视时隐藏的 backface-visiblity: hidden; 属性
  • 将卡片背面(Back Face)设置 Y 轴旋转 180 度效果 transform: rotateY(180deg);,而卡片正面(front face)由于是默认展示则不需要设置旋转,而由于背面已经事先旋转了 180 度,于是容器从正面切换到背面时,背面相当于旋转了 360 度此时呈现的就是正面

为了方便实现翻转状态管理,这里我使用了 React 并搭配 Tailwind CSS 来完成最终实现。

实现

除了 ReactTailwind CSS 之外还额外使用了 tailwind-merge 用于帮我根据状态切换不同的样式。

React 与 Tailwind CSS

这里我主要通过 Vite 来帮助快速搭建一个简单的脚手架,然后安装相关依赖即可:

Terminal window
pnpm i
pnpm add -D tailwind-merge

Tailwind CSS 设置可参考 官方文档

最终实现如下:

FlippableCard.tsx
import { useState } from "react";
import { twMerge as tw } from "tailwind-merge";
export default function FlippableCard() {
const [hasFlipped, setHasFilpped] = useState(false);
return (
<div className="flex items-center justify-center min-h-screen">
<div
className={tw(
"relative w-[240px] h-[360px] cursor-pointer",
"transition-transform duration-700 perspective-1000",
"transform-style-3d perspective-origin-center",
hasFlipped && "rotate-y-180"
)}
onClick={() => setHasFilpped(!hasFlipped)}
>
<div
className={tw(
"absolute inset-0 top-0 left-0",
"flex flex-col items-center justify-center w-full h-full p-2",
"rounded-lg shadow-[0_10px_30px_-15px_rgba(0,0,0,0.2)]",
"bg-gradient-to-b from-sky-200/50 via-purple-100 to-slate-50 ",
"backdrop-blur-lg backface-hidden"
)}
>
<div className="font-mono text-lg">Front Face</div>
</div>
<div
className={tw(
"absolute inset-0 top-0 left-0",
"flex flex-col items-center justify-center w-full h-full p-2",
"rounded-lg shadow-[0_10px_30px_-15px_rgba(0,0,0,0.2)]",
"bg-gradient-to-b from-sky-200/50 via-purple-100 to-slate-50 ",
"backdrop-blur-lg backface-hidden rotate-y-180"
)}
>
<div className="font-mono text-lg">Back Face</div>
</div>
</div>
</div>
);
}

原生 HTML 与 CSS

当然同样附上对应的原生 HTML5 与 CSS3 代码(基于 ChatGPT 转换):

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
.container {
display: flex;
min-height: 100vh;
min-width: 100vw;
align-items: center;
justify-content: center;
}
.card {
position: relative;
width: 240px;
height: 360px;
cursor: pointer;
transition: transform 700ms;
transform-style: preserve-3d;
perspective: 1000px;
perspective-origin: center;
}
.card.is-flipped {
transform: rotateY(180deg);
}
.front-face,
.back-face {
position: absolute;
inset: 0;
top: 0;
left: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
padding: 2px;
border-radius: 8px;
box-shadow: 0 10px 30px -15px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(8px);
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
background-image: linear-gradient(to bottom, rgba(135, 206, 235, 0.5),
rgba(147, 112, 219, 0.5), rgba(119, 128, 144, 0.5));
}
.back-face {
transform: rotateY(180deg);
}
.text {
font-family: monospace;
font-size: 18px;
}
</style>
</head>
<body>
<div class="container">
<div id="card" class="card">
<div class="front-face">
<div class="text">Front Face</div>
</div>
<div class="back-face">
<div class="text">Back Face</div>
</div>
</div>
</div>
<script>
const card = document.getElementById("card");
let isFlipped = false;
card.addEventListener("click", () => {
isFlipped = !isFlipped;
card.classList.toggle("is-flipped", isFlipped);
});
</script>
</body>
</html>