同步 · dev.to / @markyu
A cleaner CSS heart animation tutorial focused on transform, pseudo-elements, keyframes, and the small mistakes that break simple UI animations.
- 发布日期
- May 22 '24
- 阅读时长
- 2 min read
- 点赞
- 9
Small CSS animations are underrated.
They look like toy demos, but they teach the same things you need for real UI work: transforms, timing, layout, pseudo-elements, and not accidentally triggering expensive repaint work.
Let’s build a heart animation with mostly CSS.
The HTML
Keep the markup boring:
<main class="stage">
<button class="heart-button" aria-label="Like">
<span class="heart"></span>
</button>
</main>The button gives us keyboard accessibility. The span handles the shape.
The CSS Shape
A heart is basically a rotated square with two circles attached.
.stage {
min-height: 100vh;
display: grid;
place-items: center;
background: #111827;
}
.heart-button {
border: 0;
background: transparent;
cursor: pointer;
padding: 48px;
}
.heart {
position: relative;
display: block;
width: 80px;
height: 80px;
background: #ef4444;
transform: rotate(45deg);
}
.heart::before,
.heart::after {
content: "";
position: absolute;
width: 80px;
height: 80px;
border-radius: 50%;
background: #ef4444;
}
.heart::before {
left: -40px;
}
.heart::after {
top: -40px;
}Visual model:
circle + circle
\ /
rotated squareThat is the whole trick.
Add the Pulse
Use transform, not layout properties.
.heart-button:hover .heart,
.heart-button:focus-visible .heart {
animation: pulse 700ms ease-in-out infinite;
}
@keyframes pulse {
0% {
transform: rotate(45deg) scale(1);
}
45% {
transform: rotate(45deg) scale(1.18);
}
100% {
transform: rotate(45deg) scale(1);
}
}The common mistake is forgetting the rotation inside the keyframes. If you animate only scale(), the heart can snap back because transform gets replaced.
Bad:
transform: scale(1.2);Good:
transform: rotate(45deg) scale(1.2);Add a Click State With JavaScript
CSS hover is nice, but click feedback feels better.
const button = document.querySelector(".heart-button");
button.addEventListener("click", () => {
button.classList.toggle("is-liked");
});.heart-button.is-liked .heart,
.heart-button.is-liked .heart::before,
.heart-button.is-liked .heart::after {
background: #fb7185;
}
.heart-button.is-liked .heart {
box-shadow: 0 0 48px rgba(251, 113, 133, 0.55);
}Respect Reduced Motion
This is a small demo, but accessibility still counts.
@media (prefers-reduced-motion: reduce) {
.heart-button:hover .heart,
.heart-button:focus-visible .heart {
animation: none;
}
}I would add this even for playful UI. Not every user wants pulsing motion.
What This Demo Teaches
| Technique | Real UI use |
|---|---|
::before / ::after | icons, badges, decorative layers |
transform | cheap animation |
@keyframes | reusable motion |
focus-visible | keyboard-friendly interaction |
| reduced motion | accessibility polish |
Final Thought
This is not just a heart animation. It is a tiny lab for learning how browser motion behaves.
What small CSS animation taught you more than you expected?
相关阅读
CSS 3D Transform Bugs Usually Come From Perspective
A practical CSS 3D transform guide explaining perspective, rotateX, rotateY, transform-style, backface visibility, and debugging layout.
css
React Loading Screens Are a State Machine Problem
A practical React loading screen guide using hooks, request states, error states, CSS animation, and why spinners alone are not enough.
react
Frontend Linear Data Structures Deep Dive: Arrays, Stacks, Queues, and Linked Lists
The Big Picture Before diving into stacks, queues, and linked lists, it helps to know...
computerscience
原文发布
本文首发于 dev.to,评论与点赞保留在原站。
在 dev.to 继续阅读