転がって着地するトグルRolling Knob Toggle
.cg-fm-roll {
position: relative;
display: inline-flex;
align-items: center;
gap: 12px;
font: 700 12px/1 "Helvetica Neue", Arial, sans-serif;
color: #1B1A17;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.cg-fm-roll input {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
margin: 0;
opacity: 0;
cursor: pointer;
}
.cg-fm-roll-track {
position: relative;
width: 76px;
height: 34px;
border-radius: 999px;
background: #D8D3C6;
box-shadow: inset 0 2px 5px rgba(27, 26, 23, .28);
transition: background .45s cubic-bezier(.34, 1.56, .64, 1);
}
.cg-fm-roll:has(input:checked) .cg-fm-roll-track { background: #2F8A59; }
.cg-fm-roll:has(input:focus-visible) .cg-fm-roll-track {
outline: 3px solid #F2B707;
outline-offset: 3px;
}
/* 1層目 軌跡: ノブが通った道に光の尾が残り、遅れて消える */
.cg-fm-roll-trail {
position: absolute;
top: 50%;
left: 16px;
width: 44px;
height: 6px;
margin-top: -3px;
border-radius: 999px;
background: linear-gradient(90deg, rgba(242, 183, 7, 0), rgba(242, 183, 7, .95));
opacity: 0;
transform: scaleX(0);
transform-origin: left center;
}
.cg-fm-roll:has(input:checked) .cg-fm-roll-trail {
animation: cg-fm-roll-trail .72s ease-out;
}
/* 2層目 接地影: ノブと一緒に走り、着地でだけ濃くなる */
.cg-fm-roll-dent {
position: absolute;
bottom: 3px;
left: 7px;
width: 22px;
height: 6px;
border-radius: 50%;
background: rgba(27, 26, 23, .32);
filter: blur(2px);
transition: transform .5s cubic-bezier(.34, 1.56, .64, 1);
}
.cg-fm-roll:has(input:checked) .cg-fm-roll-dent { transform: translateX(42px); }
/* 3層目 本体: 位置は外側、潰れは内側と分けると干渉しない */
.cg-fm-roll-knob {
position: absolute;
top: 4px;
left: 4px;
width: 26px;
height: 26px;
transition: transform .5s cubic-bezier(.34, 1.56, .64, 1);
}
.cg-fm-roll:has(input:checked) .cg-fm-roll-knob { transform: translateX(42px); }
.cg-fm-roll-ball {
position: absolute;
inset: 0;
border-radius: 50%;
background: #F5F2EA;
box-shadow: 0 3px 6px rgba(27, 26, 23, .32);
}
.cg-fm-roll:has(input:checked) .cg-fm-roll-ball {
animation: cg-fm-roll-squash .58s cubic-bezier(.34, 1.56, .64, 1);
}
/* 4層目 回転の目印: 一本線が回ることで「転がった」と伝わる */
.cg-fm-roll-pin {
position: absolute;
left: 50%;
top: 5px;
width: 3px;
height: 16px;
margin-left: -1.5px;
border-radius: 999px;
background: #C7C1B2;
transition: transform .5s cubic-bezier(.34, 1.56, .64, 1);
}
.cg-fm-roll:has(input:checked) .cg-fm-roll-pin { transform: rotate(540deg); }
.cg-fm-roll-text {
position: relative;
display: inline-block;
width: 30px;
height: 13px;
}
.cg-fm-roll-off,
.cg-fm-roll-on {
position: absolute;
left: 0;
top: 0;
font-style: normal;
letter-spacing: .09em;
transition: opacity .3s ease, transform .45s cubic-bezier(.34, 1.56, .64, 1);
}
.cg-fm-roll-on {
color: #2F8A59;
opacity: 0;
transform: translateY(8px);
}
.cg-fm-roll:has(input:checked) .cg-fm-roll-off { opacity: 0; transform: translateY(-8px); }
.cg-fm-roll:has(input:checked) .cg-fm-roll-on { opacity: 1; transform: translateY(0); }
@keyframes cg-fm-roll-squash {
0% { transform: scale(1, 1); }
20% { transform: scale(1.12, .9); }
52% { transform: scale(.94, 1.08); }
76% { transform: scale(1.16, .86); }
90% { transform: scale(.97, 1.04); }
100% { transform: scale(1, 1); }
}
@keyframes cg-fm-roll-trail {
0% { opacity: 0; transform: scaleX(0); }
16% { opacity: .95; transform: scaleX(.34); }
52% { opacity: .8; transform: scaleX(1); }
78% { opacity: .35; transform: scaleX(1); }
100% { opacity: 0; transform: scaleX(1); }
}
@media (prefers-reduced-motion: reduce) {
.cg-fm-roll-track,
.cg-fm-roll-knob,
.cg-fm-roll-ball,
.cg-fm-roll-pin,
.cg-fm-roll-dent,
.cg-fm-roll-off,
.cg-fm-roll-on { transition: none; animation: none; }
.cg-fm-roll-trail { display: none; }
}
<label class="cg-fm-roll"><input type="checkbox" role="switch" aria-label="通知を受け取る"><span class="cg-fm-roll-track" aria-hidden="true"><i class="cg-fm-roll-trail"></i><i class="cg-fm-roll-dent"></i><i class="cg-fm-roll-knob"><i class="cg-fm-roll-ball"><i class="cg-fm-roll-pin"></i></i></i></span><span class="cg-fm-roll-text" aria-hidden="true"><i class="cg-fm-roll-off">OFF</i><i class="cg-fm-roll-on">ON</i></span></label>