À̹ÌÁö ¾Ö´Ï¸ÞÀÌ¼Ç ÆÄÀÏÀ» »ç¿ëÇÏÁö ¾Ê°í HTML°ú 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);
}
}
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>
);
}