| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
- CSS
- 홈페이지
- #웹솔루션
- 웹사이트제작
- 사이트제작
- jquery
- #솔루션
- #jQuery
- javascript
- 해피CGI
- 홈페이지제작
- 웹솔루션
- #image
- 해피씨지아이
- #CSS
- #해피CGI
- #쇼핑몰
- #cgimall
- #홈페이지
- happycgi
- #이미지
- 솔루션
- CGIMALL
- #뉴스
- #업종별
- 이미지
- php
- #happycgi
- 게시판
- #홈페이지제작
- Today
- Total
웹솔루션개발 26년 노하우! 해피CGI의 모든것
[해피CGI][cgimall] GIF 없이 구현하는 동적 애니메이션 버튼 본문
이미지 애니메이션 파일을 사용하지 않고 HTML과 CSS 효과를 활용해 동적인 버튼을 구현했습니다.
마우스를 올리거나 클릭하면 버튼 내부의 그래픽 요소와 배경이 자연스럽게 움직입니다.
문구, 색상, 크기, 애니메이션 속도를 코드에서 간편하게 수정할 수 있습니다.
이벤트 페이지, 신청 화면, 상품 상세 페이지 등 강조가 필요한 버튼에 활용하기 좋습니다.

HTML 구조
<div id="root"></div>
CSS 소스
* {
border: 0;
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--hue: 210;
--gray50: hsl(var(--hue) 10% 95%);
--gray950: hsl(var(--hue) 10% 5%);
--primary500: hsl(var(--hue) 90% 45%);
--trans-dur: 0.3s;
color-scheme: light dark;
font-size: clamp(1.5rem, 1.425rem + 0.375vw, 1.875rem);
}
body,
button {
font: 1em / 1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
body {
background-color: light-dark(var(--gray50), var(--gray950));
color: light-dark(var(--gray950), var(--gray50));
display: grid;
place-items: center;
height: 100vh;
transition:
background-color var(--trans-dur),
color var(--trans-dur);
}
button {
-webkit-tap-highlight-color: transparent;
}
.plane-btn {
background-color: var(--primary500);
border-radius: 0.5em;
color: var(--gray50);
cursor: pointer;
display: block;
overflow: hidden;
padding: 0.5em 1.5em;
position: relative;
transition:
background-color var(--trans-dur),
color var(--trans-dur),
transform var(--trans-dur);
&__cloud {
background-color: hsl(0 0% 100%);
border-radius: 50%;
display: block;
filter: blur(16px);
opacity: 0.7;
height: 2.25em;
&--l {
transform: translateX(-33%);
}
&--r {
margin-left: auto;
transform: translateX(33%);
}
&--sm {
width: 33%;
}
&--md {
width: 50%;
}
&--lg {
width: 67%;
}
}
&__land {
background-color: hsl(120 30% 50%);
border-radius: 33% 67% 70% 30% / 33% 67% 33% 67%;
box-shadow: 0 0 0 0.5em hsla(0 0% 100% / 0.1);
width: 3.5em;
height: 2.25em;
transform: translateX(-25%);
}
&__plane,
&__scroller {
position: absolute;
}
&__plane,
&__text {
transition: transform 0.5s ease-in-out;
}
&__plane {
display: grid;
place-items: center;
font-size: 1.25em;
inset: 0;
transform: translateY(100%);
&::before {
animation: 1.5s plane-move ease-in-out infinite;
content: "✈️";
display: block;
}
}
&__scroller {
display: flex;
flex-direction: column;
inset: 0 0 auto;
padding: 3em 0.75em;
transform: translateY(-100%);
}
&__text {
display: block;
}
&:focus-visible,
&:hover {
transform: scale(1.05);
}
&:active {
transform: scale(0.95);
}
&:focus-visible &__plane,
&:hover &__plane {
transform: translateY(0);
}
&:focus-visible &__scroller,
&:hover &__scroller {
animation: 3s scroller-move linear infinite;
}
&:focus-visible &__text,
&:hover &__text {
transform: translateY(-2.5em);
}
}
// Animations
@keyframes plane-move {
0%,
100% {
transform: translateX(-0.125rem) rotate(-45deg);
}
50% {
transform: translateX(0.125rem) rotate(-45deg);
}
}
@keyframes scroller-move {
0% {
transform: translateY(-100%);
}
100% {
transform: translateY(0);
}
}
JS 소스
import React, { StrictMode } from "https://esm.sh/react";
import { createRoot } from "https://esm.sh/react-dom/client";
import clsx from "https://esm.sh/clsx";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<AirplaneTicketButton />
</StrictMode>
);
interface AirplaneTicketButtonProps {
onclick?: () => void;
}
function AirplaneTicketButton({
onclick
}: Readonly<AirplaneTicketButtonProps>) {
return (
<button
className="plane-btn"
type="button"
onClick={onclick}>
<span className="plane-btn__scroller">
<AirplaneTicketButtonLand />
</span>
<span className="plane-btn__scroller">
<AirplaneTicketButtonCloud position="r" size="lg" />
<AirplaneTicketButtonCloud />
<AirplaneTicketButtonCloud size="sm" />
<AirplaneTicketButtonCloud position="r" size="lg" />
</span>
<span className="plane-btn__plane" aria-hidden="true"></span>
<span className="plane-btn__text">Get Tickets</span>
</button>
);
}
interface AirplaneTicketButtonCloudProps {
position?: "l" | "r";
size?: "sm" | "md" | "lg";
}
function AirplaneTicketButtonCloud({
position = "l",
size = "md"
}: Readonly<AirplaneTicketButtonCloudProps>) {
const _position = `plane-btn__cloud--${position}`;
const _size = `plane-btn__cloud--${size}`;
return (
<span className={clsx("plane-btn__cloud", _position, _size)}></span>
);
}
interface AirplaneTicketButtonLandProps {
position?: "l" | "r";
}
function AirplaneTicketButtonLand({
position = "l"
}: Readonly<AirplaneTicketButtonLandProps>) {
const _position = `plane-btn__land--${position}`;
return (
<span className={clsx("plane-btn__land", _position)}></span>
);
}
'웹프로그램밍 자료실 > 기타 자료' 카테고리의 다른 글
| [해피CGI][cgimall] Fancy multiple select 다중 셀렉트박스 (0) | 2026.09.15 |
|---|---|
| [해피CGI][cgimall] Pure CSS Select Box 커스텀 셀렉트박스 (0) | 2026.09.14 |
| [해피CGI][cgimall] Transition API를 활용한 대시보드 화면 전환 (0) | 2026.09.10 |
| [해피CGI][cgimall] 오버레이 메뉴 애니메이션 Menu animation (0) | 2026.09.09 |
| [해피CGI][cgimall] Uptrends - 40개 이상 글로벌 위치에서 접속 체크 (0) | 2026.09.07 |
