No.196
CARD CSSJS

CSS Custom Function

CSS の @function で「高さの段数を渡すと影を返す関数」を作り、カードを持ち上げると影が段数に合わせて深くなるデモ。段数は @property で登録した数値なので、影も途中の値を通って滑らかに変わります。

MY COLLECTION RANDOM ↗

DEMO

カードを押すと段数 6 まで持ち上がります(もう一度押すと戻る)

CODE

HTML
<div class="demo-stage cfn-stage">
  <div class="cfn-row">
    <button type="button" class="cfn-card" style="--rest: 1" aria-pressed="false"><span class="cfn-lv">rest 1</span>Card</button>
    <button type="button" class="cfn-card" style="--rest: 2" aria-pressed="false"><span class="cfn-lv">rest 2</span>Card</button>
    <button type="button" class="cfn-card" style="--rest: 3" aria-pressed="false"><span class="cfn-lv">rest 3</span>Card</button>
  </div>
  <p class="cfn-hint">カードを押すと段数 6 まで持ち上がります(もう一度押すと戻る)</p>
  <p class="cfn-badge"></p>
</div>
CSS
/* 段数(number)を渡すと、2層の影を返す関数 */
@function --elevation(--level <number>) {
  result:
    0 calc(var(--level) * 1px) calc(var(--level) * 3px) rgb(0 0 0 / .35),
    0 calc(var(--level) * 4px) calc(var(--level) * 10px) rgb(0 0 0 / .25);
}
@property --lift { syntax: '<number>'; inherits: false; initial-value: 1; }
.cfn-stage { position:relative; overflow:hidden; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1.1rem; padding:2.2rem 1.25rem; background:#EDEAF2; min-height:320px; }
.cfn-row { display:flex; gap:1.2rem; flex-wrap:wrap; justify-content:center; }
.cfn-card { --lift: var(--rest);
  display:flex; flex-direction:column; justify-content:space-between; align-items:flex-start; width:110px; height:130px; box-sizing:border-box; padding:.8rem;
  border:0; border-radius:14px; background:#fff; color:#1A1A1A; font:inherit; font-weight:600; font-size:1rem; text-align:left; cursor:pointer;
  translate: 0 calc(var(--lift) * -2px);
  /* @function 未対応のブラウザ向け:関数の中身と同じ計算を直接書く */
  box-shadow:
    0 calc(var(--lift) * 1px) calc(var(--lift) * 3px) rgb(0 0 0 / .35),
    0 calc(var(--lift) * 4px) calc(var(--lift) * 10px) rgb(0 0 0 / .25);
  transition: --lift .45s cubic-bezier(.22,1,.36,1); }
.cfn-card[aria-pressed="true"] { --lift: 6; }
.cfn-card:focus-visible { outline:2px solid #7B37A8; outline-offset:4px; }
.cfn-lv { color:#6B21A8; font-size:.7rem; font-family:ui-monospace,monospace; font-weight:400; }
.cfn-hint { margin:0; color:#4B4B55; font-size:.78rem; text-align:center; }
.cfn-badge { margin:0; color:#6B21A8; font-size:.74rem; font-family:ui-monospace,monospace; text-align:center; }
.cfn-badge::after { content:"@function 未対応:calc() を直接書いた影で表示中"; }
@supports (box-shadow: --elevation(1)) {
  .cfn-card { box-shadow: --elevation(var(--lift)); }
  .cfn-badge::after { content:"box-shadow: --elevation(var(--lift)) で計算中"; }
}
@media (prefers-reduced-motion: reduce) {
  .cfn-card { transition:none; }
}
JS
(function(){
  document.querySelectorAll('.cfn-card').forEach(function(c){
    c.addEventListener('click', function(){
      c.setAttribute('aria-pressed', String(c.getAttribute('aria-pressed') !== 'true'));
    });
  });
})();

AI PROMPT

Claude Code Cursor v0
PROMPT
CSS の @function で「段数を渡すと影を返す関数」を作り、カードを押すと段数に合わせて影が深くなるデモを作って。@function --elevation(--level <number>) の result に、0 calc(var(--level) * 1px) calc(var(--level) * 3px) rgb(0 0 0 / .35) と 0 calc(var(--level) * 4px) calc(var(--level) * 10px) rgb(0 0 0 / .25) の2層の影を書く。@property --lift を syntax:'<number>'・inherits:false・initial-value:1 で登録し、カード(button・aria-pressed 付き)は --lift: var(--rest)(style 属性で --rest に 1〜3 を渡す)から始め、押すと aria-pressed="true" で --lift: 6 にする。box-shadow には、@function 未対応ブラウザ向けに関数の中身と同じ calc() を直接書いた影を先に置き、@supports (box-shadow: --elevation(1)) の中で box-shadow: --elevation(var(--lift)) に置き換える。translate: 0 calc(var(--lift) * -2px) で少し浮かせ、transition: --lift .45s を付けると登録済みの数値が途中の値を通るので影も滑らかに変わる。どちらで表示しているかを ::after の文言で示す。prefers-reduced-motion: reduce の時は transition を無効化する。

Acceptance criteria: works with prefers-reduced-motion (animation disabled), zero console errors, zero layout shift

AIエージェントでの実装ガイド → Claude Code でWebアニメーションを実装する

BOOKS · PR · AMAZON

📖 なかしまぁ先生のCSSアニメーション入門 📖 UIデザインの教科書 新版 マルチデバイス時代のインターフェース設計 📖 Fundamentals of Web Animation with GSAP

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

学びを止めない · AMAZONサブスク

📚 Kindle Unlimited — 技術書・デザイン書が読み放題。30日無料体験 → 🎧 Audible — 移動中に耳で学ぶオーディオブック。30日無料体験 → 📦 Amazon Prime — Prime Reading・配送特典つき。30日無料体験 → 🎵 Music Unlimited — 作業用BGMに。1億曲が聴き放題・30日無料体験 →

※当サイトはAmazonアソシエイトプログラムの参加者です。リンクから商品をご購入いただくと、当方に紹介料が発生します。

CREATOR TOOLS · スクール(PR)

🎬 Adobe Creative Cloud — After Effects / Photoshop 業界標準ツール → 💻 SkillHacks — プログラミング・動画編集オンラインスクール → 🚀 デイトラ — Web制作スクール・実案件レベルまで習得 →

※本サイトはアフィリエイトリンクを含みます。リンク経由のご購入・お申し込みで運営者が紹介料を受け取る場合があります。

RELATED EFFECTS

↗ MORE
→ STACKED CARDS → CARD 3D PIN → GLASSMORPHISM CARD

@function(CSS のカスタム関数)とは

@function は、引数を受け取って値を返す関数を CSS の中で定義できる仕組みです。MDN では「CSS カスタム関数の定義を可能にする」アットルールと説明され、関数は --名前(引数) の形で呼び出し、返す値は result 記述子に書きます。引数と戻り値には <number> や <color> のような型を付けられ、型に合わない値を渡すと無効になります。

当サイトの Stacked Cards は重なったカードを押すと先頭が背面へ送られる演出、Card 3D Pin はカードが奥へ倒れてピンが立ち上がる3D の演出、Glassmorphism Card は背景を磨りガラスのようにぼかす半透明のカードです。本ページはカードの影の計算を関数にまとめ、段数だけで深さを決める別テーマです。

影・余白・文字の大きさのように、同じ計算式を何か所にも書いている値をまとめるのに向いています。式を直す時は関数の中を1か所直すだけで済み、使う側は段数や大きさのような意味のある数値だけを渡せます。対応が Chromium 系だけのうちは、関数の中身と同じ式を直接書いた指定を先に置き、その上に @supports で関数の呼び出しを重ねると安全です。

よくある質問

どのブラウザで使えますか?

MDN の互換性データ(browser-compat-data 2026-09-24 版)では、@function は Chrome・Edge 139 から使え、Firefox は未対応、Safari は開発版(Technology Preview)のみで、仕様も実験的な扱いです。本実装は同じ計算を calc() で直接書いた影を先に置き、@supports (box-shadow: --elevation(1)) で関数に置き換えているので、未対応のブラウザでも同じ見た目になります。

影が滑らかに変わるのはなぜですか?

段数の --lift を @property で数値(<number>)として登録しているためです。登録していないカスタムプロパティは文字列のように扱われ、transition を付けても途中の値を通らずに一気に切り替わります。数値として登録すると 1 から 6 までの途中の値を通り、そのたびに関数が影を計算し直すので、影が少しずつ深くなります。

コードは無料で使えますか?

はい。Motion LabのコードスニペットとAIプロンプトは、クレジット表記なしで自由に使えます。コピーしてそのままプロジェクトに利用できます。