motion
Stagger
複数要素を少しずつ時間差で出すための補助。CSS簡易版 + JS可変版。
staggerlistscrollcss+js
Customize
data-mk-stagger-step(ms)で刻み幅を調整できます。
A11y
reduced motion では実質的に同時表示に近づきます。
Install
“壊れにくさ”優先のため、依存は増やさずに ファイルをコピーして使う 形式です。
Files
まずは下記ファイルをプロジェクトに配置してください。
required-files.md
- tailwind-motion-kit/css/00_tokens.css (css)
- tailwind-motion-kit/css/02_stagger.css (css)
- tailwind-motion-kit/js/stagger.js (js)
- tailwind-motion-kit/css/01_inview-reveal.css (css)
- tailwind-motion-kit/js/inview.js (js)Option A
まずは Bundle を読み込んで、動作を確認するのがおすすめです。
globals.css
/* Bundle: まずはこれが一番簡単 */
@import "./tailwind-motion-kit/css/99_bundle.css";Option B
必要な CSS だけ読み込みたい場合はこちら。
globals.css
/* 1) CSS: グローバルCSSに追記 */
@import "./tailwind-motion-kit/css/00_tokens.css";
@import "./tailwind-motion-kit/css/02_stagger.css";
@import "./tailwind-motion-kit/css/01_inview-reveal.css";JavaScript
動きやUI制御が必要な場合のみ、初期化を追加します。
init.js
import { initStagger } from "./tailwind-motion-kit/js/stagger.js";
import { initInView } from "./tailwind-motion-kit/js/inview.js";
initStagger();
initInView();Snippets
Copy & paste
usage.html
<div class="mk-stagger" data-mk-stagger data-mk-stagger-step="90">
<div class="mk-reveal mk-reveal-up" data-mk-inview>...</div>
<div class="mk-reveal mk-reveal-up" data-mk-inview>...</div>
</div>usage.js
import { initStagger } from "./tailwind-motion-kit/js/stagger.js";
import { initInView } from "./tailwind-motion-kit/js/inview.js";
initStagger();
initInView();Files
この部品が参照する実ファイルです。
tailwind-motion-kit/css/00_tokens.csscss
tailwind-motion-kit/css/00_tokens.css
/*!
* Tailwind Motion Kit (mk) - Tokens
* --------------------------------
* 目的: 速度・距離・イージング等の“モーション言語”を統一し、
* パーツを増やしても品位が崩れない状態を作ります。
*
* 使い方:
* - :root の変数を上書きするだけで全体の動きを一括調整できます。
* - 個別要素は style="" か data-mk-* 属性で上書きできます(JSがある場合)。
*/
:root {
/* --- イージング(加減速) --- */
--mk-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--mk-ease-in: cubic-bezier(0.32, 0, 0.67, 0);
--mk-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
/* “少しだけ遊び”が欲しい時用(多用は上品さを損ねます) */
--mk-ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1);
/* --- 時間 --- */
--mk-duration-fast: 160ms;
--mk-duration-medium: 280ms;
--mk-duration-slow: 600ms;
--mk-duration-slower: 900ms;
/* コンポーネントが参照する“共通デフォルト” */
--mk-duration: var(--mk-duration-medium);
--mk-ease: var(--mk-ease-out);
--mk-delay: 0ms;
/* --- 移動量 --- */
--mk-distance-1: 6px;
--mk-distance-2: 12px;
--mk-distance-3: 20px;
--mk-distance: var(--mk-distance-2);
/* --- ぼかし --- */
--mk-blur-0: 0px;
--mk-blur-1: 6px;
--mk-blur-2: 10px;
--mk-blur: var(--mk-blur-1);
/* --- 影(浮遊感) --- */
--mk-shadow-lift: 0 12px 30px rgba(0, 0, 0, 0.14);
--mk-shadow-soft: 0 10px 22px rgba(0, 0, 0, 0.10);
/* --- アンダーライン --- */
--mk-underline-thickness: 2px;
--mk-underline-offset: 0.2em;
/* --- フォーカスリング --- */
--mk-ring-color: rgba(59, 130, 246, 0.35);
--mk-ring-size: 3px;
/* --- スケルトン(ロード中) --- */
--mk-skeleton-a: rgba(0, 0, 0, 0.06);
--mk-skeleton-b: rgba(0, 0, 0, 0.12);
--mk-skeleton-speed: 1250ms;
/* --- スピナー --- */
--mk-spinner-size: 1.2em;
--mk-spinner-border: 2px;
/* --- テキスト分割表示 --- */
--mk-char-step: 25ms; /* 1文字ごとの遅延 */
/* --- ダイアログ / ドロワー --- */
--mk-dialog-duration: var(--mk-duration-slow);
--mk-dialog-ease: var(--mk-ease-out);
--mk-dialog-backdrop-opacity: 0.45;
--mk-dialog-offset: var(--mk-distance-3);
--mk-dialog-scale-from: 0.995;
--mk-dialog-radius: 18px;
/* --- 折りたたみ(アコーディオン) --- */
--mk-collapse-duration: var(--mk-duration-medium);
--mk-collapse-ease: var(--mk-ease);
/* --- Toast / 通知 --- */
--mk-toast-gap: 10px;
--mk-toast-width: min(360px, calc(100vw - 24px));
--mk-toast-duration: var(--mk-duration-medium);
--mk-toast-ease: var(--mk-ease-out);
/* --- スクロール進捗(トップバー) --- */
--mk-scroll-progress-top: 0px;
--mk-scroll-progress-height: 3px;
--mk-scroll-progress-ease: linear;
/* --- ナビ下線インジケータ --- */
--mk-nav-underline-height: 2px;
--mk-nav-underline-radius: 999px;
--mk-nav-duration: var(--mk-duration-medium);
--mk-nav-ease: var(--mk-ease-out);
/* --- UI state(idle/loading/success/error) --- */
--mk-state-hold-success: 900ms;
--mk-state-hold-error: 1400ms;
/* --- Tabs --- */
--mk-tabs-gap: 10px;
--mk-tabs-radius: 999px;
--mk-tabs-duration: var(--mk-duration-medium);
--mk-tabs-ease: var(--mk-ease-out);
--mk-tabs-underline-height: 2px;
--mk-tabs-underline-color: currentColor;
/* --- Tooltip --- */
--mk-tooltip-bg: rgba(0, 0, 0, 0.82);
--mk-tooltip-fg: #fff;
--mk-tooltip-radius: 10px;
--mk-tooltip-padding: 10px 12px;
--mk-tooltip-gap: 8px;
--mk-tooltip-shadow: 0 18px 60px rgba(0,0,0,0.35);
--mk-tooltip-max-width: 260px;
/* --- Stepper --- */
--mk-stepper-gap: 14px;
--mk-step-size: 28px;
--mk-step-line: rgba(255, 255, 255, 0.14);
--mk-step-done: rgba(59, 130, 246, 1);
--mk-step-current: rgba(255, 255, 255, 0.92);
--mk-step-todo: rgba(255, 255, 255, 0.5);
}
/* motion減衰設定のあるユーザーには、動きを最小化して“結果”を出します */
@media (prefers-reduced-motion: reduce) {
:root {
--mk-duration-fast: 0ms;
--mk-duration-medium: 0ms;
--mk-duration-slow: 0ms;
--mk-duration-slower: 0ms;
--mk-duration: 0ms;
--mk-delay: 0ms;
}
}
tailwind-motion-kit/css/02_stagger.csscss
tailwind-motion-kit/css/02_stagger.css
/*!
* Tailwind Motion Kit (mk) - Stagger
* ---------------------------------
* “順番に現れる”を、できるだけ軽量に。
*
* 使い方(CSSだけの簡易版):
* <div class="mk-stagger">
* <div class="mk-reveal mk-reveal-up" data-mk-inview>...</div>
* <div class="mk-reveal mk-reveal-up" data-mk-inview>...</div>
* </div>
*
* 重要:
* - CSSだけの場合、遅延は固定(80ms刻み / 12個まで)です。
* - 任意の刻み幅や“もっと多い要素”は JS の initStagger を推奨します。
*/
.mk-stagger { --mk-stagger-step: 80ms; }
/* デフォルト遅延 */
.mk-stagger > * { --mk-delay: 0ms; }
/* CSS-only fallback: 12個まで */
.mk-stagger > :nth-child(1) { --mk-delay: 0ms; }
.mk-stagger > :nth-child(2) { --mk-delay: 80ms; }
.mk-stagger > :nth-child(3) { --mk-delay: 160ms; }
.mk-stagger > :nth-child(4) { --mk-delay: 240ms; }
.mk-stagger > :nth-child(5) { --mk-delay: 320ms; }
.mk-stagger > :nth-child(6) { --mk-delay: 400ms; }
.mk-stagger > :nth-child(7) { --mk-delay: 480ms; }
.mk-stagger > :nth-child(8) { --mk-delay: 560ms; }
.mk-stagger > :nth-child(9) { --mk-delay: 640ms; }
.mk-stagger > :nth-child(10) { --mk-delay: 720ms; }
.mk-stagger > :nth-child(11) { --mk-delay: 800ms; }
.mk-stagger > :nth-child(12) { --mk-delay: 880ms; }
/* JSが付与するインデックスがある場合は、こちらが優先されます */
.mk-stagger > *[style*="--mk-stagger-index"] {
/* JS側で --mk-delay も直接入れる方針なので、ここは保険 */
}
tailwind-motion-kit/js/stagger.jsjs
tailwind-motion-kit/js/stagger.js
// Tailwind Motion Kit (mk) - Stagger
// ---------------------------------
// data-mk-stagger を持つ親要素の子に、遅延(--mk-delay)を付与します。
// CSS-only fallback より柔軟(要素数 / step変更)です。
function toMs(value, fallbackMs) {
if (value == null || value === "") return fallbackMs;
const v = String(value).trim();
if (v.endsWith("ms")) return Number(v.replace("ms", "")) || fallbackMs;
if (v.endsWith("s")) return (Number(v.replace("s", "")) * 1000) || fallbackMs;
const n = Number(v);
return Number.isFinite(n) ? n : fallbackMs;
}
export function applyStagger(container, {
step = 80,
childSelector = ":scope > *",
from = 0,
} = {}) {
const stepMs = toMs(container.getAttribute("data-mk-stagger-step"), step);
const fromMs = toMs(container.getAttribute("data-mk-stagger-from"), from);
const children = Array.from(container.querySelectorAll(childSelector));
children.forEach((child, i) => {
const delay = fromMs + i * stepMs;
child.style.setProperty("--mk-delay", `${delay}ms`);
child.style.setProperty("--mk-stagger-index", String(i));
});
}
export function initStagger({
selector = "[data-mk-stagger]",
step = 80,
childSelector = ":scope > *",
from = 0,
} = {}) {
const run = () => {
document.querySelectorAll(selector).forEach((container) => {
applyStagger(container, { step, childSelector, from });
});
};
run();
return { refresh: run };
}
tailwind-motion-kit/css/01_inview-reveal.csscss
tailwind-motion-kit/css/01_inview-reveal.css
/*!
* Tailwind Motion Kit (mk) - InView Reveal
* --------------------------------------
* IntersectionObserver 等で `data-mk-inview="true"` を付けると、表示状態へ遷移します。
*
* マークアップ例:
* <div class="mk-reveal mk-reveal-up" data-mk-inview>...</div>
*
* JS を使う場合:
* import { initInView } from "./js/inview.js";
* initInView();
*/
.mk-reveal {
/* インタラクション用の変数(hover/press と合成できるように) */
--mk-interact-x: 0px;
--mk-interact-y: 0px;
--mk-interact-scale: 1;
--mk-interact-rotate: 0deg;
/* ここは“最終状態”を定義。初期状態は data-mk-inview が付いた時だけ適用します */
opacity: 1;
transform:
translate3d(0, 0, 0)
translate3d(var(--mk-interact-x), var(--mk-interact-y), 0)
scale(1)
scale(var(--mk-interact-scale))
rotate(0deg)
rotate(var(--mk-interact-rotate));
filter: blur(0);
}
/* data-mk-inview があるが true ではない → 初期状態(隠れる) */
.mk-reveal[data-mk-inview]:not([data-mk-inview="true"]) {
opacity: var(--mk-reveal-opacity-from, 0);
transform:
translate3d(
var(--mk-reveal-x, 0px),
var(--mk-reveal-y, 0px),
0
)
translate3d(var(--mk-interact-x), var(--mk-interact-y), 0)
scale(var(--mk-reveal-scale, 1))
scale(var(--mk-interact-scale))
rotate(var(--mk-reveal-rotate, 0deg))
rotate(var(--mk-interact-rotate));
filter: blur(var(--mk-reveal-blur, var(--mk-blur)));
}
/* true → 表示状態 */
.mk-reveal[data-mk-inview="true"] {
opacity: var(--mk-reveal-opacity-to, 1);
transform:
translate3d(0, 0, 0)
translate3d(var(--mk-interact-x), var(--mk-interact-y), 0)
scale(1)
scale(var(--mk-interact-scale))
rotate(0deg)
rotate(var(--mk-interact-rotate));
filter: blur(0);
}
/* トランジション(共通) */
.mk-reveal[data-mk-inview] {
transition-property: transform, opacity, filter, box-shadow;
transition-duration: var(--mk-reveal-duration, var(--mk-duration-slow));
transition-timing-function: var(--mk-reveal-ease, var(--mk-ease));
transition-delay: var(--mk-reveal-delay, var(--mk-delay));
will-change: transform, opacity, filter;
}
/* 方向プリセット(mk-reveal と併用) */
.mk-reveal-up { --mk-reveal-y: var(--mk-distance); }
.mk-reveal-down { --mk-reveal-y: calc(var(--mk-distance) * -1); }
.mk-reveal-left { --mk-reveal-x: var(--mk-distance); }
.mk-reveal-right { --mk-reveal-x: calc(var(--mk-distance) * -1); }
.mk-reveal-scale { --mk-reveal-scale: 0.985; --mk-reveal-blur: var(--mk-blur); }
.mk-reveal-fade { --mk-reveal-x: 0px; --mk-reveal-y: 0px; --mk-reveal-blur: 0px; }
.mk-reveal-rotate-s { --mk-reveal-rotate: -1deg; }
/* blur を使いたくない時 */
.mk-reveal-no-blur { --mk-reveal-blur: 0px; }
/* reduce motion: 可能なら“最終状態”へ固定(動かさない) */
@media (prefers-reduced-motion: reduce) {
.mk-reveal[data-mk-inview]:not([data-mk-inview="true"]) {
opacity: 1 !important;
transform:
translate3d(0, 0, 0)
translate3d(var(--mk-interact-x), var(--mk-interact-y), 0)
scale(1)
scale(var(--mk-interact-scale))
rotate(0deg)
rotate(var(--mk-interact-rotate)) !important;
filter: blur(0) !important;
}
.mk-reveal[data-mk-inview] {
transition: none !important;
will-change: auto;
}
}
tailwind-motion-kit/js/inview.jsjs
tailwind-motion-kit/js/inview.js
// Tailwind Motion Kit (mk) - InView observer
// -----------------------------------------
// 目的: data-mk-inview を持つ要素に対して、ビューポート侵入時に
// data-mk-inview="true" を付与し、CSS側の reveal 等を発火させます。
function prefersReducedMotion() {
return window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
}
function toMs(value, fallbackMs) {
if (value == null || value === "") return fallbackMs;
const v = String(value).trim();
if (v.endsWith("ms")) return Number(v.replace("ms", "")) || fallbackMs;
if (v.endsWith("s")) return (Number(v.replace("s", "")) * 1000) || fallbackMs;
const n = Number(v);
return Number.isFinite(n) ? n : fallbackMs;
}
function prepareElement(el) {
// data-mk-* を “CSS変数” へ反映(style="" を汚したくない時に便利)
// 例: <div data-mk-delay="120" data-mk-duration="700" data-mk-distance="16" data-mk-blur="0">
const delay = el.getAttribute("data-mk-delay");
if (delay != null && delay !== "") el.style.setProperty("--mk-delay", `${toMs(delay, 0)}ms`);
const duration = el.getAttribute("data-mk-duration");
if (duration != null && duration !== "") el.style.setProperty("--mk-duration", `${toMs(duration, 0)}ms`);
const distance = el.getAttribute("data-mk-distance");
if (distance != null && distance !== "") el.style.setProperty("--mk-distance", `${Number(distance)}px`);
const blur = el.getAttribute("data-mk-blur");
if (blur != null && blur !== "") el.style.setProperty("--mk-blur", `${Number(blur)}px`);
}
export function initInView({
selector = "[data-mk-inview]",
threshold = 0.15,
rootMargin = "0px 0px -12% 0px",
once = true,
onEnter,
onExit,
} = {}) {
const reduce = prefersReducedMotion();
const elements = new Set();
const scan = () => {
document.querySelectorAll(selector).forEach((el) => elements.add(el));
};
scan();
// reduce motion: 最初から “true” へ
if (reduce) {
elements.forEach((el) => {
prepareElement(el);
el.setAttribute("data-mk-inview", "true");
});
return {
refresh() {
scan();
elements.forEach((el) => el.setAttribute("data-mk-inview", "true"));
},
destroy() {},
observer: null,
elements,
};
}
const io = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const el = entry.target;
// 要素ごとの上書き: data-mk-inview-once="false"
const elOnceAttr = el.getAttribute("data-mk-inview-once");
const elOnce = elOnceAttr == null ? once : elOnceAttr !== "false";
if (entry.isIntersecting) {
prepareElement(el);
el.setAttribute("data-mk-inview", "true");
if (typeof onEnter === "function") onEnter(el, entry);
if (elOnce) io.unobserve(el);
} else if (!elOnce) {
el.setAttribute("data-mk-inview", "false");
if (typeof onExit === "function") onExit(el, entry);
}
});
}, { threshold, rootMargin });
elements.forEach((el) => io.observe(el));
return {
refresh() {
scan();
elements.forEach((el) => io.observe(el));
},
destroy() {
io.disconnect();
elements.clear();
},
observer: io,
elements,
};
}
Preview
Item 01
Staggered
親の mk-stagger が delay を割り当てます。
Item 02
Staggered
親の mk-stagger が delay を割り当てます。
Item 03
Staggered
親の mk-stagger が delay を割り当てます。
Tips
- • デザイン側は CSS 変数(tokens)で一括調整できます。
- • 動きは “短く・小さく・同じ癖” を優先すると品位が保てます。
- • prefers-reduced-motion を必ず考慮してください。
