08Mở với hiệu ứng đàn hồi
Khi mở, vượt nhẹ qua chiều cao đích rồi nảy lại vào đúng vị trí.
Dùng choAccordion khuyến mãi cần nhấn mạnh, card onboarding
overshoot 6%cubic-bezier닫힘은 즉시

<main class="stage">
<section class="accordion">
<button class="accordion__header" type="button" aria-expanded="false">
<span class="accordion__title">Ưu đãi thành viên mới</span>
<span class="accordion__chevron" aria-hidden="true"></span>
</button>
<div class="accordion__panel is-demo">
<p class="accordion__body is-demo">Tặng mã giảm 10% cho lần mua đầu tiên.</p>
</div>
</section>
</main>@use "../../../../site/static/demo-tokens" as *;
$duration: $dur-enter;
$easing: $ease-pop; // 튕기는 느낌의 오버슈트 곡선
$color: $subject-blue;
@include stage($stage-paper);
.accordion {
width: clamp(220px, 78vw, 340px);
background: $subject-ink;
border-radius: $r-card;
box-shadow: $shadow-raised;
overflow: hidden;
font-family: inherit;
}
.accordion__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
width: 100%;
border: 0;
background: none;
padding: clamp(12px, 3vw, 18px) clamp(14px, 4vw, 20px);
cursor: pointer;
color: $subject-cream;
font-weight: 600;
font-size: clamp(14px, 3.6vw, 17px);
line-height: 1.3;
font-family: inherit;
text-align: left;
}
.accordion__chevron {
flex: none;
width: 9px;
height: 9px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(45deg);
}
.accordion__body {
margin: 0;
padding: 0 clamp(14px, 4vw, 20px) clamp(12px, 3vw, 18px);
color: $subject-cream;
opacity: .86;
font-weight: 400;
font-size: clamp(12px, 3.2vw, 14.5px);
line-height: 1.5;
font-family: inherit;
}
.accordion__panel {
height: 0;
overflow: hidden;
interpolate-size: allow-keywords;
}
.accordion.is-open .accordion__panel {
height: calc-size(auto, size);
transition: height $duration $easing;
}
@supports not (height: calc-size(auto, size)) {
.accordion.is-open .accordion__panel { height: auto; }
}
// 데모: 열릴 때 안쪽 카드가 살짝 커졌다가($ease-pop) 자리를 잡는다
.accordion__panel.is-demo {
animation: accordion-loop $dur-loop $easing infinite;
}
@keyframes accordion-loop {
0%, 8% { height: 0; }
42%, 58% { height: calc-size(auto, size); }
92%, 100% { height: 0; }
}
@supports not (height: calc-size(auto, size)) {
@keyframes accordion-loop { 0%, 8% { height: 0; } 42%, 58% { height: 220px; } 92%, 100% { height: 0; } }
}
.accordion__body.is-demo {
transform-origin: top center; // 규칙8: 헤더 아래 변 붙어 펼침 팝
animation: bounce-loop $dur-loop $easing infinite;
}
@keyframes bounce-loop {
0%, 8% { transform: scale(1); opacity: 0; }
36% { transform: scale(1.06); opacity: 1; }
50%, 58% { transform: scale(1); opacity: 1; }
92%, 100% { transform: scale(1); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.accordion__panel,
.accordion__panel.is-demo,
.accordion__chevron,
.accordion__body,
.accordion.is-open .accordion__panel {
animation: none !important;
transition: none !important;
}
.accordion__panel.is-demo { height: auto; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffffff;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.accordion {
width: clamp(220px, 78vw, 340px);
background: #17141a;
border-radius: 16px;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
overflow: hidden;
font-family: inherit;
}
.accordion__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
width: 100%;
border: 0;
background: none;
padding: clamp(12px, 3vw, 18px) clamp(14px, 4vw, 20px);
cursor: pointer;
color: #fff7e6;
font-weight: 600;
font-size: clamp(14px, 3.6vw, 17px);
line-height: 1.3;
font-family: inherit;
text-align: left;
}
.accordion__chevron {
flex: none;
width: 9px;
height: 9px;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(45deg);
}
.accordion__body {
margin: 0;
padding: 0 clamp(14px, 4vw, 20px) clamp(12px, 3vw, 18px);
color: #fff7e6;
opacity: 0.86;
font-weight: 400;
font-size: clamp(12px, 3.2vw, 14.5px);
line-height: 1.5;
font-family: inherit;
}
.accordion__panel {
height: 0;
overflow: hidden;
interpolate-size: allow-keywords;
}
.accordion.is-open .accordion__panel {
height: calc-size(auto, size);
transition: height 600ms cubic-bezier(0.2, 1.4, 0.4, 1);
}
@supports not (height: calc-size(auto, size)) {
.accordion.is-open .accordion__panel {
height: auto;
}
}
.accordion__panel.is-demo {
animation: accordion-loop 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes accordion-loop {
0%, 8% {
height: 0;
}
42%, 58% {
height: calc-size(auto, size);
}
92%, 100% {
height: 0;
}
}
@supports not (height: calc-size(auto, size)) {
@keyframes accordion-loop {
0%, 8% {
height: 0;
}
42%, 58% {
height: 220px;
}
92%, 100% {
height: 0;
}
}
}
.accordion__body.is-demo {
transform-origin: top center;
animation: bounce-loop 2s cubic-bezier(0.2, 1.4, 0.4, 1) infinite;
}
@keyframes bounce-loop {
0%, 8% {
transform: scale(1);
opacity: 0;
}
36% {
transform: scale(1.06);
opacity: 1;
}
50%, 58% {
transform: scale(1);
opacity: 1;
}
92%, 100% {
transform: scale(1);
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.accordion__panel,
.accordion__panel.is-demo,
.accordion__chevron,
.accordion__body,
.accordion.is-open .accordion__panel {
animation: none !important;
transition: none !important;
}
.accordion__panel.is-demo {
height: auto;
}
}(function () {
document.querySelectorAll('.accordion').forEach(function (acc) {
var header = acc.querySelector('.accordion__header');
var panel = acc.querySelector('.accordion__panel');
header.addEventListener('click', function () {
panel.classList.remove('is-demo');
var open = acc.classList.toggle('is-open');
header.setAttribute('aria-expanded', String(open));
});
});
})();import { useState } from "react";
import type { CSSProperties, ReactNode } from "react";
import styles from "./AccordionElasticBounce.module.scss";
export type AccordionElasticBounceProps = {
/** 헤더에 보이는 제목 */
header?: ReactNode;
/** 열렸을 때 보이는 본문 */
children?: ReactNode;
/** height:auto 전환 시간 (기본 300ms) */
duration?: number;
/** CSS easing */
easing?: string;
/** 강조 색 */
color?: string;
defaultOpen?: boolean;
};
type Vars = CSSProperties & { "--duration"?: string; "--easing"?: string; "--color"?: string };
export default function AccordionElasticBounce({
header = "New member perks",
children = "Get a 10% coupon on your first purchase.",
duration = 300,
easing = "cubic-bezier(.2,.8,.2,1)",
color = "#2f6df6",
defaultOpen = false,
}: AccordionElasticBounceProps) {
const [open, setOpen] = useState(defaultOpen);
const vars: Vars = { "--duration": `${duration}ms`, "--easing": easing, "--color": color };
return (
<section className={styles.accordion} style={vars}>
<button
type="button"
className={styles.header}
aria-expanded={open}
onClick={() => setOpen((v) => !v)}
>
<span>{header}</span>
<span className={`${styles.chevron} ${open ? styles.chevronOpen : ""}`} aria-hidden="true" />
</button>
<div className={`${styles.panel} ${open ? styles.panelOpen : ""}`}>
<p className={styles.body}>{children}</p>
</div>
</section>
);
}08 Mở với hiệu ứng đàn hồi — Diện tích đo được 19.022% · khung hình chuyển động 15/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 hiệu ứng css accordion animation không JS
Bài viết chứa demo này
9 hiệu ứng css accordion animation không JSCss accordion animation là hiệu ứng chuyển chiều cao nội dung từ 0 sang auto, dựng bằng interpolate-size và calc-size() Xem tất cả demo trong bài này


