Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> CSS

Cách tạo Comment Box (Hộp bình luận) với CSS clip-path

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 ::after pseudo-element, cắt hình tam giác bằng clip-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ênKhông thể (border không có shadow)Có thể (drop-shadow trên pseudo)
Gradient backgroundKhông thểCó thể
Border radius trên box chínhKhó khăn (mũi tên là border riêng)Dễ dàng, độc lập
Responsive sizeCứ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 ::after phả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).