06Rotating text
The words "SCROLL TO TOP" curve around the button's edge and slowly spin forever while the center arrow stays still.
Best forSites with a strong brand personality, portfolio footer buttons
SVG textPathrotate8s

<main class="stage">
<button type="button" class="totop" aria-label="Back to top">
<svg class="totop__ring is-demo" viewBox="0 0 64 64" aria-hidden="true">
<defs>
<path id="ring-path" d="M32,32 m-24,0 a24,24 0 1,1 48,0 a24,24 0 1,1 -48,0"/>
</defs>
<text class="totop__ring-text">
<textPath href="#ring-path" startOffset="0%">SCROLL TO TOP · SCROLL TO TOP · </textPath>
</text>
</svg>
<svg class="totop__arrow" viewBox="0 0 24 24" aria-hidden="true"><polyline points="5,15 12,8 19,15"/></svg>
</button>
</main>@use "../../../../site/static/demo-tokens" as *;
// ── 변수 3개 ──────────────────────────────────────────
$duration: 8s;
$easing: linear;
$color: $subject-ink; // 글자 색
@include stage($stage-yellow);
.totop {
position: relative;
width: 72px; height: 72px;
border: 0;
border-radius: $r-pill;
background: transparent;
display: grid;
place-items: center;
cursor: pointer;
}
.totop__ring {
position: absolute;
inset: 0;
width: 72px; height: 72px;
transform-origin: center;
}
// 상시 반복 — 실제 사용도 버튼이 떠 있는 동안 계속 돈다
.totop__ring.is-demo { animation: ring-spin $duration $easing infinite; }
@keyframes ring-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.totop__ring-text {
font: 700 6.4px/1 -apple-system, "Segoe UI", sans-serif;
letter-spacing: .12em;
fill: $color;
}
.totop__arrow {
position: relative;
display: block;
width: 22px; height: 22px;
fill: none;
stroke: $subject-ink;
stroke-width: 2.4;
stroke-linecap: round;
stroke-linejoin: round;
background: $stage-paper;
border-radius: $r-pill;
padding: $sp-2;
box-shadow: $shadow-raised;
box-sizing: content-box;
}
@media (prefers-reduced-motion: reduce) {
.totop__ring.is-demo { animation: none; }
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffd23f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.totop {
position: relative;
width: 72px;
height: 72px;
border: 0;
border-radius: 999px;
background: transparent;
display: grid;
place-items: center;
cursor: pointer;
}
.totop__ring {
position: absolute;
inset: 0;
width: 72px;
height: 72px;
transform-origin: center;
}
.totop__ring.is-demo {
animation: ring-spin 8s linear infinite;
}
@keyframes ring-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.totop__ring-text {
font: 700 6.4px/1 -apple-system, "Segoe UI", sans-serif;
letter-spacing: 0.12em;
fill: #17141a;
}
.totop__arrow {
position: relative;
display: block;
width: 22px;
height: 22px;
fill: none;
stroke: #17141a;
stroke-width: 2.4;
stroke-linecap: round;
stroke-linejoin: round;
background: #ffffff;
border-radius: 999px;
padding: 8px;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
box-sizing: content-box;
}
@media (prefers-reduced-motion: reduce) {
.totop__ring.is-demo {
animation: none;
}
}import type { CSSProperties } from "react";
import styles from "./TextRotateButton.module.scss";
export type TextRotateButtonProps = {
/** 한 바퀴 도는 시간 (기본 8s) */
duration?: number;
label?: string;
/** SVG textPath id — 한 페이지에 여러 개 두면 서로 다른 값을 준다 (기본 "totop-ring-path") */
pathId?: string;
};
type Vars = CSSProperties & { "--duration"?: string };
export default function TextRotateButton({
duration = 8000,
label = "SCROLL TO TOP",
pathId = "totop-ring-path",
}: TextRotateButtonProps) {
const vars: Vars = { "--duration": `${duration}ms` };
const text = `${label} · ${label} · `;
return (
<button
type="button"
className={styles.totop}
style={vars}
aria-label="맨 위로"
onClick={() => window.scrollTo({ top: 0, behavior: "smooth" })}
>
<svg className={styles.ring} viewBox="0 0 64 64" aria-hidden="true">
<defs>
<path id={pathId} d="M32,32 m-24,0 a24,24 0 1,1 48,0 a24,24 0 1,1 -48,0" />
</defs>
<text className={styles.ringText}>
<textPath href={`#${pathId}`} startOffset="0%">{text}</textPath>
</text>
</svg>
<svg className={styles.arrow} viewBox="0 0 24 24" aria-hidden="true">
<polyline points="5,15 12,8 19,15" />
</svg>
</button>
);
}06 Rotating text — Measured area 0.895% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · 9 Back To Top Button CSS: No JS, Scroll-Driven
The post this demo lives in
9 Back To Top Button CSS: No JS, Scroll-DrivenA back to top button css animation is what makes the small button that carries a visitor back up a long page fade in, sp See all demos in this post


