09Ghim tin nhắn mới ở đáy
Tin nhắn mới trượt lên từ dưới bằng translateY, chỉ khi khung chat đã cuộn sát đáy.
Dùng choNhận tin nhắn mới trong phòng chat thời gian thực, khung chat livestream
@keyframes pin-in-looptranslateY(46px)조건부 자동스크롤

<main class="stage">
<div class="thread">
<div class="bubble bubble--in">Được đó</div>
<div class="bubble bubble--out is-demo">Đi đây!</div>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────────
$duration: $dur-base; // 새 메시지가 하단까지 밀려 올라오는 데 걸리는 시간
$easing: $ease-out;
$color: $subject-cream; // 새 메시지 말풍선 색 (ink 무대라 cream)
@include stage($stage-ink);
.thread {
display: grid;
gap: 10px;
justify-items: end;
width: min(220px, 78vw);
}
.bubble {
padding: 12px 18px;
border-radius: $r-card;
font-weight: 600; font-size: clamp(13px, 4vw, 15px); line-height: 1.4; font-family: inherit;
max-width: 80%;
}
.bubble--in {
justify-self: start;
background: rgba(255, 255, 255, .1);
color: #fff;
}
.bubble--out {
background: $color;
color: $stage-ink;
transform-origin: bottom center; // 규칙8: 순수 세로 이동, origin 무의미
&.is-demo { animation: pin-in-loop $dur-loop $easing infinite; }
}
// 스크롤이 바닥에 붙어 있을 때만 새 메시지가 아래에서 밀려 올라와 자리를 잡는다
@keyframes pin-in-loop {
0% { transform: translateY(46px); opacity: 0; }
28% { transform: translateY(0); opacity: 1; }
84% { transform: translateY(0); opacity: 1; }
100% { transform: translateY(46px); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.bubble--out.is-demo { animation: none !important; transform: none; opacity: 1; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #17141a;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.thread {
display: grid;
gap: 10px;
justify-items: end;
width: min(220px, 78vw);
}
.bubble {
padding: 12px 18px;
border-radius: 16px;
font-weight: 600;
font-size: clamp(13px, 4vw, 15px);
line-height: 1.4;
font-family: inherit;
max-width: 80%;
}
.bubble--in {
justify-self: start;
background: rgba(255, 255, 255, 0.1);
color: #fff;
}
.bubble--out {
background: #fff7e6;
color: #17141a;
transform-origin: bottom center;
}
.bubble--out.is-demo {
animation: pin-in-loop 2s ease-out infinite;
}
@keyframes pin-in-loop {
0% {
transform: translateY(46px);
opacity: 0;
}
28% {
transform: translateY(0);
opacity: 1;
}
84% {
transform: translateY(0);
opacity: 1;
}
100% {
transform: translateY(46px);
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.bubble--out.is-demo {
animation: none !important;
transform: none;
opacity: 1;
}
}import type { CSSProperties, ReactNode } from "react";
import styles from "./ScrollPinNewMessage.module.scss";
export type ScrollPinNewMessageProps = {
children: ReactNode;
/** 하단까지 밀려 올라오는 데 걸리는 시간 (기본 300ms) */
duration?: number;
easing?: string;
className?: string;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string };
export default function ScrollPinNewMessage({
children,
duration = 300,
easing = "ease-out",
className = "",
}: ScrollPinNewMessageProps) {
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing };
return (
<div className={`${styles.bubble} ${styles.enter} ${className}`} style={vars}>
{children}
</div>
);
}09 Ghim tin nhắn mới ở đáy — Diện tích đo được 3.932% · khung hình chuyển động 10/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 CSS Chat Bubble Animation — dán 1 dòng là chạy
Bài viết chứa demo này
9 CSS Chat Bubble Animation — dán 1 dòng là chạyCSS chat bubble animation là hiệu ứng bong bóng chat chạy bằng CSS thuần khi tin nhắn xuất hiện hoặc được xem. Chín kiểu Xem tất cả demo trong bài này


