05Liên kết ngoài xoay 45 độ
Hover vào mũi tên cạnh liên kết ngoài, nó xoay thêm 45 độ để báo liên kết sẽ mở trong tab mới.
Dùng choDấu nhận biết liên kết ngoài, icon liên kết mở tab mới
rotatetranslatehover

<main class="stage">
<a class="er-link is-demo" href="#" target="_blank" rel="noopener">
<span class="er-link__label">example.com</span>
<span class="er-link__arrow">
<span class="er-link__shape"></span>
<span class="er-link__trail" aria-hidden="true"></span>
</span>
</a>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: $dur-loop; // is-demo 강제 루프 주기. 실제 호버 전환은 아래 260ms 를 쓴다
$easing: $ease-elastic; // 고무줄식 오버슛 — 회전이 45도를 살짝 넘었다 되돌아와 멈춘다
$color: $subject-cream;
@include stage($stage-ink);
.er-link {
display: inline-flex;
align-items: center;
gap: $sp-2;
color: $color;
text-decoration: none;
font: 600 16px/1 -apple-system, "Segoe UI", sans-serif;
}
.er-link__arrow {
position: relative;
display: inline-block;
width: 12px;
height: 12px;
}
.er-link__shape {
position: absolute;
inset: 0;
border-top: 2px solid $color;
border-right: 2px solid $color;
transform: rotate(0deg) translate(0, 0);
transition: transform 260ms $easing;
}
// 트레일 파티클 — 회전하는 화살표 뒤로 흐린 잔상 하나가 따라붙는다
.er-link__trail {
position: absolute;
inset: 0;
border-top: 2px solid $color;
border-right: 2px solid $color;
opacity: 0;
}
// 실사용: 링크에 마우스를 올리면 화살표가 45도 더 돌아가며 밖으로 살짝 빠져나가듯 움직여 "새 창에서 열림"을 암시한다
.er-link:hover .er-link__shape { transform: rotate(45deg) translate(2px, -2px); }
@keyframes er-trail-hover {
0% { opacity: 0; transform: rotate(0deg) translate(0, 0) scale(.7); }
45% { opacity: .4; transform: rotate(30deg) translate(1px, -1px) scale(1); }
100% { opacity: 0; transform: rotate(45deg) translate(2px, -2px) scale(.5); }
}
.er-link:hover .er-link__trail { animation: er-trail-hover 300ms $ease-out forwards; }
// 데모 루프: is-demo 는 :hover 를 흉내 낸 keyframes 로 무한 반복 (헤드리스는 :hover 를 못 건다)
@keyframes er-rotate {
0%, 20% { transform: rotate(0deg) translate(0, 0); }
50%, 70% { transform: rotate(45deg) translate(2px, -2px); }
100% { transform: rotate(0deg) translate(0, 0); }
}
@keyframes er-trail-loop {
0%, 20% { opacity: 0; transform: rotate(0deg) translate(0, 0) scale(.7); }
40% { opacity: .4; transform: rotate(30deg) translate(1px, -1px) scale(1); }
50%, 70% { opacity: 0; transform: rotate(45deg) translate(2px, -2px) scale(.5); }
100% { opacity: 0; transform: rotate(0deg) translate(0, 0) scale(.7); }
}
.er-link.is-demo .er-link__shape { animation: er-rotate $duration $easing infinite; }
.er-link.is-demo .er-link__trail { animation: er-trail-loop $duration $ease-out infinite; }
@media (prefers-reduced-motion: reduce) {
.er-link:hover .er-link__shape,
.er-link.is-demo .er-link__shape {
animation: none !important;
transition: none !important;
transform: rotate(0deg) translate(0, 0) !important;
}
.er-link__trail,
.er-link:hover .er-link__trail,
.er-link.is-demo .er-link__trail {
animation: none !important;
opacity: 0 !important;
}
}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);
}
.er-link {
display: inline-flex;
align-items: center;
gap: 8px;
color: #fff7e6;
text-decoration: none;
font: 600 16px/1 -apple-system, "Segoe UI", sans-serif;
}
.er-link__arrow {
position: relative;
display: inline-block;
width: 12px;
height: 12px;
}
.er-link__shape {
position: absolute;
inset: 0;
border-top: 2px solid #fff7e6;
border-right: 2px solid #fff7e6;
transform: rotate(0deg) translate(0, 0);
transition: transform 260ms cubic-bezier(0.34, 1.56, 0.64, 1);
}
.er-link__trail {
position: absolute;
inset: 0;
border-top: 2px solid #fff7e6;
border-right: 2px solid #fff7e6;
opacity: 0;
}
.er-link:hover .er-link__shape {
transform: rotate(45deg) translate(2px, -2px);
}
@keyframes er-trail-hover {
0% {
opacity: 0;
transform: rotate(0deg) translate(0, 0) scale(0.7);
}
45% {
opacity: 0.4;
transform: rotate(30deg) translate(1px, -1px) scale(1);
}
100% {
opacity: 0;
transform: rotate(45deg) translate(2px, -2px) scale(0.5);
}
}
.er-link:hover .er-link__trail {
animation: er-trail-hover 300ms ease-out forwards;
}
@keyframes er-rotate {
0%, 20% {
transform: rotate(0deg) translate(0, 0);
}
50%, 70% {
transform: rotate(45deg) translate(2px, -2px);
}
100% {
transform: rotate(0deg) translate(0, 0);
}
}
@keyframes er-trail-loop {
0%, 20% {
opacity: 0;
transform: rotate(0deg) translate(0, 0) scale(0.7);
}
40% {
opacity: 0.4;
transform: rotate(30deg) translate(1px, -1px) scale(1);
}
50%, 70% {
opacity: 0;
transform: rotate(45deg) translate(2px, -2px) scale(0.5);
}
100% {
opacity: 0;
transform: rotate(0deg) translate(0, 0) scale(0.7);
}
}
.er-link.is-demo .er-link__shape {
animation: er-rotate 2s cubic-bezier(0.34, 1.56, 0.64, 1) infinite;
}
.er-link.is-demo .er-link__trail {
animation: er-trail-loop 2s ease-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.er-link:hover .er-link__shape,
.er-link.is-demo .er-link__shape {
animation: none !important;
transition: none !important;
transform: rotate(0deg) translate(0, 0) !important;
}
.er-link__trail,
.er-link:hover .er-link__trail,
.er-link.is-demo .er-link__trail {
animation: none !important;
opacity: 0 !important;
}
}import { CSSProperties } from "react";
import styles from "./ExternalLinkRotate.module.scss";
export interface ExternalLinkRotateProps {
duration?: string;
easing?: string;
color?: string;
}
export default function ExternalLinkRotate({
duration = "260ms",
easing = "cubic-bezier(.34, 1.56, .64, 1)",
color = "#fff7e6",
}: ExternalLinkRotateProps) {
const style = { "--duration": duration, "--easing": easing, "--color": color } as CSSProperties;
return (
<a className={styles.link} href="#" target="_blank" rel="noopener" style={style}>
<span>example.com</span>
<span className={styles.arrow}>
<span className={styles.shape} />
<span className={styles.trail} aria-hidden="true" />
</span>
</a>
);
}05 Liên kết ngoài xoay 45 độ — Diện tích đo được 0.103% · khung hình chuyển động 15/23Nguyên tác GODRICH · SCSS và React nằm trong tệp zip · 9 css arrow animation — dán một dòng là chạy
Bài viết chứa demo này
9 css arrow animation — dán một dòng là chạycss arrow animation là mũi tên tự chuyển động trên trang: hover thì thân tên dài ra, hoặc nó nảy lên như giục bạn bấm nú Xem tất cả demo trong bài này


