Giới thiệu
Comment box (speech bubble) với mũi tên chỉ về người nói là UI pattern phổ biến cho chat, testimonial, feedback. clip-path cho phép tạo hình dạng phức tạp này chỉ với CSS, không cần hình ảnh.
Nguyên lý
- Box chính: Bo góc, padding, background.
- Mũi tên (tail): Tạo bằng
::afterpseudo-element, cắt hình tam giác bằngclip-path: polygon(...). - Vị trí mũi tên:
position: absolute, định vịbottom/left/right.
Ví dụ 1: Chat bubbles 2 bên (Left/Right aligned)
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Comment Box with clip-path</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; background: #f0f2f5; padding: 2rem; display: flex; justify-content: center; min-height: 100vh; }
.chatContainer { width: 100%; max-width: 600px; display: flex; flex-direction: column; gap: 1rem; }
.bubble {
position: relative;
max-width: 75%;
padding: 1rem 1.5rem;
border-radius: 20px;
font-size: 1rem;
line-height: 1.5;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
animation: fadeInUp 0.3s ease;
}
@keyframes fadeInUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
/* Incoming (bên trái) */
.bubble.incoming {
align-self: flex-start;
background: white;
border-bottom-left-radius: 4px; /* Góc bo nhỏ cho mũi tên */
}
.bubble.incoming::after {
content: "";
position: absolute;
bottom: 0; left: -12px;
width: 20px; height: 20px;
background: white;
clip-path: polygon(0 0, 100% 0, 0 100%); /* Tam giác vuông góc trái dưới */
}
/* Outgoing (bên phải) */
.bubble.outgoing {
align-self: flex-end;
background: linear-gradient(135deg, #6a1b9a, #8e24aa);
color: white;
border-bottom-right-radius: 4px;
}
.bubble.outgoing::after {
content: "";
position: absolute;
bottom: 0; right: -12px;
width: 20px; height: 20px;
background: #8e24aa; /* Màu khớp gradient cuối */
clip-path: polygon(100% 0, 100% 100%, 0 100%); /* Tam giác vuông góc phải dưới */
}
.sender { font-size: 0.8rem; font-weight: 600; margin-bottom: 0.25rem; opacity: 0.7; }
.time { font-size: 0.7rem; opacity: 0.6; text-align: right; margin-top: 0.5rem; }
</style>
</head>
<body>
<div class="chatContainer">
<div class="bubble incoming">
<div class="sender">Jane Doe</div>
<p>Chào bạn! Cảm ơn bạn đã quan tâm đến sản phẩm của chúng tôi.</p>
<div class="time">10:30 AM</div>
</div>
<div class="bubble outgoing">
<p>Tôi muốn hỏi về chính sách đổi trả.</p>
<div class="time">10:31 AM</div>
</div>
<div class="bubble incoming">
<div class="sender">Support Bot</div>
<p>Chính sách đổi trả trong 30 ngày, sản phẩm phải còn nguyên seal. Bạn cần hỗ trợ thêm không?</p>
<div class="time">10:32 AM</div>
</div>
</div>
</body>
</html>
Ví dụ 2: Comment Box với mũi tên tùy chỉnh (Top/Bottom/Center)
.commentBox {
position: relative;
background: white;
border-radius: 12px;
padding: 1.5rem;
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
max-width: 400px;
}
.commentBox::after {
content: "";
position: absolute;
width: 24px; height: 24px;
background: white;
box-shadow: 4px 4px 8px rgba(0,0,0,0.1); /* Shadow cho mũi tên */
}
/* Mũi tên dưới giữa */
.commentBox.bottom::after {
bottom: -12px; left: 50%; transform: translateX(-50%);
clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
/* Mũi tên trên giữa */
.commentBox.top::after {
top: -12px; left: 50%; transform: translateX(-50%);
clip-path: polygon(0 0, 100% 0, 50% 100%);
}
/* Mũi tên trái giữa */
.commentBox.left::after {
left: -12px; top: 50%; transform: translateY(-50%);
clip-path: polygon(0 50%, 100% 0, 100% 100%);
}
/* Mũi tên phải giữa */
.commentBox.right::after {
right: -12px; top: 50%; transform: translateY(-50%);
clip-path: polygon(0 0, 100% 50%, 0 100%);
}
Ví dụ 3: Tooltip / Popover với clip-path
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltipText {
visibility: hidden; opacity: 0;
position: absolute; bottom: 120%; left: 50%; transform: translateX(-50%);
background: #2c3e50; color: white; padding: 0.75rem 1rem;
border-radius: 8px; white-space: nowrap; font-size: 0.85rem;
clip-path: polygon(0 0, 100% 0, 100% 85%, 50% 100%, 0 85%);
transition: opacity 0.2s, visibility 0.2s;
z-index: 10;
}
.tooltip:hover .tooltipText { visibility: visible; opacity: 1; }
Mũi tên tooltip là phần polygon(0 0, 100% 0, 100% 85%, 50% 100%, 0 85%) - hình thang cắt đỉnh giữa.
So sánh: clip-path vs Border Triangle (Cách cũ)
| Tiêu chí | Border Triangle | clip-path |
|---|---|---|
| Box-shadow trên mũi tên | Không thể (border không có shadow) | Có thể (drop-shadow trên pseudo) |
| Gradient background | Không thể | Có thể |
| Border radius trên box chính | Khó khăn (mũi tên là border riêng) | Dễ dàng, độc lập |
| Responsive size | Cứng nhắc (px) | Linh hoạt (% based) |
Lưu ý
drop-shadow()filter trên pseudo-element tạo shadow cho cả hình clip-path.- Màu background của
::afterphải khớp với box chính (hoặc điểm cuối gradient). - Trên mobile, đảm bảo touch target đủ lớn (min 44x44px).