デモ
デモを読み込めませんでした。
仕様
- 用途
- フォーム
- 動作
- DOM
- 種類
- レシピ
- 使うもの
- DrawSVGPlugin
- 動作確認
- three 0.186.0・gsap 3.15.0
- 公開日
しくみと調整
押すと、箱が一度縮んでから弾んで戻り、そのあいだに枠と地が成功の緑に塗られて、白いチェックの線が描かれます。外すときは同じタイムラインを逆再生するので、線が巻き戻ってから地が抜けます。
箱は 0.1 秒で 0.86 倍に縮め、0.5 秒の back.out(3) で戻します。塗りは縮み始めてすぐ 0.15 秒で入れ替え、線は 0.18 秒後から 0.35 秒かけて描きます。箱が戻りきる前に描き終わるので、弾みとチェックが一続きに見えます。線は DrawSVGPlugin で 0% から 100% まで描くので、線端が丸くても、描き始める前に点が残りません。
塗りは、緑を透明にした色から緑へ補間します。transparent から塗ると、黒の透明から補間することになり、途中の色が黒ずみます。
チェックの線は、塗りより明るい白にします。塗りと同じくらい暗い線だと、描くほど目立たなくなります。外れているときの枠は補足の灰色にします。罫線の色では、箱が地に溶けます。
本物の input は見えなくして label で包み、change で再生と逆再生を切り替えます。箱と文言のどちらを押しても、スペースキーでも切り替わり、フォームの送信もそのまま使えます。フォームの同意、ログイン状態の保持、ToDo リスト、絞り込みの選択に向いています。
コード
<!-- 本物の 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()));
});