デモ
デモを読み込めませんでした。
仕様
- 用途
- フォーム
- 動作
- DOM
- 種類
- レシピ
- 使うもの
- DrawSVGPlugin
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
押した四角が一瞬すぼまってから弾むように戻る、そのわずかなあいだに背景が緑で塗られ、白いチェックの線が描かれていきます。外すときは同じタイムラインを逆再生するので、線が巻き戻るように消えてから、背景の色が抜けます。
縮むのは0.86倍まで、戻りは0.5秒の back.out(3)。塗りは縮み始めてすぐに切り替わり、チェックの線は少し遅れて描き始め、四角が戻りきる前に描き終えるようにしました。弾みと線が重なるので、ひと続きの動作に見えます。線は DrawSVGPlugin で描いていて、線端を丸くしていても、描き始める前に丸い点だけが残ることはありません。
背景の塗りは、緑を透明にした色から緑へ補間しています。transparent から塗れば済みそうですが、CSS の transparent は黒を透明にした色として扱われるので、補間の途中で色が黒ずみます。線の色も、塗りより明るい白にしてあります。塗りと同じくらい暗い色の線だと、描くほど緑に埋もれていくからです。外れているときの枠線はグレーで、罫線と同じ淡い色では四角が背景に溶けます。
本物の input は見えなくして label で包み、change イベントでタイムラインの再生と逆再生を切り替えています。四角でも文言でも、スペースキーでも切り替わり、フォームの送信もふつうのチェックボックスのまま使えます。
コード
<!-- 本物の input を label で包む。チェックボックスの四角と文言のどちらを押してもよく、キーボードとフォームの送信もそのまま使える -->
<label class="check">
<input class="check__input" type="checkbox" name="remember" />
<!-- 見た目の四角は飾りなので読み上げから外す。状態は input が持つ -->
<span class="check__box" aria-hidden="true">
<svg class="check__svg" viewBox="0 0 24 24" focusable="false">
<path class="check__mark" d="M6 12.6 L10.4 17 L18.4 8.2" />
</svg>
</span>
<span class="check__label">ログイン状態を保持する</span>
</label>
.check {
--size: 22px; /* チェックボックスの四角の大きさ */
--edge: #898989; /* 外れているときの枠線(罫線の色だと、四角が背景に溶ける) */
--on: #4f8a64; /* 付いているときの塗りと枠線(成功の色) */
--mark: #ffffff; /* チェックの線。塗りより明るくする */
position: relative;
display: inline-flex;
align-items: center;
gap: 12px;
font-size: 15px;
font-weight: 600;
letter-spacing: 0.04em;
cursor: pointer;
}
/* 本物の input は見えなくするだけ(display: none にすると、キーボードで選べなくなる) */
.check__input {
position: absolute;
width: 1px;
height: 1px;
margin: 0;
opacity: 0;
pointer-events: none;
}
.check__box {
position: relative;
flex: none;
width: var(--size);
height: var(--size);
box-sizing: border-box;
border: 2px solid var(--edge);
border-radius: 5px;
}
.check__svg {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
overflow: visible;
}
/* 24 グリッドで線幅 3.2、線端と角は丸。描く準備ができるまで隠しておく(JS が見せる) */
.check__mark {
fill: none;
stroke: var(--mark);
stroke-width: 3.2;
stroke-linecap: round;
stroke-linejoin: round;
visibility: hidden;
}
/* キーボードで選んだときは、四角の外にアウトラインを表示する */
.check__input:focus-visible + .check__box {
outline: 2px solid currentColor;
outline-offset: 3px;
}
import { gsap } from "gsap";
import { DrawSVGPlugin } from "gsap/DrawSVGPlugin";
gsap.registerPlugin(DrawSVGPlugin);
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
document.querySelectorAll(".check").forEach((label) => {
const input = label.querySelector(".check__input");
const box = label.querySelector(".check__box");
const mark = label.querySelector(".check__mark");
const styles = getComputedStyle(label);
// 色は CSS で決める。GSAP は var(--name) のままでは補間できないので、値にして渡す
const edge = styles.getPropertyValue("--edge").trim();
const on = styles.getPropertyValue("--on").trim();
// transparent(黒の透明)から塗ると途中が黒ずむので、同じ色の透明から塗る
const [r, g, b] = gsap.utils.splitColor(on);
const clear = `rgba(${r}, ${g}, ${b}, 0)`;
// 縮んで弾む → その間に枠線と背景を塗る → 線を描く。外すときは同じタイムラインを逆再生する
const check = gsap
.timeline({ paused: true })
.fromTo(box, { scale: 1 }, { scale: 0.86, duration: 0.1, ease: "power2.in" }, 0)
.fromTo(box, { scale: 0.86 }, { scale: 1, duration: 0.5, ease: "back.out(3)", immediateRender: false }, 0.1)
.fromTo(box, { backgroundColor: clear, borderColor: edge }, { backgroundColor: on, borderColor: on, duration: 0.15, ease: "none" }, 0.05)
// 線端が丸くても、0% のときに頭の点が残らない(DrawSVG が破線の位置をずらす)
.fromTo(mark, { drawSVG: "0%", visibility: "visible" }, { drawSVG: "100%", duration: 0.35, ease: "power2.out" }, 0.18);
// 動きを減らす設定では、ほぼ一瞬で切り替える
if (reduceMotion) check.timeScale(20);
// 最初からチェック済み(入力を戻したときなど)は、動かさずに終わりの状態にする
if (input.checked) check.progress(1);
// label で包んであるので、四角と文言のどちらを押しても、スペースキーでも change が起きる
input.addEventListener("change", () => (input.checked ? check.play() : check.reverse()));
});