単語カードのようにめくれるボタン
概要
単語カードのように、ホバーするとテキストがめくれて、次のテキストが表示されるボタンです。
遊び心があり、「これどう動くのだろう?」って気になってついホバーしたくなるアニメーションです。
画面に向かって縦方向を軸に回転するため、横幅の確保が必要です。そのため、あまり長い文字には適してはいないです。
FAQの質問と回答を「一問一答」形式で出したり、「Next」→「ページ2へ」のような補足説明をつけたりという運用がいいかと思います。
実装については、buttonタグの中にspanタグを2つ、リング用のSVGを用意し、重ね合わせています。
ホバーすることで1つ目のspanタグがrotate3dによって立体的に回転し、それと同時にz-indexを操作し、2つ目のspanタグが前に出てくるように指定います。
リングはSVGで「C」のように完全な円にせず、位置を調整することでspanタグ2つを繋げているように見せています。
ホバー時には、このリングは長さと回転で動きを調整することで、めくる時に極力不自然な繋ぎ目が見えにくいようにしています。
コードサンプル
HTML
<button class="button" type="button" aria-label="more">
<span class="button__cover">お問い合わせ</span>
<span class="button__more">送信フォームへ</span>
<svg class="ring" viewBox="0 0 28 28" width="25" height="25" aria-hidden="true">
<circle cx="14" cy="14" r="12.5" pathLength="1" />
</svg>
</button>
CSS
/* ボタン本体:重ね合わせの土台 */
.button {
--duration: 0.5s;
--ease: ease-in-out;
position: relative;
display: inline-grid;
grid-template-areas: "button";
width: fit-content;
border: 0;
background: transparent;
cursor: pointer;
isolation: isolate; /* z-indexの干渉を防止 */
}
/* カードの共通見た目 */
.button__cover,
.button__more {
grid-area: button;
position: relative;
display: inline-block;
padding: 0.4em 0.8em;
width: 100%;
font-size: 1rem;
color: #f4f5f9;
text-align: center;
line-height: 1;
transform-origin: -3px -3px;
transition: transform var(--duration) var(--ease);
}
/* 疑似要素(黒い丸) */
.button__cover::before,
.button__more::before {
content: "";
position: absolute;
inset-inline-start: 5px;
inset-block-start: 5px;
width: 5px;
height: 5px;
background: #333333;
border-radius: 50%;
}
/* 初期状態:coverが上、moreが斜め下にオフセット */
.button__cover {
background: #08254f;
z-index: 2;
}
.button__more {
background: #7d8797;
transform: translate(4px, -4px);
z-index: 1;
}
/* リング(SVG) */
.ring {
position: absolute;
inset-block-start: -12.5px;
inset-inline-start: -12.5px;
rotate: 40deg;
z-index: 3;
}
.ring circle {
fill: none;
stroke: #F7DAB0;
stroke-width: 2;
stroke-dasharray: 1;
stroke-dashoffset: 0.17;
stroke-linecap: butt;
shape-rendering: geometricPrecision;
}
/* =========================
Hover(ホバー対応デバイスのみ)
========================= */
@media (any-hover: hover) {
.button:hover .button__cover {
transform: rotate3d(0, -1, 0.5, 360deg) translate(4px, -4px);
z-index: 1;
}
.button:hover .button__more {
transform: translate(0);
z-index: 2;
}
.button:hover .ring {
animation: ringRotate var(--duration) var(--ease);
}
.button:hover .ring circle {
animation: ringRotateCircle var(--duration) var(--ease);
}
}
/* =========================
Focus-visible(まとめて)
========================= */
.button:focus-visible {
outline: none;
}
.button:focus-visible .button__cover {
transform: rotate3d(0, -1, 0.5, 360deg) translate(4px, -4px);
z-index: 1;
}
.button:focus-visible .button__more {
transform: translate(0);
z-index: 2;
}
.button:focus-visible .ring {
animation: ringRotate var(--duration) var(--ease);
}
.button:focus-visible .ring circle {
animation: ringRotateCircle var(--duration) var(--ease);
}
/* アニメーション */
@keyframes ringRotate {
0% {
rotate: 25deg;
}
100% {
rotate: 40deg;
}
}
@keyframes ringRotateCircle {
0% {
stroke-dashoffset: 0.12;
}
100% {
stroke-dashoffset: 0.17;
}
}
/* 低モーション環境の配慮 */
@media (prefers-reduced-motion: reduce) {
.button__cover,
.button__more {
transition: none;
}
.button:focus-visible .ring,
.button:focus-visible .ring circle {
animation: none;
}
@media (any-hover: hover) {
.button:hover .ring,
.button:hover .ring circle {
animation: none;
}
}
}